1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ----------------------------------------------------------
6 # Automatic generation of lamps
7 # Author: Antonio Vazquez (antonioya)
9 # ----------------------------------------------------------
11 from math
import cos
, sin
, radians
13 from bpy
.types
import Operator
14 from bpy
.props
import EnumProperty
, FloatProperty
, IntProperty
, BoolProperty
, FloatVectorProperty
15 from .achm_tools
import *
18 # ------------------------------------------------------
19 # set predefined designs
21 # self: self container
22 # ------------------------------------------------------
24 # -----------------------
26 # -----------------------
27 if self
.preset
== "1":
28 self
.base_height
= 0.22
29 self
.base_segments
= 16
46 # -----------------------
48 # -----------------------
49 if self
.preset
== "2":
50 self
.base_height
= 0.20
51 self
.base_segments
= 16
68 # -----------------------
70 # -----------------------
71 if self
.preset
== "3":
72 self
.base_height
= 0.20
73 self
.base_segments
= 8
90 # -----------------------
92 # -----------------------
93 if self
.preset
== "4":
94 self
.base_height
= 0.15
95 self
.base_segments
= 4
98 self
.subdivide
= False
112 # ------------------------------------------------------------------
115 # ------------------------------------------------------------------
116 class ARCHIMESH_PT_Lamp(Operator
):
117 bl_idname
= "mesh.archimesh_light"
119 bl_description
= "Lamp Generator"
121 bl_options
= {'REGISTER', 'UNDO'}
123 preset
: EnumProperty(
129 ('4', "Rectangular", ""),
132 description
="Apply predefined design",
134 oldpreset
: EnumProperty(
140 ('4', "Rectangular", ""),
143 description
="Apply predefined design",
146 base_height
: FloatProperty(
148 min=0.01, max=10, default
=0.20, precision
=3,
149 description
='lamp base height',
151 base_segments
: IntProperty(
153 min=3, max=128, default
=16,
154 description
='Number of segments (vertical)',
156 base_rings
: IntProperty(
158 min=2, max=12, default
=6,
159 description
='Number of rings (horizontal)',
161 holder
: FloatProperty(
163 min=0.001, max=10, default
=0.02, precision
=3,
164 description
='Lampholder height',
166 smooth
: BoolProperty(
168 description
="Use smooth shader",
171 subdivide
: BoolProperty(
173 description
="Add subdivision modifier",
177 bz01
: FloatProperty(name
='S1', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
178 bz02
: FloatProperty(name
='S2', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
179 bz03
: FloatProperty(name
='S3', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
180 bz04
: FloatProperty(name
='S4', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
181 bz05
: FloatProperty(name
='S5', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
182 bz06
: FloatProperty(name
='S6', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
183 bz07
: FloatProperty(name
='S7', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
184 bz08
: FloatProperty(name
='S8', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
185 bz09
: FloatProperty(name
='S9', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
186 bz10
: FloatProperty(name
='S10', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
187 bz11
: FloatProperty(name
='S11', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
188 bz12
: FloatProperty(name
='S12', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
190 br01
: FloatProperty(name
='R1', min=0.001, max=10, default
=0.06, precision
=3, description
='Ring radio')
191 br02
: FloatProperty(name
='R2', min=0.001, max=10, default
=0.08, precision
=3, description
='Ring radio')
192 br03
: FloatProperty(name
='R3', min=0.001, max=10, default
=0.09, precision
=3, description
='Ring radio')
193 br04
: FloatProperty(name
='R4', min=0.001, max=10, default
=0.08, precision
=3, description
='Ring radio')
194 br05
: FloatProperty(name
='R5', min=0.001, max=10, default
=0.06, precision
=3, description
='Ring radio')
195 br06
: FloatProperty(name
='R6', min=0.001, max=10, default
=0.03, precision
=3, description
='Ring radio')
196 br07
: FloatProperty(name
='R7', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
197 br08
: FloatProperty(name
='R8', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
198 br09
: FloatProperty(name
='R9', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
199 br10
: FloatProperty(name
='R10', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
200 br11
: FloatProperty(name
='R11', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
201 br12
: FloatProperty(name
='R12', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
203 top_height
: FloatProperty(
204 name
='Height', min=0.01, max=10,
205 default
=0.20, precision
=3,
206 description
='lampshade height',
208 top_segments
: IntProperty(
209 name
='Segments', min=3, max=128,
211 description
='Number of segments (vertical)',
214 name
='R1', min=0.001, max=10,
215 default
=0.16, precision
=3,
216 description
='lampshade bottom radio',
218 tr02
: FloatProperty(name
='R2', min=0.001, max=10,
219 default
=0.08, precision
=3,
220 description
='lampshade top radio')
221 pleats
: BoolProperty(
222 name
="Pleats", description
="Create pleats in the lampshade",
226 name
='R3', min=0.001, max=1,
227 default
=0.01, precision
=3, description
='Pleats size',
229 energy
: FloatProperty(
230 name
='Light', min=0.00, max=1000,
231 default
=15, precision
=3,
232 description
='Light intensity',
234 opacity
: FloatProperty(
235 name
='Translucency', min=0.00, max=1,
236 default
=0.3, precision
=3,
237 description
='Lampshade translucency factor (1 completely translucent)',
241 crt_mat
: BoolProperty(
242 name
="Create default Cycles materials",
243 description
="Create default materials for Cycles render",
246 objcol
: FloatVectorProperty(
248 description
="Color for material",
249 default
=(1.0, 1.0, 1.0, 1.0),
255 # -----------------------------------------------------
256 # Draw (create UI interface)
257 # -----------------------------------------------------
258 # noinspection PyUnusedLocal
259 def draw(self
, context
):
261 space
= bpy
.context
.space_data
262 if not space
.local_view
:
263 # Imperial units warning
264 if bpy
.context
.scene
.unit_settings
.system
== "IMPERIAL":
266 row
.label(text
="Warning: Imperial units not supported", icon
='COLOR_RED')
269 box
.label(text
="Lamp base")
271 row
.prop(self
, 'preset')
273 row
.prop(self
, 'base_height')
274 row
.prop(self
, 'base_segments')
275 row
.prop(self
, 'base_rings')
277 row
.prop(self
, 'smooth')
278 row
.prop(self
, 'subdivide')
280 row
.prop(self
, 'holder')
282 if self
.base_rings
>= 1:
284 row
.prop(self
, 'br01')
285 row
.prop(self
, 'bz01', slider
=True)
286 if self
.base_rings
>= 2:
288 row
.prop(self
, 'br02')
289 row
.prop(self
, 'bz02', slider
=True)
290 if self
.base_rings
>= 3:
292 row
.prop(self
, 'br03')
293 row
.prop(self
, 'bz03', slider
=True)
295 if self
.base_rings
>= 4:
297 row
.prop(self
, 'br04')
298 row
.prop(self
, 'bz04', slider
=True)
299 if self
.base_rings
>= 5:
301 row
.prop(self
, 'br05')
302 row
.prop(self
, 'bz05', slider
=True)
303 if self
.base_rings
>= 6:
305 row
.prop(self
, 'br06')
306 row
.prop(self
, 'bz06', slider
=True)
308 if self
.base_rings
>= 7:
310 row
.prop(self
, 'br07')
311 row
.prop(self
, 'bz07', slider
=True)
312 if self
.base_rings
>= 8:
314 row
.prop(self
, 'br08')
315 row
.prop(self
, 'bz08', slider
=True)
316 if self
.base_rings
>= 9:
318 row
.prop(self
, 'br09')
319 row
.prop(self
, 'bz09', slider
=True)
321 if self
.base_rings
>= 10:
323 row
.prop(self
, 'br10')
324 row
.prop(self
, 'bz10', slider
=True)
325 if self
.base_rings
>= 11:
327 row
.prop(self
, 'br11')
328 row
.prop(self
, 'bz11', slider
=True)
329 if self
.base_rings
>= 12:
331 row
.prop(self
, 'br12')
332 row
.prop(self
, 'bz12', slider
=True)
335 box
.label(text
="Lampshade")
337 row
.prop(self
, 'top_height')
338 row
.prop(self
, 'top_segments')
340 row
.prop(self
, 'tr01')
341 row
.prop(self
, 'tr02')
343 row
.prop(self
, 'energy')
344 row
.prop(self
, 'opacity', slider
=True)
346 row
.prop(self
, 'pleats')
348 row
.prop(self
, 'tr03')
351 if not context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
353 box
.prop(self
, 'crt_mat')
356 row
.prop(self
, 'objcol')
359 row
.label(text
="Warning: Operator does not work in local view mode", icon
='ERROR')
361 # -----------------------------------------------------
363 # -----------------------------------------------------
364 # noinspection PyUnusedLocal
365 def execute(self
, context
):
366 if bpy
.context
.mode
== "OBJECT":
367 if self
.oldpreset
!= self
.preset
:
369 self
.oldpreset
= self
.preset
372 create_light_mesh(self
)
375 self
.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
379 # ------------------------------------------------------------------------------
381 # All custom values are passed using self container (self.myvariable)
382 # ------------------------------------------------------------------------------
383 def create_light_mesh(self
):
385 for o
in bpy
.data
.objects
:
386 if o
.select_get() is True:
388 bpy
.ops
.object.select_all(action
='DESELECT')
394 # ------------------------------------------------------------------------------
396 # All custom values are passed using self container (self.myvariable)
397 # ------------------------------------------------------------------------------
398 def generate_light(self
):
399 location
= bpy
.context
.scene
.cursor
.location
400 myloc
= copy(location
) # copy location to keep 3D cursor position
401 # ---------------------
403 # ---------------------
404 mydata
= create_light_base("Lamp_base", self
.base_height
,
405 myloc
.x
, myloc
.y
, myloc
.z
,
406 self
.base_segments
, self
.base_rings
,
407 [self
.br01
, self
.br02
, self
.br03
, self
.br04
, self
.br05
, self
.br06
,
408 self
.br07
, self
.br08
, self
.br09
, self
.br10
, self
.br11
, self
.br12
],
409 (self
.bz01
, self
.bz02
, self
.bz03
, self
.bz04
, self
.bz05
, self
.bz06
,
410 self
.bz07
, self
.bz08
, self
.bz09
, self
.bz10
, self
.bz11
, self
.bz12
),
412 self
.crt_mat
, self
.objcol
)
416 remove_doubles(mybase
)
422 set_modifier_subsurf(mybase
)
423 # ---------------------
425 # ---------------------
426 myholder
= create_lightholder("Lampholder", self
.holder
,
427 myloc
.x
, myloc
.y
, myloc
.z
,
430 remove_doubles(myholder
)
431 set_normals(myholder
)
434 myholder
.parent
= mybase
435 myholder
.location
.x
= 0
436 myholder
.location
.y
= 0
437 myholder
.location
.z
= posz
438 # ---------------------
440 # ---------------------
441 mystrings
= create_lightholder_strings("Lampstrings", self
.holder
,
442 myloc
.x
, myloc
.y
, myloc
.z
,
447 remove_doubles(mystrings
)
448 set_normals(mystrings
)
450 mystrings
.parent
= myholder
451 mystrings
.location
.x
= 0
452 mystrings
.location
.y
= 0
453 mystrings
.location
.z
= 0.03
454 # ---------------------
456 # ---------------------
457 mytop
= create_lightshade("Lampshade", self
.top_height
,
458 myloc
.x
, myloc
.y
, myloc
.z
,
460 self
.tr01
, self
.tr02
,
461 self
.pleats
, self
.tr03
,
465 remove_doubles(mytop
)
467 if self
.pleats
is False:
470 mytop
.parent
= mybase
473 mytop
.location
.z
= posz
+ self
.holder
474 # ---------------------
476 # ---------------------
478 bpy
.ops
.mesh
.primitive_uv_sphere_add(segments
=16, radius
=radbulb
)
479 mybulb
= bpy
.data
.objects
[bpy
.context
.active_object
.name
]
480 mybulb
.name
= "Lamp_Bulb"
481 mybulb
.parent
= myholder
482 mybulb
.location
= (0, 0, radbulb
+ self
.holder
+ 0.04)
483 if self
.crt_mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
484 mat
= create_emission_material(mybulb
.name
, True, 0.8, 0.8, 0.8, self
.energy
)
485 set_material(mybulb
, mat
)
488 for o
in bpy
.data
.objects
:
489 if o
.select_get() is True:
492 mybase
.select_set(True)
493 bpy
.context
.view_layer
.objects
.active
= mybase
498 # ------------------------------------------------------------------------------
501 # objName: Name for the new object
502 # height: Size in Z axis
503 # pX: position X axis
504 # pY: position Y axis
505 # pZ: position Z axis
506 # segments: number of segments
507 # rings: number of rings
508 # radios: ring radios
509 # ratios: Z shift ratios
510 # subdivide: Subdivision flag
511 # mat: Flag for creating materials
513 # ------------------------------------------------------------------------------
514 def create_light_base(objname
, height
, px
, py
, pz
, segments
, rings
, radios
, ratios
, subdivide
, mat
, objcol
):
516 h
= height
/ (rings
- 1)
519 for f
in range(0, rings
):
520 listheight
.extend([z
+ (z
* ratios
[f
])])
523 mydata
= create_cylinder_data(segments
, listheight
,
525 True, True, False, 0, subdivide
)
529 mymesh
= bpy
.data
.meshes
.new(objname
)
530 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
531 bpy
.context
.collection
.objects
.link(mycylinder
)
533 mymesh
.from_pydata(myvertex
, [], myfaces
)
534 mymesh
.update(calc_edges
=True)
536 mycylinder
.location
.x
= px
537 mycylinder
.location
.y
= py
538 mycylinder
.location
.z
= pz
540 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
542 mymat
= create_diffuse_material(mycylinder
.name
+ "_material", True, rgb
[0], rgb
[1], rgb
[2], rgb
[0], rgb
[1],
544 set_material(mycylinder
, mymat
)
546 return mycylinder
, listheight
[len(listheight
) - 1]
549 # ------------------------------------------------------------------------------
552 # objName: Name for the new object
553 # height: Size in Z axis
554 # pX: position X axis
555 # pY: position Y axis
556 # pZ: position Z axis
557 # mat: Flag for creating materials
558 # ------------------------------------------------------------------------------
559 def create_lightholder(objname
, height
, px
, py
, pz
, mat
):
560 mydata
= create_cylinder_data(16, [0, height
, height
+ 0.005, height
+ 0.008, height
+ 0.05],
561 [0.005, 0.005, 0.010, 0.018, 0.018],
562 False, False, False, 0, False)
566 mymesh
= bpy
.data
.meshes
.new(objname
)
567 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
568 bpy
.context
.collection
.objects
.link(mycylinder
)
570 mymesh
.from_pydata(myvertex
, [], myfaces
)
571 mymesh
.update(calc_edges
=True)
573 mycylinder
.location
.x
= px
574 mycylinder
.location
.y
= py
575 mycylinder
.location
.z
= pz
578 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
579 mat
= create_diffuse_material(mycylinder
.name
+ "_material", True, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.1)
580 set_material(mycylinder
, mat
)
585 # ------------------------------------------------------------------------------
586 # Create lampholder strings
588 # objName: Name for the new object
589 # height: Size in Z axis
590 # pX: position X axis
591 # pY: position Y axis
592 # pZ: position Z axis
593 # radio: radio of lampshade
594 # shadeh: height of lampshader
595 # mat: Flag for creating materials
596 # ------------------------------------------------------------------------------
597 def create_lightholder_strings(objname
, height
, px
, py
, pz
, radio
, shadeh
, mat
):
598 mydata
= create_cylinder_data(32, [height
+ 0.005, height
+ 0.005, height
+ 0.006, height
+ 0.006],
599 [0.018, 0.025, 0.025, 0.018],
600 False, False, False, 0, False)
604 mymesh
= bpy
.data
.meshes
.new(objname
)
605 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
606 bpy
.context
.collection
.objects
.link(mycylinder
)
608 mymesh
.from_pydata(myvertex
, [], myfaces
)
609 mymesh
.update(calc_edges
=True)
611 mycylinder
.location
.x
= px
612 mycylinder
.location
.y
= py
613 mycylinder
.location
.z
= pz
615 box1
= create_box_segments("Lamp_B1", shadeh
- 0.036, radio
- 0.023)
616 box1
.parent
= mycylinder
617 box1
.location
= (0.021, 0, height
+ 0.004)
619 box2
= create_box_segments("Lamp_B2", shadeh
- 0.036, -radio
+ 0.023)
620 box2
.parent
= mycylinder
621 box2
.location
= (-0.021, 0, height
+ 0.004)
624 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
625 mat
= create_diffuse_material(mycylinder
.name
+ "_material", True, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.1)
626 set_material(mycylinder
, mat
)
627 set_material(box1
, mat
)
628 set_material(box2
, mat
)
633 # ------------------------------------------------------------------------------
636 # objName: Name for the new object
637 # height: Size in Z axis
638 # pX: position X axis
639 # pY: position Y axis
640 # pZ: position Z axis
641 # segments: number of segments
642 # radio1: ring radio 1
643 # radio2: ring radio 2
644 # pleats: flag for pleats
645 # pleatsize: difference in radios (less)
646 # opacity: opacity factor
647 # mat: Flag for creating materials
648 # ------------------------------------------------------------------------------
649 def create_lightshade(objname
, height
, px
, py
, pz
, segments
, radio1
, radio2
, pleats
, pleatsize
, opacity
, mat
):
651 radios
= [radio1
- gap
, radio1
- gap
, radio1
, radio2
, radio2
- gap
, radio2
- gap
]
652 heights
= [gap
* 2, 0, 0, height
, height
, height
- (gap
* 2)]
653 mydata
= create_cylinder_data(segments
, heights
,
655 False, False, pleats
, pleatsize
, False)
659 mymesh
= bpy
.data
.meshes
.new(objname
)
660 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
661 bpy
.context
.collection
.objects
.link(mycylinder
)
663 mymesh
.from_pydata(myvertex
, [], myfaces
)
664 mymesh
.update(calc_edges
=True)
666 mycylinder
.location
.x
= px
667 mycylinder
.location
.y
= py
668 mycylinder
.location
.z
= pz
670 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
671 mymat
= create_translucent_material(mycylinder
.name
+ "_material", True, 0.8, 0.65, 0.45, 0.8, 0.65, 0.45,
673 set_material(mycylinder
, mymat
)
678 # ------------------------------------------------------------------------------
679 # Create box segments
681 # objName: Name for the new object
682 # height: Size in Z axis
683 # shift: Shift movement
684 # ------------------------------------------------------------------------------
685 def create_box_segments(objname
, height
, shift
):
687 myvertex
= [(0, 0, 0), (0, gap
, 0), (gap
, gap
, 0), (gap
, 0, 0),
689 (shift
, gap
, height
),
690 (shift
+ gap
, gap
, height
),
691 (shift
+ gap
, 0, height
)]
692 myfaces
= [(6, 5, 1, 2), (7, 6, 2, 3), (4, 7, 3, 0), (1, 5, 4, 0)]
694 mymesh
= bpy
.data
.meshes
.new(objname
)
695 mysegment
= bpy
.data
.objects
.new(objname
, mymesh
)
696 bpy
.context
.collection
.objects
.link(mysegment
)
698 mymesh
.from_pydata(myvertex
, [], myfaces
)
699 mymesh
.update(calc_edges
=True)
701 mysegment
.location
.x
= 0
702 mysegment
.location
.y
= 0
703 mysegment
.location
.z
= 0
708 # ------------------------------------------------------------------------------
709 # Create cylinders data
711 # segments: Number of pies
712 # listHeight: list of heights
713 # listRadio: list of radios
715 # bottom: bottom face flag
716 # pleats: flag for pleats
717 # pleatsize: difference in radios (less)
718 # subdiv: fix subdivision problem
719 # ------------------------------------------------------------------------------
720 def create_cylinder_data(segments
, listheight
, listradio
, bottom
, top
, pleats
, pleatsize
, subdiv
):
724 # Add at element 0 to fix subdivision problems
725 listheight
.insert(0, listheight
[0] + 0.001)
726 listradio
.insert(0, listradio
[0])
727 # Add at last element to fix subdivision problems
728 e
= len(listheight
) - 1
729 listheight
.insert(e
, listheight
[e
] + 0.001)
730 listradio
.insert(e
, listradio
[e
])
731 # -------------------------------------
733 # -------------------------------------
738 for i
in range(segments
):
739 x
= cos(radians(seg
)) * (listradio
[idx
] + rp
)
740 y
= sin(radians(seg
)) * (listradio
[idx
] + rp
)
741 mypoint
= [(x
, y
, z
)]
742 myvertex
.extend(mypoint
)
743 seg
+= 360 / segments
745 if pleats
is True and rp
== 0:
751 # -------------------------------------
753 # -------------------------------------
754 for r
in range(0, len(listheight
) - 1):
757 for n
in range(0, segments
):
761 myface
= [(n
+ s
, n
+ s
- segments
+ 1, n
+ s
+ 1, n
+ s
+ segments
)]
762 myfaces
.extend(myface
)
764 myface
= [(n
+ s
, n
+ s
+ 1, n
+ s
+ segments
+ 1, n
+ s
+ segments
)]
765 myfaces
.extend(myface
)
772 for f
in range(0, segments
):
780 for f
in range(len(myvertex
) - segments
, len(myvertex
)):
784 return myvertex
, myfaces