# SPDX-License-Identifier: GPL-2.0-or-later # ---------------------------------------------------------- # Automatic generation of lamps # Author: Antonio Vazquez (antonioya) # # ---------------------------------------------------------- import bpy from math import cos, sin, radians from copy import copy from bpy.types import Operator from bpy.props import EnumProperty, FloatProperty, IntProperty, BoolProperty, FloatVectorProperty from .achm_tools import * # ------------------------------------------------------ # set predefined designs # # self: self container # ------------------------------------------------------ def set_preset(self): # ----------------------- # Sphere # ----------------------- if self.preset == "1": self.base_height = 0.22 self.base_segments = 16 self.base_rings = 6 self.smooth = True self.subdivide = True self.br01 = 0.05 self.br02 = 0.07 self.br03 = 0.11 self.br04 = 0.11 self.br05 = 0.07 self.br06 = 0.03 self.bz01 = 0 self.bz02 = -1 self.bz03 = -0.456 self.bz04 = 0.089 self.bz05 = -0.038 self.bz06 = -0.165 # ----------------------- # Pear # ----------------------- if self.preset == "2": self.base_height = 0.20 self.base_segments = 16 self.base_rings = 6 self.smooth = True self.subdivide = True self.br01 = 0.056 self.br02 = 0.062 self.br03 = 0.072 self.br04 = 0.090 self.br05 = 0.074 self.br06 = 0.03 self.bz01 = 0 self.bz02 = 0 self.bz03 = 0 self.bz04 = 0 self.bz05 = 0 self.bz06 = 0 # ----------------------- # Vase # ----------------------- if self.preset == "3": self.base_height = 0.20 self.base_segments = 8 self.base_rings = 6 self.smooth = True self.subdivide = True self.br01 = 0.05 self.br02 = 0.11 self.br03 = 0.15 self.br04 = 0.07 self.br05 = 0.05 self.br06 = 0.03 self.bz01 = 0 self.bz02 = 0 self.bz03 = 0 self.bz04 = 0 self.bz05 = 0 self.bz06 = 0 # ----------------------- # Rectangular # ----------------------- if self.preset == "4": self.base_height = 0.15 self.base_segments = 4 self.base_rings = 5 self.smooth = False self.subdivide = False self.br01 = 0.08 self.br02 = 0.08 self.br03 = 0.08 self.br04 = 0.08 self.br05 = 0.03 self.bz01 = 0 self.bz02 = 0 self.bz03 = 0 self.bz04 = 0.25 self.bz05 = 0 # ------------------------------------------------------------------ # Define UI class # Lamps # ------------------------------------------------------------------ class ARCHIMESH_PT_Lamp(Operator): bl_idname = "mesh.archimesh_light" bl_label = "Lamp" bl_description = "Lamp Generator" bl_category = 'View' bl_options = {'REGISTER', 'UNDO'} # preset preset: EnumProperty( items=( ('0', "None", ""), ('1', "Sphere", ""), ('2', "Pear", ""), ('3', "Vase", ""), ('4', "Rectangular", ""), ), name="Predefined", description="Apply predefined design", ) oldpreset: EnumProperty( items=( ('0', "None", ""), ('1', "Sphere", ""), ('2', "Pear", ""), ('3', "Vase", ""), ('4', "Rectangular", ""), ), name="Predefined", description="Apply predefined design", ) base_height: FloatProperty( name='Height', min=0.01, max=10, default=0.20, precision=3, description='lamp base height', ) base_segments: IntProperty( name='Segments', min=3, max=128, default=16, description='Number of segments (vertical)', ) base_rings: IntProperty( name='Rings', min=2, max=12, default=6, description='Number of rings (horizontal)', ) holder: FloatProperty( name='Lampholder', min=0.001, max=10, default=0.02, precision=3, description='Lampholder height', ) smooth: BoolProperty( name="Smooth", description="Use smooth shader", default=True, ) subdivide: BoolProperty( name="Subdivide", description="Add subdivision modifier", default=True, ) bz01: FloatProperty(name='S1', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz02: FloatProperty(name='S2', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz03: FloatProperty(name='S3', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz04: FloatProperty(name='S4', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz05: FloatProperty(name='S5', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz06: FloatProperty(name='S6', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz07: FloatProperty(name='S7', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz08: FloatProperty(name='S8', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz09: FloatProperty(name='S9', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz10: FloatProperty(name='S10', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz11: FloatProperty(name='S11', min=-1, max=1, default=0, precision=3, description='Z shift factor') bz12: FloatProperty(name='S12', min=-1, max=1, default=0, precision=3, description='Z shift factor') br01: FloatProperty(name='R1', min=0.001, max=10, default=0.06, precision=3, description='Ring radio') br02: FloatProperty(name='R2', min=0.001, max=10, default=0.08, precision=3, description='Ring radio') br03: FloatProperty(name='R3', min=0.001, max=10, default=0.09, precision=3, description='Ring radio') br04: FloatProperty(name='R4', min=0.001, max=10, default=0.08, precision=3, description='Ring radio') br05: FloatProperty(name='R5', min=0.001, max=10, default=0.06, precision=3, description='Ring radio') br06: FloatProperty(name='R6', min=0.001, max=10, default=0.03, precision=3, description='Ring radio') br07: FloatProperty(name='R7', min=0.001, max=10, default=0.10, precision=3, description='Ring radio') br08: FloatProperty(name='R8', min=0.001, max=10, default=0.10, precision=3, description='Ring radio') br09: FloatProperty(name='R9', min=0.001, max=10, default=0.10, precision=3, description='Ring radio') br10: FloatProperty(name='R10', min=0.001, max=10, default=0.10, precision=3, description='Ring radio') br11: FloatProperty(name='R11', min=0.001, max=10, default=0.10, precision=3, description='Ring radio') br12: FloatProperty(name='R12', min=0.001, max=10, default=0.10, precision=3, description='Ring radio') top_height: FloatProperty( name='Height', min=0.01, max=10, default=0.20, precision=3, description='lampshade height', ) top_segments: IntProperty( name='Segments', min=3, max=128, default=32, description='Number of segments (vertical)', ) tr01: FloatProperty( name='R1', min=0.001, max=10, default=0.16, precision=3, description='lampshade bottom radio', ) tr02: FloatProperty(name='R2', min=0.001, max=10, default=0.08, precision=3, description='lampshade top radio') pleats: BoolProperty( name="Pleats", description="Create pleats in the lampshade", default=False, ) tr03: FloatProperty( name='R3', min=0.001, max=1, default=0.01, precision=3, description='Pleats size', ) energy: FloatProperty( name='Light', min=0.00, max=1000, default=15, precision=3, description='Light intensity', ) opacity: FloatProperty( name='Translucency', min=0.00, max=1, default=0.3, precision=3, description='Lampshade translucency factor (1 completely translucent)', ) # Materials crt_mat: BoolProperty( name="Create default Cycles materials", description="Create default materials for Cycles render", default=True, ) objcol: FloatVectorProperty( name="Color", description="Color for material", default=(1.0, 1.0, 1.0, 1.0), min=0.1, max=1, subtype='COLOR', size=4, ) # ----------------------------------------------------- # Draw (create UI interface) # ----------------------------------------------------- # noinspection PyUnusedLocal def draw(self, context): layout = self.layout space = bpy.context.space_data if not space.local_view: # Imperial units warning if bpy.context.scene.unit_settings.system == "IMPERIAL": row = layout.row() row.label(text="Warning: Imperial units not supported", icon='COLOR_RED') box = layout.box() box.label(text="Lamp base") row = box.row() row.prop(self, 'preset') row = box.row() row.prop(self, 'base_height') row.prop(self, 'base_segments') row.prop(self, 'base_rings') row = box.row() row.prop(self, 'smooth') row.prop(self, 'subdivide') row = box.row() row.prop(self, 'holder') if self.base_rings >= 1: row = box.row() row.prop(self, 'br01') row.prop(self, 'bz01', slider=True) if self.base_rings >= 2: row = box.row() row.prop(self, 'br02') row.prop(self, 'bz02', slider=True) if self.base_rings >= 3: row = box.row() row.prop(self, 'br03') row.prop(self, 'bz03', slider=True) if self.base_rings >= 4: row = box.row() row.prop(self, 'br04') row.prop(self, 'bz04', slider=True) if self.base_rings >= 5: row = box.row() row.prop(self, 'br05') row.prop(self, 'bz05', slider=True) if self.base_rings >= 6: row = box.row() row.prop(self, 'br06') row.prop(self, 'bz06', slider=True) if self.base_rings >= 7: row = box.row() row.prop(self, 'br07') row.prop(self, 'bz07', slider=True) if self.base_rings >= 8: row = box.row() row.prop(self, 'br08') row.prop(self, 'bz08', slider=True) if self.base_rings >= 9: row = box.row() row.prop(self, 'br09') row.prop(self, 'bz09', slider=True) if self.base_rings >= 10: row = box.row() row.prop(self, 'br10') row.prop(self, 'bz10', slider=True) if self.base_rings >= 11: row = box.row() row.prop(self, 'br11') row.prop(self, 'bz11', slider=True) if self.base_rings >= 12: row = box.row() row.prop(self, 'br12') row.prop(self, 'bz12', slider=True) box = layout.box() box.label(text="Lampshade") row = box.row() row.prop(self, 'top_height') row.prop(self, 'top_segments') row = box.row() row.prop(self, 'tr01') row.prop(self, 'tr02') row = box.row() row.prop(self, 'energy') row.prop(self, 'opacity', slider=True) row = box.row() row.prop(self, 'pleats') if self.pleats: row.prop(self, 'tr03') box = layout.box() if not context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: box.enabled = False box.prop(self, 'crt_mat') if self.crt_mat: row = box.row() row.prop(self, 'objcol') else: row = layout.row() row.label(text="Warning: Operator does not work in local view mode", icon='ERROR') # ----------------------------------------------------- # Execute # ----------------------------------------------------- # noinspection PyUnusedLocal def execute(self, context): if bpy.context.mode == "OBJECT": if self.oldpreset != self.preset: set_preset(self) self.oldpreset = self.preset # Create lamp create_light_mesh(self) return {'FINISHED'} else: self.report({'WARNING'}, "Archimesh: Option only valid in Object mode") return {'CANCELLED'} # ------------------------------------------------------------------------------ # Generate mesh data # All custom values are passed using self container (self.myvariable) # ------------------------------------------------------------------------------ def create_light_mesh(self): # deactivate others for o in bpy.data.objects: if o.select_get() is True: o.select_set(False) bpy.ops.object.select_all(action='DESELECT') generate_light(self) return # ------------------------------------------------------------------------------ # Generate lamps # All custom values are passed using self container (self.myvariable) # ------------------------------------------------------------------------------ def generate_light(self): location = bpy.context.scene.cursor.location myloc = copy(location) # copy location to keep 3D cursor position # --------------------- # Lamp base # --------------------- mydata = create_light_base("Lamp_base", self.base_height, myloc.x, myloc.y, myloc.z, self.base_segments, self.base_rings, [self.br01, self.br02, self.br03, self.br04, self.br05, self.br06, self.br07, self.br08, self.br09, self.br10, self.br11, self.br12], (self.bz01, self.bz02, self.bz03, self.bz04, self.bz05, self.bz06, self.bz07, self.bz08, self.bz09, self.bz10, self.bz11, self.bz12), self.subdivide, self.crt_mat, self.objcol) mybase = mydata[0] posz = mydata[1] # refine remove_doubles(mybase) set_normals(mybase) # Smooth if self.smooth: set_smooth(mybase) if self.subdivide: set_modifier_subsurf(mybase) # --------------------- # Lampholder # --------------------- myholder = create_lightholder("Lampholder", self.holder, myloc.x, myloc.y, myloc.z, self.crt_mat) # refine remove_doubles(myholder) set_normals(myholder) set_smooth(myholder) myholder.parent = mybase myholder.location.x = 0 myholder.location.y = 0 myholder.location.z = posz # --------------------- # Lamp strings # --------------------- mystrings = create_lightholder_strings("Lampstrings", self.holder, myloc.x, myloc.y, myloc.z, self.tr02, self.top_height, self.crt_mat) # refine remove_doubles(mystrings) set_normals(mystrings) mystrings.parent = myholder mystrings.location.x = 0 mystrings.location.y = 0 mystrings.location.z = 0.03 # --------------------- # Lampshade # --------------------- mytop = create_lightshade("Lampshade", self.top_height, myloc.x, myloc.y, myloc.z, self.top_segments, self.tr01, self.tr02, self.pleats, self.tr03, self.opacity, self.crt_mat) # refine remove_doubles(mytop) set_normals(mytop) if self.pleats is False: set_smooth(mytop) mytop.parent = mybase mytop.location.x = 0 mytop.location.y = 0 mytop.location.z = posz + self.holder # --------------------- # Light bulb # --------------------- radbulb = 0.02 bpy.ops.mesh.primitive_uv_sphere_add(segments=16, radius=radbulb) mybulb = bpy.data.objects[bpy.context.active_object.name] mybulb.name = "Lamp_Bulb" mybulb.parent = myholder mybulb.location = (0, 0, radbulb + self.holder + 0.04) if self.crt_mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: mat = create_emission_material(mybulb.name, True, 0.8, 0.8, 0.8, self.energy) set_material(mybulb, mat) # deactivate others for o in bpy.data.objects: if o.select_get() is True: o.select_set(False) mybase.select_set(True) bpy.context.view_layer.objects.active = mybase return # ------------------------------------------------------------------------------ # Create lamp base # # objName: Name for the new object # height: Size in Z axis # pX: position X axis # pY: position Y axis # pZ: position Z axis # segments: number of segments # rings: number of rings # radios: ring radios # ratios: Z shift ratios # subdivide: Subdivision flag # mat: Flag for creating materials # objcol: Color # ------------------------------------------------------------------------------ def create_light_base(objname, height, px, py, pz, segments, rings, radios, ratios, subdivide, mat, objcol): # Calculate heights h = height / (rings - 1) listheight = [] z = 0 for f in range(0, rings): listheight.extend([z + (z * ratios[f])]) z += h mydata = create_cylinder_data(segments, listheight, radios, True, True, False, 0, subdivide) myvertex = mydata[0] myfaces = mydata[1] mymesh = bpy.data.meshes.new(objname) mycylinder = bpy.data.objects.new(objname, mymesh) bpy.context.collection.objects.link(mycylinder) mymesh.from_pydata(myvertex, [], myfaces) mymesh.update(calc_edges=True) # Position mycylinder.location.x = px mycylinder.location.y = py mycylinder.location.z = pz # Materials if mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: rgb = objcol mymat = create_diffuse_material(mycylinder.name + "_material", True, rgb[0], rgb[1], rgb[2], rgb[0], rgb[1], rgb[2], 0.1) set_material(mycylinder, mymat) return mycylinder, listheight[len(listheight) - 1] # ------------------------------------------------------------------------------ # Create lampholder # # objName: Name for the new object # height: Size in Z axis # pX: position X axis # pY: position Y axis # pZ: position Z axis # mat: Flag for creating materials # ------------------------------------------------------------------------------ def create_lightholder(objname, height, px, py, pz, mat): mydata = create_cylinder_data(16, [0, height, height + 0.005, height + 0.008, height + 0.05], [0.005, 0.005, 0.010, 0.018, 0.018], False, False, False, 0, False) myvertex = mydata[0] myfaces = mydata[1] mymesh = bpy.data.meshes.new(objname) mycylinder = bpy.data.objects.new(objname, mymesh) bpy.context.collection.objects.link(mycylinder) mymesh.from_pydata(myvertex, [], myfaces) mymesh.update(calc_edges=True) # Position mycylinder.location.x = px mycylinder.location.y = py mycylinder.location.z = pz # Materials if mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: mat = create_diffuse_material(mycylinder.name + "_material", True, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.1) set_material(mycylinder, mat) return mycylinder # ------------------------------------------------------------------------------ # Create lampholder strings # # objName: Name for the new object # height: Size in Z axis # pX: position X axis # pY: position Y axis # pZ: position Z axis # radio: radio of lampshade # shadeh: height of lampshader # mat: Flag for creating materials # ------------------------------------------------------------------------------ def create_lightholder_strings(objname, height, px, py, pz, radio, shadeh, mat): mydata = create_cylinder_data(32, [height + 0.005, height + 0.005, height + 0.006, height + 0.006], [0.018, 0.025, 0.025, 0.018], False, False, False, 0, False) myvertex = mydata[0] myfaces = mydata[1] mymesh = bpy.data.meshes.new(objname) mycylinder = bpy.data.objects.new(objname, mymesh) bpy.context.collection.objects.link(mycylinder) mymesh.from_pydata(myvertex, [], myfaces) mymesh.update(calc_edges=True) # Position mycylinder.location.x = px mycylinder.location.y = py mycylinder.location.z = pz # Box1 box1 = create_box_segments("Lamp_B1", shadeh - 0.036, radio - 0.023) box1.parent = mycylinder box1.location = (0.021, 0, height + 0.004) # Box2 box2 = create_box_segments("Lamp_B2", shadeh - 0.036, -radio + 0.023) box2.parent = mycylinder box2.location = (-0.021, 0, height + 0.004) # Materials if mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: mat = create_diffuse_material(mycylinder.name + "_material", True, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.1) set_material(mycylinder, mat) set_material(box1, mat) set_material(box2, mat) return mycylinder # ------------------------------------------------------------------------------ # Create lampshade # # objName: Name for the new object # height: Size in Z axis # pX: position X axis # pY: position Y axis # pZ: position Z axis # segments: number of segments # radio1: ring radio 1 # radio2: ring radio 2 # pleats: flag for pleats # pleatsize: difference in radios (less) # opacity: opacity factor # mat: Flag for creating materials # ------------------------------------------------------------------------------ def create_lightshade(objname, height, px, py, pz, segments, radio1, radio2, pleats, pleatsize, opacity, mat): gap = 0.002 radios = [radio1 - gap, radio1 - gap, radio1, radio2, radio2 - gap, radio2 - gap] heights = [gap * 2, 0, 0, height, height, height - (gap * 2)] mydata = create_cylinder_data(segments, heights, radios, False, False, pleats, pleatsize, False) myvertex = mydata[0] myfaces = mydata[1] mymesh = bpy.data.meshes.new(objname) mycylinder = bpy.data.objects.new(objname, mymesh) bpy.context.collection.objects.link(mycylinder) mymesh.from_pydata(myvertex, [], myfaces) mymesh.update(calc_edges=True) # Position mycylinder.location.x = px mycylinder.location.y = py mycylinder.location.z = pz # materials if mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}: mymat = create_translucent_material(mycylinder.name + "_material", True, 0.8, 0.65, 0.45, 0.8, 0.65, 0.45, opacity) set_material(mycylinder, mymat) return mycylinder # ------------------------------------------------------------------------------ # Create box segments # # objName: Name for the new object # height: Size in Z axis # shift: Shift movement # ------------------------------------------------------------------------------ def create_box_segments(objname, height, shift): gap = 0.001 myvertex = [(0, 0, 0), (0, gap, 0), (gap, gap, 0), (gap, 0, 0), (shift, 0, height), (shift, gap, height), (shift + gap, gap, height), (shift + gap, 0, height)] myfaces = [(6, 5, 1, 2), (7, 6, 2, 3), (4, 7, 3, 0), (1, 5, 4, 0)] mymesh = bpy.data.meshes.new(objname) mysegment = bpy.data.objects.new(objname, mymesh) bpy.context.collection.objects.link(mysegment) mymesh.from_pydata(myvertex, [], myfaces) mymesh.update(calc_edges=True) # Position mysegment.location.x = 0 mysegment.location.y = 0 mysegment.location.z = 0 return mysegment # ------------------------------------------------------------------------------ # Create cylinders data # # segments: Number of pies # listHeight: list of heights # listRadio: list of radios # top: top face flag # bottom: bottom face flag # pleats: flag for pleats # pleatsize: difference in radios (less) # subdiv: fix subdivision problem # ------------------------------------------------------------------------------ def create_cylinder_data(segments, listheight, listradio, bottom, top, pleats, pleatsize, subdiv): myvertex = [] myfaces = [] if subdiv: # Add at element 0 to fix subdivision problems listheight.insert(0, listheight[0] + 0.001) listradio.insert(0, listradio[0]) # Add at last element to fix subdivision problems e = len(listheight) - 1 listheight.insert(e, listheight[e] + 0.001) listradio.insert(e, listradio[e]) # ------------------------------------- # Vertices # ------------------------------------- idx = 0 rp = 0 for z in listheight: seg = 0 for i in range(segments): x = cos(radians(seg)) * (listradio[idx] + rp) y = sin(radians(seg)) * (listradio[idx] + rp) mypoint = [(x, y, z)] myvertex.extend(mypoint) seg += 360 / segments # pleats if pleats is True and rp == 0: rp = -pleatsize else: rp = 0 idx += 1 # ------------------------------------- # Faces # ------------------------------------- for r in range(0, len(listheight) - 1): s = r * segments t = 1 for n in range(0, segments): t += 1 if t > segments: t = 1 myface = [(n + s, n + s - segments + 1, n + s + 1, n + s + segments)] myfaces.extend(myface) else: myface = [(n + s, n + s + 1, n + s + segments + 1, n + s + segments)] myfaces.extend(myface) # ----------------- # bottom face # ----------------- if bottom: fa = [] for f in range(0, segments): fa.extend([f]) myfaces.extend([fa]) # ----------------- # top face # ----------------- if top: fa = [] for f in range(len(myvertex) - segments, len(myvertex)): fa.extend([f]) myfaces.extend([fa]) return myvertex, myfaces