1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 # ----------------------------------------------------------
22 # Automatic generation of lamps
23 # Author: Antonio Vazquez (antonioya)
25 # ----------------------------------------------------------
27 from math
import cos
, sin
, radians
29 from bpy
.types
import Operator
30 from bpy
.props
import EnumProperty
, FloatProperty
, IntProperty
, BoolProperty
, FloatVectorProperty
31 from .achm_tools
import *
34 # ------------------------------------------------------
35 # set predefined designs
37 # self: self container
38 # ------------------------------------------------------
40 # -----------------------
42 # -----------------------
43 if self
.preset
== "1":
44 self
.base_height
= 0.22
45 self
.base_segments
= 16
62 # -----------------------
64 # -----------------------
65 if self
.preset
== "2":
66 self
.base_height
= 0.20
67 self
.base_segments
= 16
84 # -----------------------
86 # -----------------------
87 if self
.preset
== "3":
88 self
.base_height
= 0.20
89 self
.base_segments
= 8
106 # -----------------------
108 # -----------------------
109 if self
.preset
== "4":
110 self
.base_height
= 0.15
111 self
.base_segments
= 4
114 self
.subdivide
= False
128 # ------------------------------------------------------------------
131 # ------------------------------------------------------------------
132 class ARCHIMESH_PT_Lamp(Operator
):
133 bl_idname
= "mesh.archimesh_light"
135 bl_description
= "Lamp Generator"
137 bl_options
= {'REGISTER', 'UNDO'}
139 preset
: EnumProperty(
145 ('4', "Rectangular", ""),
148 description
="Apply predefined design",
150 oldpreset
: EnumProperty(
156 ('4', "Rectangular", ""),
159 description
="Apply predefined design",
162 base_height
: FloatProperty(
164 min=0.01, max=10, default
=0.20, precision
=3,
165 description
='lamp base height',
167 base_segments
: IntProperty(
169 min=3, max=128, default
=16,
170 description
='Number of segments (vertical)',
172 base_rings
: IntProperty(
174 min=2, max=12, default
=6,
175 description
='Number of rings (horizontal)',
177 holder
: FloatProperty(
179 min=0.001, max=10, default
=0.02, precision
=3,
180 description
='Lampholder height',
182 smooth
: BoolProperty(
184 description
="Use smooth shader",
187 subdivide
: BoolProperty(
189 description
="Add subdivision modifier",
193 bz01
: FloatProperty(name
='S1', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
194 bz02
: FloatProperty(name
='S2', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
195 bz03
: FloatProperty(name
='S3', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
196 bz04
: FloatProperty(name
='S4', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
197 bz05
: FloatProperty(name
='S5', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
198 bz06
: FloatProperty(name
='S6', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
199 bz07
: FloatProperty(name
='S7', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
200 bz08
: FloatProperty(name
='S8', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
201 bz09
: FloatProperty(name
='S9', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
202 bz10
: FloatProperty(name
='S10', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
203 bz11
: FloatProperty(name
='S11', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
204 bz12
: FloatProperty(name
='S12', min=-1, max=1, default
=0, precision
=3, description
='Z shift factor')
206 br01
: FloatProperty(name
='R1', min=0.001, max=10, default
=0.06, precision
=3, description
='Ring radio')
207 br02
: FloatProperty(name
='R2', min=0.001, max=10, default
=0.08, precision
=3, description
='Ring radio')
208 br03
: FloatProperty(name
='R3', min=0.001, max=10, default
=0.09, precision
=3, description
='Ring radio')
209 br04
: FloatProperty(name
='R4', min=0.001, max=10, default
=0.08, precision
=3, description
='Ring radio')
210 br05
: FloatProperty(name
='R5', min=0.001, max=10, default
=0.06, precision
=3, description
='Ring radio')
211 br06
: FloatProperty(name
='R6', min=0.001, max=10, default
=0.03, precision
=3, description
='Ring radio')
212 br07
: FloatProperty(name
='R7', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
213 br08
: FloatProperty(name
='R8', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
214 br09
: FloatProperty(name
='R9', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
215 br10
: FloatProperty(name
='R10', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
216 br11
: FloatProperty(name
='R11', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
217 br12
: FloatProperty(name
='R12', min=0.001, max=10, default
=0.10, precision
=3, description
='Ring radio')
219 top_height
: FloatProperty(
220 name
='Height', min=0.01, max=10,
221 default
=0.20, precision
=3,
222 description
='lampshade height',
224 top_segments
: IntProperty(
225 name
='Segments', min=3, max=128,
227 description
='Number of segments (vertical)',
230 name
='R1', min=0.001, max=10,
231 default
=0.16, precision
=3,
232 description
='lampshade bottom radio',
234 tr02
: FloatProperty(name
='R2', min=0.001, max=10,
235 default
=0.08, precision
=3,
236 description
='lampshade top radio')
237 pleats
: BoolProperty(
238 name
="Pleats", description
="Create pleats in the lampshade",
242 name
='R3', min=0.001, max=1,
243 default
=0.01, precision
=3, description
='Pleats size',
245 energy
: FloatProperty(
246 name
='Light', min=0.00, max=1000,
247 default
=15, precision
=3,
248 description
='Light intensity',
250 opacity
: FloatProperty(
251 name
='Translucency', min=0.00, max=1,
252 default
=0.3, precision
=3,
253 description
='Lampshade translucency factor (1 completely translucent)',
257 crt_mat
: BoolProperty(
258 name
="Create default Cycles materials",
259 description
="Create default materials for Cycles render",
262 objcol
: FloatVectorProperty(
264 description
="Color for material",
265 default
=(1.0, 1.0, 1.0, 1.0),
271 # -----------------------------------------------------
272 # Draw (create UI interface)
273 # -----------------------------------------------------
274 # noinspection PyUnusedLocal
275 def draw(self
, context
):
277 space
= bpy
.context
.space_data
278 if not space
.local_view
:
279 # Imperial units warning
280 if bpy
.context
.scene
.unit_settings
.system
== "IMPERIAL":
282 row
.label(text
="Warning: Imperial units not supported", icon
='COLOR_RED')
285 box
.label(text
="Lamp base")
287 row
.prop(self
, 'preset')
289 row
.prop(self
, 'base_height')
290 row
.prop(self
, 'base_segments')
291 row
.prop(self
, 'base_rings')
293 row
.prop(self
, 'smooth')
294 row
.prop(self
, 'subdivide')
296 row
.prop(self
, 'holder')
298 if self
.base_rings
>= 1:
300 row
.prop(self
, 'br01')
301 row
.prop(self
, 'bz01', slider
=True)
302 if self
.base_rings
>= 2:
304 row
.prop(self
, 'br02')
305 row
.prop(self
, 'bz02', slider
=True)
306 if self
.base_rings
>= 3:
308 row
.prop(self
, 'br03')
309 row
.prop(self
, 'bz03', slider
=True)
311 if self
.base_rings
>= 4:
313 row
.prop(self
, 'br04')
314 row
.prop(self
, 'bz04', slider
=True)
315 if self
.base_rings
>= 5:
317 row
.prop(self
, 'br05')
318 row
.prop(self
, 'bz05', slider
=True)
319 if self
.base_rings
>= 6:
321 row
.prop(self
, 'br06')
322 row
.prop(self
, 'bz06', slider
=True)
324 if self
.base_rings
>= 7:
326 row
.prop(self
, 'br07')
327 row
.prop(self
, 'bz07', slider
=True)
328 if self
.base_rings
>= 8:
330 row
.prop(self
, 'br08')
331 row
.prop(self
, 'bz08', slider
=True)
332 if self
.base_rings
>= 9:
334 row
.prop(self
, 'br09')
335 row
.prop(self
, 'bz09', slider
=True)
337 if self
.base_rings
>= 10:
339 row
.prop(self
, 'br10')
340 row
.prop(self
, 'bz10', slider
=True)
341 if self
.base_rings
>= 11:
343 row
.prop(self
, 'br11')
344 row
.prop(self
, 'bz11', slider
=True)
345 if self
.base_rings
>= 12:
347 row
.prop(self
, 'br12')
348 row
.prop(self
, 'bz12', slider
=True)
351 box
.label(text
="Lampshade")
353 row
.prop(self
, 'top_height')
354 row
.prop(self
, 'top_segments')
356 row
.prop(self
, 'tr01')
357 row
.prop(self
, 'tr02')
359 row
.prop(self
, 'energy')
360 row
.prop(self
, 'opacity', slider
=True)
362 row
.prop(self
, 'pleats')
364 row
.prop(self
, 'tr03')
367 if not context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
369 box
.prop(self
, 'crt_mat')
372 row
.prop(self
, 'objcol')
375 row
.label(text
="Warning: Operator does not work in local view mode", icon
='ERROR')
377 # -----------------------------------------------------
379 # -----------------------------------------------------
380 # noinspection PyUnusedLocal
381 def execute(self
, context
):
382 if bpy
.context
.mode
== "OBJECT":
383 if self
.oldpreset
!= self
.preset
:
385 self
.oldpreset
= self
.preset
388 create_light_mesh(self
)
391 self
.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
395 # ------------------------------------------------------------------------------
397 # All custom values are passed using self container (self.myvariable)
398 # ------------------------------------------------------------------------------
399 def create_light_mesh(self
):
401 for o
in bpy
.data
.objects
:
402 if o
.select_get() is True:
404 bpy
.ops
.object.select_all(action
='DESELECT')
410 # ------------------------------------------------------------------------------
412 # All custom values are passed using self container (self.myvariable)
413 # ------------------------------------------------------------------------------
414 def generate_light(self
):
415 location
= bpy
.context
.scene
.cursor
.location
416 myloc
= copy(location
) # copy location to keep 3D cursor position
417 # ---------------------
419 # ---------------------
420 mydata
= create_light_base("Lamp_base", self
.base_height
,
421 myloc
.x
, myloc
.y
, myloc
.z
,
422 self
.base_segments
, self
.base_rings
,
423 [self
.br01
, self
.br02
, self
.br03
, self
.br04
, self
.br05
, self
.br06
,
424 self
.br07
, self
.br08
, self
.br09
, self
.br10
, self
.br11
, self
.br12
],
425 (self
.bz01
, self
.bz02
, self
.bz03
, self
.bz04
, self
.bz05
, self
.bz06
,
426 self
.bz07
, self
.bz08
, self
.bz09
, self
.bz10
, self
.bz11
, self
.bz12
),
428 self
.crt_mat
, self
.objcol
)
432 remove_doubles(mybase
)
438 set_modifier_subsurf(mybase
)
439 # ---------------------
441 # ---------------------
442 myholder
= create_lightholder("Lampholder", self
.holder
,
443 myloc
.x
, myloc
.y
, myloc
.z
,
446 remove_doubles(myholder
)
447 set_normals(myholder
)
450 myholder
.parent
= mybase
451 myholder
.location
.x
= 0
452 myholder
.location
.y
= 0
453 myholder
.location
.z
= posz
454 # ---------------------
456 # ---------------------
457 mystrings
= create_lightholder_strings("Lampstrings", self
.holder
,
458 myloc
.x
, myloc
.y
, myloc
.z
,
463 remove_doubles(mystrings
)
464 set_normals(mystrings
)
466 mystrings
.parent
= myholder
467 mystrings
.location
.x
= 0
468 mystrings
.location
.y
= 0
469 mystrings
.location
.z
= 0.03
470 # ---------------------
472 # ---------------------
473 mytop
= create_lightshade("Lampshade", self
.top_height
,
474 myloc
.x
, myloc
.y
, myloc
.z
,
476 self
.tr01
, self
.tr02
,
477 self
.pleats
, self
.tr03
,
481 remove_doubles(mytop
)
483 if self
.pleats
is False:
486 mytop
.parent
= mybase
489 mytop
.location
.z
= posz
+ self
.holder
490 # ---------------------
492 # ---------------------
494 bpy
.ops
.mesh
.primitive_uv_sphere_add(segments
=16, radius
=radbulb
)
495 mybulb
= bpy
.data
.objects
[bpy
.context
.active_object
.name
]
496 mybulb
.name
= "Lamp_Bulb"
497 mybulb
.parent
= myholder
498 mybulb
.location
= (0, 0, radbulb
+ self
.holder
+ 0.04)
499 if self
.crt_mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
500 mat
= create_emission_material(mybulb
.name
, True, 0.8, 0.8, 0.8, self
.energy
)
501 set_material(mybulb
, mat
)
504 for o
in bpy
.data
.objects
:
505 if o
.select_get() is True:
508 mybase
.select_set(True)
509 bpy
.context
.view_layer
.objects
.active
= mybase
514 # ------------------------------------------------------------------------------
517 # objName: Name for the new object
518 # height: Size in Z axis
519 # pX: position X axis
520 # pY: position Y axis
521 # pZ: position Z axis
522 # segments: number of segments
523 # rings: number of rings
524 # radios: ring radios
525 # ratios: Z shift ratios
526 # subdivide: Subdivision flag
527 # mat: Flag for creating materials
529 # ------------------------------------------------------------------------------
530 def create_light_base(objname
, height
, px
, py
, pz
, segments
, rings
, radios
, ratios
, subdivide
, mat
, objcol
):
532 h
= height
/ (rings
- 1)
535 for f
in range(0, rings
):
536 listheight
.extend([z
+ (z
* ratios
[f
])])
539 mydata
= create_cylinder_data(segments
, listheight
,
541 True, True, False, 0, subdivide
)
545 mymesh
= bpy
.data
.meshes
.new(objname
)
546 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
547 bpy
.context
.collection
.objects
.link(mycylinder
)
549 mymesh
.from_pydata(myvertex
, [], myfaces
)
550 mymesh
.update(calc_edges
=True)
552 mycylinder
.location
.x
= px
553 mycylinder
.location
.y
= py
554 mycylinder
.location
.z
= pz
556 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
558 mymat
= create_diffuse_material(mycylinder
.name
+ "_material", True, rgb
[0], rgb
[1], rgb
[2], rgb
[0], rgb
[1],
560 set_material(mycylinder
, mymat
)
562 return mycylinder
, listheight
[len(listheight
) - 1]
565 # ------------------------------------------------------------------------------
568 # objName: Name for the new object
569 # height: Size in Z axis
570 # pX: position X axis
571 # pY: position Y axis
572 # pZ: position Z axis
573 # mat: Flag for creating materials
574 # ------------------------------------------------------------------------------
575 def create_lightholder(objname
, height
, px
, py
, pz
, mat
):
576 mydata
= create_cylinder_data(16, [0, height
, height
+ 0.005, height
+ 0.008, height
+ 0.05],
577 [0.005, 0.005, 0.010, 0.018, 0.018],
578 False, False, False, 0, False)
582 mymesh
= bpy
.data
.meshes
.new(objname
)
583 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
584 bpy
.context
.collection
.objects
.link(mycylinder
)
586 mymesh
.from_pydata(myvertex
, [], myfaces
)
587 mymesh
.update(calc_edges
=True)
589 mycylinder
.location
.x
= px
590 mycylinder
.location
.y
= py
591 mycylinder
.location
.z
= pz
594 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
595 mat
= create_diffuse_material(mycylinder
.name
+ "_material", True, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.1)
596 set_material(mycylinder
, mat
)
601 # ------------------------------------------------------------------------------
602 # Create lampholder strings
604 # objName: Name for the new object
605 # height: Size in Z axis
606 # pX: position X axis
607 # pY: position Y axis
608 # pZ: position Z axis
609 # radio: radio of lampshade
610 # shadeh: height of lampshader
611 # mat: Flag for creating materials
612 # ------------------------------------------------------------------------------
613 def create_lightholder_strings(objname
, height
, px
, py
, pz
, radio
, shadeh
, mat
):
614 mydata
= create_cylinder_data(32, [height
+ 0.005, height
+ 0.005, height
+ 0.006, height
+ 0.006],
615 [0.018, 0.025, 0.025, 0.018],
616 False, False, False, 0, False)
620 mymesh
= bpy
.data
.meshes
.new(objname
)
621 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
622 bpy
.context
.collection
.objects
.link(mycylinder
)
624 mymesh
.from_pydata(myvertex
, [], myfaces
)
625 mymesh
.update(calc_edges
=True)
627 mycylinder
.location
.x
= px
628 mycylinder
.location
.y
= py
629 mycylinder
.location
.z
= pz
631 box1
= create_box_segments("Lamp_B1", shadeh
- 0.036, radio
- 0.023)
632 box1
.parent
= mycylinder
633 box1
.location
= (0.021, 0, height
+ 0.004)
635 box2
= create_box_segments("Lamp_B2", shadeh
- 0.036, -radio
+ 0.023)
636 box2
.parent
= mycylinder
637 box2
.location
= (-0.021, 0, height
+ 0.004)
640 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
641 mat
= create_diffuse_material(mycylinder
.name
+ "_material", True, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.1)
642 set_material(mycylinder
, mat
)
643 set_material(box1
, mat
)
644 set_material(box2
, mat
)
649 # ------------------------------------------------------------------------------
652 # objName: Name for the new object
653 # height: Size in Z axis
654 # pX: position X axis
655 # pY: position Y axis
656 # pZ: position Z axis
657 # segments: number of segments
658 # radio1: ring radio 1
659 # radio2: ring radio 2
660 # pleats: flag for pleats
661 # pleatsize: difference in radios (less)
662 # opacity: opacity factor
663 # mat: Flag for creating materials
664 # ------------------------------------------------------------------------------
665 def create_lightshade(objname
, height
, px
, py
, pz
, segments
, radio1
, radio2
, pleats
, pleatsize
, opacity
, mat
):
667 radios
= [radio1
- gap
, radio1
- gap
, radio1
, radio2
, radio2
- gap
, radio2
- gap
]
668 heights
= [gap
* 2, 0, 0, height
, height
, height
- (gap
* 2)]
669 mydata
= create_cylinder_data(segments
, heights
,
671 False, False, pleats
, pleatsize
, False)
675 mymesh
= bpy
.data
.meshes
.new(objname
)
676 mycylinder
= bpy
.data
.objects
.new(objname
, mymesh
)
677 bpy
.context
.collection
.objects
.link(mycylinder
)
679 mymesh
.from_pydata(myvertex
, [], myfaces
)
680 mymesh
.update(calc_edges
=True)
682 mycylinder
.location
.x
= px
683 mycylinder
.location
.y
= py
684 mycylinder
.location
.z
= pz
686 if mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
687 mymat
= create_translucent_material(mycylinder
.name
+ "_material", True, 0.8, 0.65, 0.45, 0.8, 0.65, 0.45,
689 set_material(mycylinder
, mymat
)
694 # ------------------------------------------------------------------------------
695 # Create box segments
697 # objName: Name for the new object
698 # height: Size in Z axis
699 # shift: Shift movement
700 # ------------------------------------------------------------------------------
701 def create_box_segments(objname
, height
, shift
):
703 myvertex
= [(0, 0, 0), (0, gap
, 0), (gap
, gap
, 0), (gap
, 0, 0),
705 (shift
, gap
, height
),
706 (shift
+ gap
, gap
, height
),
707 (shift
+ gap
, 0, height
)]
708 myfaces
= [(6, 5, 1, 2), (7, 6, 2, 3), (4, 7, 3, 0), (1, 5, 4, 0)]
710 mymesh
= bpy
.data
.meshes
.new(objname
)
711 mysegment
= bpy
.data
.objects
.new(objname
, mymesh
)
712 bpy
.context
.collection
.objects
.link(mysegment
)
714 mymesh
.from_pydata(myvertex
, [], myfaces
)
715 mymesh
.update(calc_edges
=True)
717 mysegment
.location
.x
= 0
718 mysegment
.location
.y
= 0
719 mysegment
.location
.z
= 0
724 # ------------------------------------------------------------------------------
725 # Create cylinders data
727 # segments: Number of pies
728 # listHeight: list of heights
729 # listRadio: list of radios
731 # bottom: bottom face flag
732 # pleats: flag for pleats
733 # pleatsize: difference in radios (less)
734 # subdiv: fix subdivision problem
735 # ------------------------------------------------------------------------------
736 def create_cylinder_data(segments
, listheight
, listradio
, bottom
, top
, pleats
, pleatsize
, subdiv
):
740 # Add at element 0 to fix subdivision problems
741 listheight
.insert(0, listheight
[0] + 0.001)
742 listradio
.insert(0, listradio
[0])
743 # Add at last element to fix subdivision problems
744 e
= len(listheight
) - 1
745 listheight
.insert(e
, listheight
[e
] + 0.001)
746 listradio
.insert(e
, listradio
[e
])
747 # -------------------------------------
749 # -------------------------------------
754 for i
in range(segments
):
755 x
= cos(radians(seg
)) * (listradio
[idx
] + rp
)
756 y
= sin(radians(seg
)) * (listradio
[idx
] + rp
)
757 mypoint
= [(x
, y
, z
)]
758 myvertex
.extend(mypoint
)
759 seg
+= 360 / segments
761 if pleats
is True and rp
== 0:
767 # -------------------------------------
769 # -------------------------------------
770 for r
in range(0, len(listheight
) - 1):
773 for n
in range(0, segments
):
777 myface
= [(n
+ s
, n
+ s
- segments
+ 1, n
+ s
+ 1, n
+ s
+ segments
)]
778 myfaces
.extend(myface
)
780 myface
= [(n
+ s
, n
+ s
+ 1, n
+ s
+ segments
+ 1, n
+ s
+ segments
)]
781 myfaces
.extend(myface
)
788 for f
in range(0, segments
):
796 for f
in range(len(myvertex
) - segments
, len(myvertex
)):
800 return myvertex
, myfaces