glTF exporter: keep backward compatibility for export options
[blender-addons.git] / archimesh / achm_room_maker.py
bloba1a60b6da6f01477bdd09f32c2b640d9c071158d
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # ----------------------------------------------------------
4 # Automatic generation of rooms
5 # Author: Antonio Vazquez (antonioya) and Eduardo Gutierrez
7 # ----------------------------------------------------------
8 # noinspection PyUnresolvedReferences
9 import bpy
10 from math import sin, cos, fabs, radians
11 from mathutils import Vector
12 from datetime import datetime
13 from time import time
14 from os import path
15 from bpy.types import Operator, PropertyGroup, Object, Panel
16 from bpy.props import StringProperty, FloatProperty, BoolProperty, IntProperty, FloatVectorProperty, \
17 CollectionProperty, EnumProperty
18 from bpy_extras.io_utils import ExportHelper, ImportHelper
19 from .achm_tools import *
22 # ----------------------------------------------------------
23 # Export menu UI
24 # ----------------------------------------------------------
25 class ARCHIMESH_OT_ExportRoom(Operator, ExportHelper):
26 bl_idname = "io_export.roomdata"
27 bl_description = 'Export Room data (.dat)'
28 bl_category = 'View'
29 bl_label = "Export"
31 # From ExportHelper. Filter filenames.
32 filename_ext = ".dat"
33 filter_glob: StringProperty(
34 default="*.dat",
35 options={'HIDDEN'},
38 filepath: StringProperty(
39 name="File Path",
40 description="File path used for exporting room data file",
41 maxlen=1024, default="",
44 # ----------------------------------------------------------
45 # Execute
46 # ----------------------------------------------------------
47 # noinspection PyUnusedLocal
48 def execute(self, context):
49 print("Exporting:", self.properties.filepath)
50 # noinspection PyBroadException
51 try:
52 myobj = bpy.context.active_object
53 mydata = myobj.RoomGenerator[0]
55 # -------------------------------
56 # extract path and filename
57 # -------------------------------
58 (filepath, filename) = path.split(self.properties.filepath)
59 print('Exporting %s' % filename)
60 # -------------------------------
61 # Open output file
62 # -------------------------------
63 realpath = path.realpath(path.expanduser(self.properties.filepath))
64 fout = open(realpath, 'w')
66 st = datetime.fromtimestamp(time()).strftime('%Y-%m-%d %H:%M:%S')
67 fout.write("# Archimesh room export data\n")
68 fout.write("# " + st + "\n")
69 fout.write("#======================================================\n")
71 fout.write("name=" + myobj.name + "\n")
72 fout.write("height=" + str(round(mydata.room_height, 3)) + "\n")
73 fout.write("thickness=" + str(round(mydata.wall_width, 3)) + "\n")
74 fout.write("inverse=" + str(mydata.inverse) + "\n")
75 fout.write("ceiling=" + str(mydata.ceiling) + "\n")
76 fout.write("floor=" + str(mydata.floor) + "\n")
77 fout.write("close=" + str(mydata.merge) + "\n")
79 # Walls
80 fout.write("#\n# Walls\n#\n")
81 fout.write("walls=" + str(mydata.wall_num) + "\n")
82 i = 0
83 for w in mydata.walls:
84 if i < mydata.wall_num:
85 i += 1
86 fout.write("w=" + str(round(w.w, 3)))
87 # if w.a == True: # advance
88 fout.write(",a=" + str(w.a) + ",")
89 fout.write("r=" + str(round(w.r, 1)) + ",")
90 fout.write("h=" + str(w.h) + ",")
91 fout.write("m=" + str(round(w.m, 3)) + ",")
92 fout.write("f=" + str(round(w.f, 3)) + ",")
93 fout.write("c=" + str(w.curved) + ",")
94 fout.write("cf=" + str(round(w.curve_factor, 1)) + ",")
95 fout.write("cd=" + str(round(w.curve_arc_deg, 1)) + ",")
96 fout.write("cs=" + str(w.curve_steps) + "\n")
97 # else:
98 # fOut.write("\n")
100 # Baseboard
101 fout.write("#\n# Baseboard\n#\n")
102 fout.write("baseboard=" + str(mydata.baseboard) + "\n")
103 fout.write("baseh=" + str(round(mydata.base_height, 3)) + "\n")
104 fout.write("baset=" + str(round(mydata.base_width, 3)) + "\n")
105 # Shell
106 fout.write("#\n# Wall Cover\n#\n")
107 fout.write("shell=" + str(mydata.shell) + "\n")
108 fout.write("shellh=" + str(round(mydata.shell_height, 3)) + "\n")
109 fout.write("shellt=" + str(round(mydata.shell_thick, 3)) + "\n")
110 fout.write("shellf=" + str(round(mydata.shell_factor, 3)) + "\n")
111 fout.write("shellb=" + str(round(mydata.shell_bfactor, 3)) + "\n")
113 # Materials
114 fout.write("#\n# Materials\n#\n")
115 fout.write("materials=" + str(mydata.crt_mat) + "\n")
117 fout.close()
118 self.report({'INFO'}, realpath + "successfully exported")
119 except:
120 self.report({'ERROR'}, "Unable to export room data")
122 return {'FINISHED'}
124 # ----------------------------------------------------------
125 # Invoke
126 # ----------------------------------------------------------
128 # noinspection PyUnusedLocal
129 def invoke(self, context, event):
130 context.window_manager.fileselect_add(self)
131 return {'RUNNING_MODAL'}
134 # ----------------------------------------------------------
135 # Import menu UI
136 # ----------------------------------------------------------
137 class ARCHIMESH_OT_ImportRoom(Operator, ImportHelper):
138 bl_idname = "io_import.roomdata"
139 bl_description = 'Import Room data (.dat)'
140 bl_category = 'View'
141 bl_label = "Import"
143 # From Helper. Filter filenames.
144 filename_ext = ".dat"
145 filter_glob: StringProperty(
146 default="*.dat",
147 options={'HIDDEN'},
150 filepath: StringProperty(
151 name="File Path",
152 description="File path used for exporting room data file",
153 maxlen=1024, default="",
156 # ----------------------------------------------------------
157 # Execute
158 # ----------------------------------------------------------
159 # noinspection PyUnusedLocal
160 def execute(self, context):
161 print("Importing:", self.properties.filepath)
162 # noinspection PyBroadException
163 try:
164 realpath = path.realpath(path.expanduser(self.properties.filepath))
165 finput = open(realpath)
166 line = finput.readline()
168 myobj = bpy.context.active_object
169 mydata = myobj.RoomGenerator[0]
170 # ----------------------------------
171 # Loop all records from file
172 # ----------------------------------
173 idx = 0 # index of each wall
174 while line:
175 if line[:1] != '#':
176 if "name=" in line.lower():
177 myobj.name = line[5:-1]
179 elif "height=" in line.lower():
180 mydata.room_height = float(line[7:-1])
182 elif "thickness=" in line.lower():
183 mydata.wall_width = float(line[10:-1])
185 elif "inverse=" in line.lower():
186 if line[8:-4].upper() == "T":
187 mydata.inverse = True
188 else:
189 mydata.inverse = False
191 elif "ceiling=" in line.lower():
192 if line[8:-4].upper() == "T":
193 mydata.ceiling = True
194 else:
195 mydata.ceiling = False
197 elif "floor=" in line.lower():
198 if line[6:-4].upper() == "T":
199 mydata.floor = True
200 else:
201 mydata.floor = False
203 elif "close=" in line.lower():
204 if line[6:-4].upper() == "T":
205 mydata.merge = True
206 else:
207 mydata.merge = False
208 elif "baseboard=" in line.lower():
209 if line[10:-4].upper() == "T":
210 mydata.baseboard = True
211 else:
212 mydata.baseboard = False
213 elif "baseh=" in line.lower():
214 mydata.base_height = float(line[6:-1])
215 elif "baset=" in line.lower():
216 mydata.base_width = float(line[6:-1])
217 elif "shell=" in line.lower():
218 if line[6:-4].upper() == "T":
219 mydata.shell = True
220 else:
221 mydata.shell = False
222 elif "shellh=" in line.lower():
223 mydata.shell_height = float(line[7:-1])
224 elif "shellt=" in line.lower():
225 mydata.shell_thick = float(line[6:-1])
226 elif "shellf=" in line.lower():
227 mydata.shell_factor = float(line[6:-1])
228 elif "shellb=" in line.lower():
229 mydata.shell_bfactor = float(line[6:-1])
230 elif "walls=" in line.lower():
231 mydata.wall_num = int(line[6:-1])
233 # ---------------------
234 # Walls Data
235 # ---------------------
236 elif "w=" in line.lower() and idx < mydata.wall_num:
237 # get all pieces
238 buf = line[:-1] + ","
239 s = buf.split(",")
240 for e in s:
241 param = e.lower()
242 if "w=" in param:
243 mydata.walls[idx].w = float(e[2:])
244 elif "a=" in param:
245 if "true" == param[2:]:
246 mydata.walls[idx].a = True
247 else:
248 mydata.walls[idx].a = False
249 elif "r=" in param:
250 mydata.walls[idx].r = float(e[2:])
251 elif "h=" in param:
252 mydata.walls[idx].h = e[2:]
253 elif "m=" in param:
254 mydata.walls[idx].m = float(e[2:])
255 elif "f=" == param[0:2]:
256 mydata.walls[idx].f = float(e[2:])
257 elif "c=" in param:
258 if "true" == param[2:]:
259 mydata.walls[idx].curved = True
260 else:
261 mydata.walls[idx].curved = False
262 elif "cf=" in param:
263 mydata.walls[idx].curve_factor = float(e[3:])
264 elif "cd=" in param:
265 mydata.walls[idx].curve_arc_deg = float(e[3:])
266 elif "cs=" in param:
267 mydata.walls[idx].curve_steps = int(e[3:])
268 idx += 1
270 elif "materials=" in line.lower():
271 if line[10:-4].upper() == "T":
272 mydata.crt_mat = True
273 else:
274 mydata.crt_mat = False
276 line = finput.readline()
278 finput.close()
279 self.report({'INFO'}, realpath + "successfully imported")
280 except:
281 self.report({'ERROR'}, "Unable to import room data")
283 return {'FINISHED'}
285 # ----------------------------------------------------------
286 # Invoke
287 # ----------------------------------------------------------
288 # noinspection PyUnusedLocal
289 def invoke(self, context, event):
290 context.window_manager.fileselect_add(self)
291 return {'RUNNING_MODAL'}
294 # ------------------------------------------------------------------
295 # Define operator class to create rooms
296 # ------------------------------------------------------------------
297 class ARCHIMESH_OT_Room(Operator):
298 bl_idname = "mesh.archimesh_room"
299 bl_label = "Room"
300 bl_description = "Generate room with walls, baseboard, floor and ceiling"
301 bl_category = 'View'
302 bl_options = {'REGISTER', 'UNDO'}
304 # -----------------------------------------------------
305 # Draw (create UI interface)
306 # -----------------------------------------------------
307 # noinspection PyUnusedLocal
308 def draw(self, context):
309 layout = self.layout
310 row = layout.row()
311 row.label(text="Use Properties panel (N) to define parms", icon='INFO')
312 row = layout.row(align=False)
313 row.operator("io_import.roomdata", text="Import", icon='COPYDOWN')
315 # -----------------------------------------------------
316 # Execute
317 # -----------------------------------------------------
318 def execute(self, context):
319 if bpy.context.mode == "OBJECT":
320 create_room(self, context)
321 return {'FINISHED'}
322 else:
323 self.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
324 return {'CANCELLED'}
327 # ------------------------------------------------------------------------------
328 # Create main object for the room. The other objects of room will be children of this.
329 # ------------------------------------------------------------------------------
330 # noinspection PyUnusedLocal
331 def create_room(self, context):
332 # deselect all objects
333 for o in bpy.data.objects:
334 o.select_set(False)
336 # we create main object and mesh for walls
337 roommesh = bpy.data.meshes.new("Room")
338 roomobject = bpy.data.objects.new("Room", roommesh)
339 roomobject.location = bpy.context.scene.cursor.location
340 bpy.context.collection.objects.link(roomobject)
341 roomobject.RoomGenerator.add()
342 roomobject.RoomGenerator[0].walls.add()
344 # we shape the walls and create other objects as children of 'RoomObject'.
345 shape_walls_and_create_children(roomobject, roommesh)
347 # we select, and activate, main object for the room.
348 bpy.context.view_layer.objects.active = roomobject
349 roomobject.select_set(True)
352 # -----------------------------------------------------
353 # Verify if solidify exist
354 # -----------------------------------------------------
355 def is_solidify(myobject):
356 flag = False
357 try:
358 if myobject.modifiers is None:
359 return False
361 for mod in myobject.modifiers:
362 if mod.type == 'SOLIDIFY':
363 flag = True
364 break
365 return flag
366 except AttributeError:
367 return False
370 # ------------------------------------------------------------------------------
371 # Update wall mesh and children objects (baseboard, floor and ceiling).
372 # ------------------------------------------------------------------------------
373 # noinspection PyUnusedLocal
374 def update_room(self, context):
375 # When we update, the active object is the main object of the room.
376 o = bpy.context.active_object
377 oldmesh = o.data
378 oldname = o.data.name
379 # Now we deselect that room object to not delete it.
380 o.select_set(False)
381 # and we create a new mesh for the walls:
382 tmp_mesh = bpy.data.meshes.new("temp")
383 # deselect all objects
384 for obj in bpy.data.objects:
385 obj.select_set(False)
386 # Remove children created by this addon:
387 for child in o.children:
388 # noinspection PyBroadException
389 try:
390 if child["archimesh.room_object"]:
391 # noinspection PyBroadException
392 try:
393 # remove child relationship
394 for grandchild in child.children:
395 grandchild.parent = None
396 # remove modifiers
397 for mod in child.modifiers:
398 bpy.ops.object.modifier_remove(mod)
399 except:
400 pass
401 # clear data
402 old = child.data
403 child.select_set(True)
404 bpy.ops.object.delete()
405 bpy.data.meshes.remove(old)
406 except:
407 pass
408 # Finally we create all that again (except main object),
409 shape_walls_and_create_children(o, tmp_mesh, True)
410 o.data = tmp_mesh
411 # Remove data (mesh of active object),
412 bpy.data.meshes.remove(oldmesh)
413 tmp_mesh.name = oldname
414 # and select, and activate, the main object of the room.
415 o.select_set(True)
416 bpy.context.view_layer.objects.active = o
419 # -----------------------------------------------------
420 # Move Solidify to Top
421 # -----------------------------------------------------
422 def movetotopsolidify(myobject):
423 mymod = None
424 try:
425 if myobject.modifiers is not None:
426 for mod in myobject.modifiers:
427 if mod.type == 'SOLIDIFY':
428 mymod = mod
430 if mymod is not None:
431 while myobject.modifiers[0] != mymod:
432 bpy.ops.object.modifier_move_up(modifier=mymod.name)
433 except AttributeError:
434 return
437 # ------------------------------------------------------------------------------
438 # Generate walls, baseboard, floor, ceiling and materials.
439 # For walls, it only shapes mesh and creates modifier solidify (the modifier, only the first time).
440 # And, for the others, it creates object and mesh.
441 # ------------------------------------------------------------------------------
442 def shape_walls_and_create_children(myroom, tmp_mesh, update=False):
443 rp = myroom.RoomGenerator[0] # "rp" means "room properties".
444 mybase = None
445 myfloor = None
446 myceiling = None
447 myshell = None
448 # Create the walls (only mesh, because the object is 'myRoom', created before).
449 create_walls(rp, tmp_mesh, get_blendunits(rp.room_height))
450 myroom.data = tmp_mesh
451 # Mark Seams
452 select_vertices(myroom, [0, 1])
453 mark_seam(myroom)
454 # Unwrap
455 unwrap_mesh(myroom)
457 remove_doubles(myroom)
458 set_normals(myroom, not rp.inverse) # inside/outside
460 if rp.wall_width > 0.0:
461 if update is False or is_solidify(myroom) is False:
462 set_modifier_solidify(myroom, get_blendunits(rp.wall_width))
463 else:
464 for mod in myroom.modifiers:
465 if mod.type == 'SOLIDIFY':
466 mod.thickness = rp.wall_width
467 # Move to Top SOLIDIFY
468 movetotopsolidify(myroom)
470 else: # clear not used SOLIDIFY
471 for mod in myroom.modifiers:
472 if mod.type == 'SOLIDIFY':
473 myroom.modifiers.remove(mod)
475 # Create baseboard
476 if rp.baseboard:
477 baseboardmesh = bpy.data.meshes.new("Baseboard")
478 mybase = bpy.data.objects.new("Baseboard", baseboardmesh)
479 mybase.location = (0, 0, 0)
480 bpy.context.collection.objects.link(mybase)
481 mybase.parent = myroom
482 mybase.select_set(True)
483 mybase["archimesh.room_object"] = True
484 mybase["archimesh.room_baseboard"] = True
486 create_walls(rp, baseboardmesh, get_blendunits(rp.base_height), True)
487 set_normals(mybase, rp.inverse) # inside/outside room
488 if rp.base_width:
489 set_modifier_solidify(mybase, get_blendunits(rp.base_width))
490 # Move to Top SOLIDIFY
491 movetotopsolidify(mybase)
492 # Mark Seams
493 select_vertices(mybase, [0, 1])
494 mark_seam(mybase)
495 # Unwrap
496 unwrap_mesh(mybase)
498 # Create floor
499 if rp.floor and rp.wall_num > 1:
500 myfloor = create_floor(rp, "Floor", myroom)
501 myfloor["archimesh.room_object"] = True
502 myfloor.parent = myroom
503 # Unwrap
504 unwrap_mesh(myfloor)
506 # Create ceiling
507 if rp.ceiling and rp.wall_num > 1:
508 myceiling = create_floor(rp, "Ceiling", myroom)
509 myceiling["archimesh.room_object"] = True
510 myceiling.parent = myroom
511 # Unwrap
512 unwrap_mesh(myceiling)
514 # Create Shell
516 if rp.shell:
517 myshell = add_shell(myroom, "Wall_cover", rp)
518 myshell["archimesh.room_object"] = True
519 myshell["archimesh.room_shell"] = True
520 parentobject(myroom, myshell)
521 myshell.rotation_euler = myroom.rotation_euler
522 if rp.wall_width > 0.0:
523 # Solidify (need for boolean)
524 set_modifier_solidify(myshell, 0.01)
525 # Move to Top SOLIDIFY
526 movetotopsolidify(mybase)
528 # Create materials
529 if rp.crt_mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
530 # Wall material (two faces)
531 mat = create_diffuse_material("Wall_material", False, 0.765, 0.650, 0.588, 0.8, 0.621, 0.570, 0.1, True)
532 set_material(myroom, mat)
534 # Baseboard material
535 if rp.baseboard and mybase is not None:
536 mat = create_diffuse_material("Baseboard_material", False, 0.8, 0.8, 0.8)
537 set_material(mybase, mat)
539 # Ceiling material
540 if rp.ceiling and myceiling is not None:
541 mat = create_diffuse_material("Ceiling_material", False, 0.95, 0.95, 0.95)
542 set_material(myceiling, mat)
544 # Floor material
545 if rp.floor and myfloor is not None:
546 mat = create_brick_material("Floor_material", False, 0.711, 0.668, 0.668, 0.8, 0.636, 0.315)
547 set_material(myfloor, mat)
549 # Shell material
550 if rp.shell and myshell is not None:
551 mat = create_diffuse_material("Wall_cover_material", False, 0.507, 0.309, 0.076, 0.507, 0.309, 0.076)
552 set_material(myshell, mat)
554 # deactivate others
555 for o in bpy.data.objects:
556 if o.select_get() is True and o.name != myroom.name:
557 o.select_set(False)
560 # ------------------------------------------------------------------------------
561 # Create walls or baseboard (indicated with baseboard parameter).
562 # Some custom values are passed using the rp ("room properties" group) parameter (rp.myvariable).
563 # ------------------------------------------------------------------------------
564 def create_walls(rp, mymesh, height, baseboard=False):
565 myvertex = [(0.0, 0.0, height), (0.0, 0.0, 0.0)]
566 myfaces = []
567 lastface = 0
568 lastx = lasty = 0
569 idf = 0
570 # Iterate the walls
571 for i in range(0, rp.wall_num):
572 if 0 == i:
573 prv = False
574 else:
575 prv = rp.walls[i - 1].a and not rp.walls[i - 1].curved
577 mydat = make_wall(prv, rp.walls[i], baseboard, lastface,
578 lastx, lasty, height, myvertex, myfaces)
579 lastx = mydat[0]
580 lasty = mydat[1]
581 lastface = mydat[2]
583 # --------------------------------------
584 # saves vertex data for opengl
585 # --------------------------------------
586 point_a = None
587 point_b = None
588 try:
589 for mf in myfaces[idf]:
590 if myvertex[mf][2] == 0:
591 if point_a is None:
592 point_a = myvertex[mf]
593 else:
594 point_b = myvertex[mf]
596 rp.walls[i].glpoint_a = point_a
597 rp.walls[i].glpoint_b = point_b
598 except IndexError:
599 pass
601 idf = len(myfaces)
603 # Close room
604 if rp.merge is True:
605 if baseboard is False:
606 if rp.walls[rp.wall_num - 1].a is not True:
607 myfaces.extend([(0, 1, lastface + 1, lastface)])
608 else:
609 if rp.walls[rp.wall_num - 1].curved is True:
610 myfaces.extend([(0, 1, lastface + 1, lastface)])
611 else:
612 myfaces.extend([(0, 1, lastface, lastface + 1)])
613 else:
614 myfaces.extend([(0, 1, lastface + 1, lastface)])
616 mymesh.from_pydata(myvertex, [], myfaces)
617 mymesh.update(calc_edges=True)
620 # ------------------------------------------------------------------------------
621 # Make a Wall
622 # prv: If previous wall has 'curved' activate.
623 # lastFace: Number of faces of all before walls.
624 # lastX: X position of the end of the last wall.
625 # lastY: Y position of the end of the last wall.
626 # height: Height of the last wall, without peak.
627 # ------------------------------------------------------------------------------
628 def make_wall(prv, wall, baseboard, lastface, lastx, lasty, height, myvertex, myfaces):
629 # size: Length of the wall.
630 # over: Height of the peak from "height".
631 # factor: Displacement of the peak (between -1 and 1; 0 is the middle of the wall).
632 advanced = wall.a
633 size = wall.w
634 over = wall.m
635 factor = wall.f
636 angle = wall.r
637 hide = wall.h
639 # if angle negative, calculate real
640 # use add because the angle is negative
641 if angle < 0:
642 angle += 360
643 # Verify Units
644 size = get_blendunits(size)
645 over = get_blendunits(over)
647 # Calculate size using angle
648 sizex = cos(radians(angle)) * size
649 sizey = sin(radians(angle)) * size
651 # Create faces
652 if advanced is False or baseboard is True:
653 # Cases of this first option: Baseboard or wall without peak and without curve.
654 if baseboard is True and advanced is True and wall.curved is True:
655 (myvertex, myfaces, sizex, sizey, lastface) = make_curved_wall(myvertex, myfaces, size, angle,
656 lastx, lasty, height, lastface,
657 wall.curve_factor, int(wall.curve_arc_deg),
658 int(wall.curve_arc_deg / wall.curve_steps),
659 hide, baseboard)
660 else:
661 myvertex.extend([(lastx + sizex, lasty + sizey, height),
662 (lastx + sizex, lasty + sizey, 0.0)])
663 if check_visibility(hide, baseboard):
664 if prv is False or baseboard is True:
665 # Previous no advance or advance with curve
666 myfaces.extend([(lastface, lastface + 2, lastface + 3, lastface + 1)])
667 else:
668 # Previous advance without curve
669 myfaces.extend([(lastface, lastface + 1, lastface + 2, lastface + 3)])
670 lastface += 2
671 else:
672 # Case of this second option: Wall with advanced features (orientation, visibility and peak or curve).
673 # Orientation and visibility options ('angle' and 'hide' variables) are only visible in panel
674 # with advanced features, but are taken in account in any case.
675 if wall.curved:
676 # Wall with curve and without peak.
677 (myvertex, myfaces, sizex, sizey, lastface) = make_curved_wall(myvertex, myfaces, size, angle,
678 lastx, lasty, height, lastface,
679 wall.curve_factor, int(wall.curve_arc_deg),
680 int(wall.curve_arc_deg / wall.curve_steps),
681 hide, baseboard)
682 else:
683 # Wall with peak and without curve.
684 mid = size / 2 + ((size / 2) * factor)
685 midx = cos(radians(angle)) * mid
686 midy = sin(radians(angle)) * mid
687 # first face
688 myvertex.extend([(lastx + midx, lasty + midy, height + over),
689 (lastx + midx, lasty + midy, 0.0)])
690 if check_visibility(hide, baseboard):
691 if fabs(factor) != 1:
692 if prv is False:
693 # Previous no advance or advance with curve
694 myfaces.extend([(lastface, lastface + 2, lastface + 3, lastface + 1)])
695 else:
696 # Previous advance without curve
697 myfaces.extend([(lastface, lastface + 1, lastface + 2, lastface + 3)])
698 # second face
699 myvertex.extend([(lastx + sizex, lasty + sizey, 0.0),
700 (lastx + sizex, lasty + sizey, height)])
701 if check_visibility(hide, baseboard):
702 if fabs(factor) != 1:
703 myfaces.extend([(lastface + 2, lastface + 5, lastface + 4, lastface + 3)])
704 else:
705 if prv is False:
706 myfaces.extend([(lastface, lastface + 5, lastface + 4, lastface + 1),
707 (lastface, lastface + 2, lastface + 5)])
708 else:
709 myfaces.extend([(lastface, lastface + 4, lastface + 5, lastface + 1),
710 (lastface + 1, lastface + 2, lastface + 5)])
712 lastface += 4
714 lastx += sizex
715 lasty += sizey
717 return lastx, lasty, lastface
720 # ------------------------------------------------------------------------------
721 # Verify visibility of walls
722 # ------------------------------------------------------------------------------
723 def check_visibility(h, base):
724 # Visible
725 if h == '0':
726 return True
727 # Wall
728 if h == '2':
729 if base is True:
730 return False
731 else:
732 return True
733 # Baseboard
734 if h == '1':
735 if base is True:
736 return True
737 else:
738 return False
739 # Hidden
740 if h == '3':
741 return False
744 # ------------------------------------------------------------------------------
745 # Create a curved wall.
746 # ------------------------------------------------------------------------------
747 def make_curved_wall(myvertex, myfaces, size, wall_angle, lastx, lasty, height,
748 lastface, curve_factor, arc_angle, step_angle, hide, baseboard):
749 curvex = None
750 curvey = None
751 # Calculate size using angle
752 sizex = cos(radians(wall_angle)) * size
753 sizey = sin(radians(wall_angle)) * size
755 for step in range(0, arc_angle + step_angle, step_angle):
756 curvex = sizex / 2 - cos(radians(step + wall_angle)) * size / 2
757 curvey = sizey / 2 - sin(radians(step + wall_angle)) * size / 2
758 curvey = curvey * curve_factor
759 myvertex.extend([(lastx + curvex, lasty + curvey, height),
760 (lastx + curvex, lasty + curvey, 0.0)])
761 if check_visibility(hide, baseboard):
762 myfaces.extend([(lastface, lastface + 2, lastface + 3, lastface + 1)])
763 lastface += 2
764 return myvertex, myfaces, curvex, curvey, lastface
767 # ------------------------------------------------------------------------------
768 # Create floor or ceiling (create object and mesh)
769 # Parameters:
770 # rm: "room properties" group
771 # typ: Name of new object and mesh ('Floor' or 'Ceiling')
772 # myRoom: Main object for the room
773 # ------------------------------------------------------------------------------
775 def create_floor(rp, typ, myroom):
776 bpy.context.view_layer.objects.active = myroom
778 myvertex = []
779 myfaces = []
780 verts = []
782 obverts = bpy.context.active_object.data.vertices
783 for vertex in obverts:
784 verts.append(tuple(vertex.co))
785 # Loop only selected
786 i = 0
787 for e in verts:
788 if typ == "Floor":
789 if e[2] == 0.0:
790 myvertex.extend([(e[0], e[1], e[2])])
791 i += 1
792 else: # ceiling
793 if round(e[2], 5) == round(get_blendunits(rp.room_height), 5):
794 myvertex.extend([(e[0], e[1], e[2])])
795 i += 1
797 # Create faces
798 fa = []
799 for f in range(0, i):
800 fa.extend([f])
802 myfaces.extend([fa])
804 mymesh = bpy.data.meshes.new(typ)
805 myobject = bpy.data.objects.new(typ, mymesh)
807 myobject.location = (0, 0, 0)
808 bpy.context.collection.objects.link(myobject)
810 mymesh.from_pydata(myvertex, [], myfaces)
811 mymesh.update(calc_edges=True)
813 return myobject
816 # ------------------------------------------------------------------
817 # Define property group class to create, or modify, room walls.
818 # ------------------------------------------------------------------
819 class WallProperties(PropertyGroup):
820 w: FloatProperty(
821 name='Length',
822 min=-150, max=150,
823 default=1, precision=3,
824 description='Length of the wall (negative to reverse direction)',
825 update=update_room,
828 a: BoolProperty(
829 name="Advanced",
830 description="Define advanced parameters of the wall",
831 default=False,
832 update=update_room,
835 curved: BoolProperty(
836 name="Curved",
837 description="Enable curved wall parameters",
838 default=False,
839 update=update_room,
841 curve_factor: FloatProperty(
842 name='Factor',
843 min=-5, max=5,
844 default=1, precision=1,
845 description='Curvature variation',
846 update=update_room,
848 curve_arc_deg: FloatProperty(
849 name='Degrees', min=1, max=359,
850 default=180, precision=1,
851 description='Degrees of the curve arc (must be >= steps)',
852 update=update_room,
854 curve_steps: IntProperty(
855 name='Steps',
856 min=2, max=50,
857 default=12,
858 description='Curve steps',
859 update=update_room,
862 m: FloatProperty(
863 name='Peak', min=0, max=50,
864 default=0, precision=3,
865 description='Middle height variation',
866 update=update_room,
868 f: FloatProperty(
869 name='Factor', min=-1, max=1,
870 default=0, precision=3,
871 description='Middle displacement',
872 update=update_room,
874 r: FloatProperty(
875 name='Angle',
876 min=-180, max=180,
877 default=0, precision=1,
878 description='Wall Angle (-180 to +180)',
879 update=update_room,
882 h: EnumProperty(
883 items=(
884 ('0', "Visible", ""),
885 ('1', "Baseboard", ""),
886 ('2', "Wall", ""),
887 ('3', "Hidden", ""),
889 name="",
890 description="Wall visibility",
891 update=update_room,
894 # opengl internal data
895 glpoint_a: FloatVectorProperty(
896 name="glpointa",
897 description="Hidden property for opengl",
898 default=(0, 0, 0),
900 glpoint_b: FloatVectorProperty(
901 name="glpointb",
902 description="Hidden property for opengl",
903 default=(0, 0, 0),
906 bpy.utils.register_class(WallProperties)
909 # ------------------------------------------------------------------
910 # Add a new room wall.
911 # First add a parameter group for that new wall, and then update the room.
912 # ------------------------------------------------------------------
913 def add_room_wall(self, context):
914 rp = context.object.RoomGenerator[0]
915 for cont in range(len(rp.walls) - 1, rp.wall_num):
916 rp.walls.add()
917 # by default, we alternate the direction of the walls.
918 if 1 == cont % 2:
919 rp.walls[cont].r = 90
920 update_room(self, context)
923 # ------------------------------------
924 # Get if some vertex is highest
925 # ------------------------------------
926 def get_hight(verts, faces_4, faces_3, face_index, face_num):
927 rtn = face_index
928 a = faces_4[face_num][0]
929 b = faces_4[face_num][1]
930 c = faces_4[face_num][2]
931 d = faces_4[face_num][3]
933 for face3 in faces_3:
934 for idx3 in face3:
935 if idx3 != face_index:
936 # check x and y position (must be equal)
937 if verts[idx3][0] == verts[face_index][0] and verts[idx3][1] == verts[face_index][1]:
938 # only if z is > that previous z
939 if verts[idx3][2] > verts[face_index][2]:
940 # checking if the original vertex is in the same face
941 # must have 2 vertices on the original face
942 t = 0
943 for e in face3:
944 if e == a or e == b or e == c or e == d:
945 t += 1
946 if t >= 2:
947 rtn = idx3
949 return rtn
952 # ------------------------------------
953 # Sort list of faces
954 # ------------------------------------
955 def sort_facelist(activefaces, activenormals):
956 totfaces = len(activefaces)
957 newlist = []
958 newnormal = []
959 # -----------------------
960 # Only one face
961 # -----------------------
962 if totfaces == 1:
963 newlist.append(activefaces[0])
964 newnormal.append(activenormals[0])
965 return newlist, newnormal
967 # -----------------------
968 # Look for first element
969 # -----------------------
970 idx = 0
971 for face in activefaces:
972 c = 0
973 for i in face:
974 if i == 0 or i == 1:
975 c += 1
977 if c >= 2 and face not in newlist:
978 newlist.append(face)
979 newnormal.append(activenormals[idx])
980 break
981 idx += 1
983 # -----------------------
984 # Look for second element
985 # -----------------------
986 idx = 0
987 for face in activefaces:
988 c = 0
989 for i in face:
990 if i == 2 or i == 3:
991 c += 1
992 if c >= 2 and face not in newlist:
993 newlist.append(face)
994 newnormal.append(activenormals[idx])
995 break
996 idx += 1
998 # -----------------------
999 # Add next faces
1000 # -----------------------
1001 for x in range(2, totfaces):
1002 idx = 0
1003 for face in activefaces:
1004 c = 0
1005 for i in face:
1006 if i == newlist[x - 1][0] or i == newlist[x - 1][1] or i == newlist[x - 1][2] or i == newlist[x - 1][3]:
1007 c += 1
1008 if c >= 2 and face not in newlist:
1009 newlist.append(face)
1010 newnormal.append(activenormals[idx])
1011 idx += 1
1013 return newlist, newnormal
1016 # ------------------------------------
1017 # Get points of the walls
1018 # selobject: room
1019 # ------------------------------------
1020 def get_wall_points(selobject):
1021 obverts = selobject.data.vertices
1022 obfaces = selobject.data.polygons
1024 verts = []
1025 faces_3 = []
1026 faces_4 = []
1027 normals = []
1028 activefaces = []
1029 activenormals = []
1031 # --------------------------
1032 # Recover all vertex
1033 # --------------------------
1034 for vertex in obverts:
1035 verts.append(list(vertex.co))
1037 # --------------------------
1038 # Recover 3 faces
1039 # --------------------------
1040 for face in obfaces:
1041 # get only 4 corners faces
1042 if len(list(face.vertices)) == 3:
1043 faces_3.append(list(face.vertices))
1044 # --------------------------
1045 # Recover 4 faces
1046 # --------------------------
1047 for face in obfaces:
1048 # get only 4 corners faces
1049 if len(list(face.vertices)) == 4:
1050 faces_4.append(list(face.vertices))
1051 normals.append(face.normal)
1052 # --------------------------
1053 # Replace highest
1054 # --------------------------
1055 idx = 0
1056 for face in faces_4:
1057 mylist = []
1058 for e in face: # e contains the number of vertex element
1059 if verts[e][2] == 0:
1060 mylist.append(e)
1061 # Only if Z > 0, recalculate
1062 if verts[e][2] != 0:
1063 mylist.append(get_hight(verts, faces_4, faces_3, e, idx))
1065 activefaces.append(mylist)
1066 activenormals.append(normals[idx])
1067 idx += 1
1069 # ------------------------
1070 # Sort faces
1071 # ------------------------
1072 newlist, newnormal = sort_facelist(activefaces, activenormals)
1074 return verts, newlist, newnormal
1077 # ------------------------------------
1078 # Create a shell of boards
1079 # selobject: room
1080 # objname: Name for new object
1081 # rp: room properties
1082 # ------------------------------------
1083 def add_shell(selobject, objname, rp):
1085 myvertex = []
1086 myfaces = []
1088 verts, activefaces, activenormals = get_wall_points(selobject)
1090 # --------------------------
1091 # Get line points
1092 # --------------------------
1093 i = 0
1094 idx = 0
1095 for face in activefaces:
1096 a1 = None
1097 b1 = None
1098 a2 = None
1099 b2 = None
1100 # Bottom
1101 for e in face:
1102 if verts[e][2] == 0:
1103 if a1 is None:
1104 a1 = e
1105 else:
1106 b1 = e
1107 # Top
1108 for e in face:
1109 if verts[e][2] != 0:
1110 if verts[a1][0] == verts[e][0] and verts[a1][1] == verts[e][1]:
1111 a2 = e
1112 else:
1113 b2 = e
1114 # Create the mesh
1115 mydata = create_cover_mesh(idx, verts, activefaces, activenormals, i, a1, a2, b1, b2,
1116 rp.merge, 0.005,
1117 rp.shell_height, rp.shell_thick, rp.shell_factor, rp.shell_bfactor)
1118 i = mydata[0]
1119 myvertex.extend(mydata[1])
1120 myfaces.extend(mydata[2])
1121 idx += 1
1122 # --------------------------
1123 # Create the mesh
1124 # --------------------------
1125 mesh = bpy.data.meshes.new(objname)
1126 myobject = bpy.data.objects.new(objname, mesh)
1128 myobject.location = selobject.location
1129 bpy.context.collection.objects.link(myobject)
1131 mesh.from_pydata(myvertex, [], myfaces)
1132 mesh.update(calc_edges=True)
1134 remove_doubles(myobject)
1135 set_normals(myobject)
1137 return myobject
1140 # ---------------------------------------------------------
1141 # Project point using face normals
1143 # m: Magnitud
1144 # pf: Comparison face +/-
1145 # ---------------------------------------------------------
1146 def project_point(idx, point, normals, m, pf):
1147 v1 = Vector(normals[idx])
1148 if idx + pf >= len(normals):
1149 vf = v1
1150 elif idx + pf < 0:
1151 vf = v1
1152 else:
1153 v2 = Vector(normals[idx + pf])
1154 if v1 != v2:
1155 vf = v1 + v2
1156 vf.normalize() # must be length equal to 1
1157 else:
1158 vf = v1
1160 n1 = (vf[0] * m, vf[1] * m, vf[2] * m)
1161 p1 = (point[0] + n1[0], point[1] + n1[1], point[2] + n1[2])
1162 return p1
1165 # ---------------------------------------------------------
1166 # Create wall cover mesh
1168 # Uses linear equation for cutting
1170 # Z = This value is the z axis value
1171 # so, we can replace t with ((Z-Z1) / (Z2-Z1))
1173 # X = X1 + ((X2 - X1) * t)
1175 # X = X1 + ((X2 - X1) * ((Z-Z1) / (Z2-Z1)))
1176 # Y = Y1 + ((Y2 - Y1) * ((Z-Z1) / (Z2-Z1)))
1178 # height refers to the height of the cover piece
1179 # width refers to the width of the cover piece
1180 # ---------------------------------------------------------
1183 def create_cover_mesh(idx, verts, activefaces, normals, i, a1, a2, b1, b2, merge, space=0.005,
1184 height=0.20, thickness=0.025, shell_factor=1, shell_bfactor=1):
1185 pvertex = []
1186 pfaces = []
1188 a1_x = verts[a1][0]
1189 a1_y = verts[a1][1]
1190 a1_z = verts[a1][2]
1192 a2_x = verts[a2][0]
1193 a2_y = verts[a2][1]
1194 a2_z = verts[a2][2]
1196 b1_x = verts[b1][0]
1197 b1_y = verts[b1][1]
1198 b1_z = verts[b1][2]
1200 b2_x = verts[b2][0]
1201 b2_y = verts[b2][1]
1202 b2_z = verts[b2][2]
1204 # Get highest
1205 if a2_z >= b2_z:
1206 top = a2_z
1207 limit = b2_z
1208 else:
1209 top = b2_z
1210 limit = a2_z
1212 # apply factor
1213 # get high point of walls
1214 maxh = 0
1215 for v in verts:
1216 if v[2] > maxh:
1217 maxh = v[2]
1218 maxh *= shell_factor
1219 minh = maxh * (1 - shell_bfactor)
1220 if minh < 0:
1221 minh = 0
1223 if shell_factor < 1:
1224 if top > maxh:
1225 top = maxh
1227 # --------------------------------------
1228 # Loop to generate each piece of cover
1229 # --------------------------------------
1230 zpos = minh # initial position
1231 f = 0
1232 f2 = 0
1233 # detect what face must use to compare
1234 face_num = len(activefaces) - 1
1235 if idx == 0 and merge is True:
1236 if is_in_nextface(idx + 1, activefaces, verts, a1_x, a1_y) is True:
1237 side_a = 1
1238 side_b = face_num
1239 else:
1240 side_a = face_num
1241 side_b = 1
1242 elif idx == face_num and merge is True:
1243 if is_in_nextface(face_num, activefaces, verts, a1_x, a1_y) is False:
1244 side_b = -face_num
1245 side_a = -1
1246 else:
1247 side_b = -1
1248 side_a = -face_num
1249 else:
1250 if is_in_nextface(idx + 1, activefaces, verts, a1_x, a1_y) is True:
1251 side_a = 1
1252 side_b = -1
1253 else:
1254 side_a = -1
1255 side_b = 1
1256 # Last wall
1257 if idx + 1 >= len(activefaces):
1258 if is_in_nextface(idx - 1, activefaces, verts, a1_x, a1_y) is True:
1259 side_a = -1
1260 side_b = 1
1261 else:
1262 side_a = 1
1263 side_b = -1
1265 na1_x = 0
1266 na1_y = 0
1267 na2_x = 0
1268 na2_y = 0
1270 nb1_x = 0
1271 nb1_y = 0
1272 nb2_x = 0
1273 nb2_y = 0
1275 nc1_x = 0
1276 nc1_y = 0
1277 nc2_x = 0
1278 nc2_y = 0
1280 nd1_x = 0
1281 nd1_y = 0
1282 nd2_x = 0
1283 nd2_y = 0
1285 while zpos <= top:
1286 # ----------------------
1287 # Full cover piece
1288 # ----------------------
1289 if zpos <= limit:
1290 # ----------------
1291 # Point A
1292 # ----------------
1293 mypoint = project_point(idx, (a1_x, a1_y, zpos), normals, space, side_a)
1295 pvertex.extend([mypoint])
1296 na1_x = mypoint[0]
1297 na1_y = mypoint[1]
1298 # external point
1299 mypoint = project_point(idx, (a1_x, a1_y, zpos), normals, space + thickness, side_a)
1300 pvertex.extend([mypoint])
1301 nc1_x = mypoint[0]
1302 nc1_y = mypoint[1]
1303 # get second point (vertical)
1304 mypoint = project_point(idx, (a2_x, a2_y, zpos), normals, space, side_a)
1305 na2_x = mypoint[0]
1306 na2_y = mypoint[1]
1307 mypoint = project_point(idx, (a2_x, a2_y, zpos), normals, space + thickness, side_a)
1308 nc2_x = mypoint[0]
1309 nc2_y = mypoint[1]
1311 # ----------------
1312 # Point B
1313 # ----------------
1314 mypoint = project_point(idx, (b1_x, b1_y, zpos), normals, space, side_b)
1315 pvertex.extend([mypoint])
1316 nb1_x = mypoint[0]
1317 nb1_y = mypoint[1]
1318 # external point
1319 mypoint = project_point(idx, (b1_x, b1_y, zpos), normals, space + thickness, side_b)
1320 pvertex.extend([mypoint])
1321 nd1_x = mypoint[0]
1322 nd1_y = mypoint[1]
1323 # get second point (vertical)
1324 mypoint = project_point(idx, (b2_x, b2_y, zpos), normals, space, side_b)
1325 nb2_x = mypoint[0]
1326 nb2_y = mypoint[1]
1327 mypoint = project_point(idx, (b2_x, b2_y, zpos), normals, space + thickness, side_b)
1328 nd2_x = mypoint[0]
1329 nd2_y = mypoint[1]
1331 # Faces
1332 if zpos != top:
1333 pfaces.extend([(i, i + 1, i + 3, i + 2)])
1335 if f >= 1:
1336 pfaces.extend([(i - 3, i, i + 2, i - 1)])
1338 i += 4
1339 f += 1
1340 # ----------------------
1341 # Cut pieces
1342 # ----------------------
1343 else:
1344 # -------------------------------
1345 # Internal Points
1346 # -------------------------------
1347 # Get highest
1348 if a2_z >= b2_z:
1349 ax1 = na1_x
1350 ay1 = na1_y
1351 az1 = a1_z
1352 ax2 = na2_x
1353 ay2 = na2_y
1354 az2 = a2_z
1356 bx1 = na2_x
1357 by1 = na2_y
1358 bz1 = a2_z
1359 bx2 = nb2_x
1360 by2 = nb2_y
1361 bz2 = b2_z
1362 else:
1363 ax1 = na2_x
1364 ay1 = na2_y
1365 az1 = a2_z
1366 ax2 = nb2_x
1367 ay2 = nb2_y
1368 az2 = b2_z
1370 bx1 = nb1_x
1371 by1 = nb1_y
1372 bz1 = b1_z
1373 bx2 = nb2_x
1374 by2 = nb2_y
1375 bz2 = b2_z
1377 # ----------------
1378 # Point A
1379 # ----------------
1380 x = ax1 + ((ax2 - ax1) * ((zpos - az1) / (az2 - az1)))
1381 y = ay1 + ((ay2 - ay1) * ((zpos - az1) / (az2 - az1)))
1382 pvertex.extend([(x, y, zpos)])
1383 # ----------------
1384 # Point B
1385 # ----------------
1386 x = bx1 + ((bx2 - bx1) * ((zpos - bz1) / (bz2 - bz1)))
1387 y = by1 + ((by2 - by1) * ((zpos - bz1) / (bz2 - bz1)))
1388 pvertex.extend([(x, y, zpos)])
1389 # -------------------------------
1390 # External Points
1391 # -------------------------------
1392 # Get highest
1393 if a2_z >= b2_z:
1394 ax1 = nc1_x
1395 ay1 = nc1_y
1396 az1 = a1_z
1397 ax2 = nc2_x
1398 ay2 = nc2_y
1399 az2 = a2_z
1401 bx1 = nc2_x
1402 by1 = nc2_y
1403 bz1 = a2_z
1404 bx2 = nd2_x
1405 by2 = nd2_y
1406 bz2 = b2_z
1407 else:
1408 ax1 = nc2_x
1409 ay1 = nc2_y
1410 az1 = a2_z
1411 ax2 = nd2_x
1412 ay2 = nd2_y
1413 az2 = b2_z
1415 bx1 = nd1_x
1416 by1 = nd1_y
1417 bz1 = b1_z
1418 bx2 = nd2_x
1419 by2 = nd2_y
1420 bz2 = b2_z
1422 # ----------------
1423 # Point A
1424 # ----------------
1425 x = ax1 + ((ax2 - ax1) * ((zpos - az1) / (az2 - az1)))
1426 y = ay1 + ((ay2 - ay1) * ((zpos - az1) / (az2 - az1)))
1427 pvertex.extend([(x, y, zpos)])
1428 # ----------------
1429 # Point B
1430 # ----------------
1431 x = bx1 + ((bx2 - bx1) * ((zpos - bz1) / (bz2 - bz1)))
1432 y = by1 + ((by2 - by1) * ((zpos - bz1) / (bz2 - bz1)))
1433 pvertex.extend([(x, y, zpos)])
1434 # Faces
1435 if zpos != top:
1436 pfaces.extend([(i, i + 1, i + 3, i + 2)])
1438 if f2 == 0:
1439 pfaces.extend([(i - 1, i - 3, i, i + 1)])
1440 else:
1441 pfaces.extend([(i - 1, i - 2, i, i + 1)])
1443 i += 4
1444 f2 += 1
1445 # avoid infinite loop
1446 if zpos == top:
1447 break
1449 # add new piece
1450 zpos += height
1451 # cut oversized
1452 if zpos > top:
1453 zpos = top
1455 return i, pvertex, pfaces
1458 # -------------------------------------------------------------
1459 # Detect if the vertex is face
1460 # -------------------------------------------------------------
1461 def is_in_nextface(idx, activefaces, verts, x, y):
1462 if idx >= len(activefaces):
1463 return False
1465 for f in activefaces[idx]:
1466 if verts[f][2] == 0: # only ground
1467 if verts[f][0] == x and verts[f][1] == y:
1468 return True
1470 return False
1473 # ------------------------------------------------------------------
1474 # Define property group class to create or modify a rooms.
1475 # ------------------------------------------------------------------
1476 class RoomProperties(PropertyGroup):
1477 room_height: FloatProperty(
1478 name='Height', min=0.001, max=50,
1479 default=2.4, precision=3,
1480 description='Room height', update=update_room,
1482 wall_width: FloatProperty(
1483 name='Thickness', min=0.000, max=10,
1484 default=0.0, precision=3,
1485 description='Thickness of the walls', update=update_room,
1487 inverse: BoolProperty(
1488 name="Inverse", description="Inverse normals to outside",
1489 default=False,
1490 update=update_room,
1492 crt_mat: BoolProperty(
1493 name="Create default Cycles materials",
1494 description="Create default materials for Cycles render",
1495 default=True,
1496 update=update_room,
1499 wall_num: IntProperty(
1500 name='Number of Walls', min=1, max=50,
1501 default=1,
1502 description='Number total of walls in the room', update=add_room_wall,
1505 baseboard: BoolProperty(
1506 name="Baseboard", description="Create a baseboard automatically",
1507 default=True,
1508 update=update_room,
1511 base_width: FloatProperty(
1512 name='Width', min=-10, max=10,
1513 default=0.015, precision=3,
1514 description='Baseboard width', update=update_room,
1516 base_height: FloatProperty(
1517 name='Height', min=0.05, max=20,
1518 default=0.12, precision=3,
1519 description='Baseboard height', update=update_room,
1522 ceiling: BoolProperty(
1523 name="Ceiling", description="Create a ceiling",
1524 default=False, update=update_room,
1526 floor: BoolProperty(
1527 name="Floor", description="Create a floor automatically",
1528 default=False,
1529 update=update_room,
1532 merge: BoolProperty(
1533 name="Close walls", description="Close walls to create a full closed room",
1534 default=False, update=update_room,
1537 walls: CollectionProperty(
1538 type=WallProperties,
1541 shell: BoolProperty(
1542 name="Wall cover", description="Create a cover of boards",
1543 default=False, update=update_room,
1545 shell_thick: FloatProperty(
1546 name='Thickness', min=0.001, max=1,
1547 default=0.025, precision=3,
1548 description='Cover board thickness', update=update_room,
1550 shell_height: FloatProperty(
1551 name='Height', min=0.05, max=1,
1552 default=0.20, precision=3,
1553 description='Cover board height', update=update_room,
1555 shell_factor: FloatProperty(
1556 name='Top', min=0.1, max=1,
1557 default=1, precision=1,
1558 description='Percentage for top covering (1 Full)', update=update_room,
1560 shell_bfactor: FloatProperty(
1561 name='Bottom', min=0.1, max=1,
1562 default=1, precision=1,
1563 description='Percentage for bottom covering (1 Full)', update=update_room,
1566 bpy.utils.register_class(RoomProperties)
1567 Object.RoomGenerator = CollectionProperty(type=RoomProperties)
1570 # -----------------------------------------------------
1571 # Add wall parameters to the panel.
1572 # -----------------------------------------------------
1573 def add_wall(idx, box, wall):
1574 box.label(text="Wall " + str(idx))
1575 row = box.row()
1576 row.prop(wall, 'w')
1577 row.prop(wall, 'a')
1578 # row.prop(wall, 'curved')
1579 if wall.a is True:
1580 srow = box.row()
1581 srow.prop(wall, 'r')
1582 srow.prop(wall, 'h')
1584 srow = box.row()
1585 srow.prop(wall, 'curved')
1587 if wall.curved is False:
1588 srow.prop(wall, 'm')
1589 srow.prop(wall, 'f')
1591 if wall.curved is True:
1592 srow.prop(wall, 'curve_factor')
1593 srow.prop(wall, 'curve_arc_deg')
1594 srow.prop(wall, 'curve_steps')
1597 # ------------------------------------------------------------------
1598 # Define panel class to modify rooms.
1599 # ------------------------------------------------------------------
1600 class ARCHIMESH_PT_RoomGenerator(Panel):
1601 bl_idname = "OBJECT_PT_room_generator"
1602 bl_label = "Room"
1603 bl_space_type = 'VIEW_3D'
1604 bl_region_type = 'UI'
1605 bl_category = 'Create'
1607 # -----------------------------------------------------
1608 # Verify if visible
1609 # -----------------------------------------------------
1610 @classmethod
1611 def poll(cls, context):
1612 o = context.object
1613 if o is None:
1614 return False
1615 if 'RoomGenerator' not in o:
1616 return False
1617 else:
1618 return True
1620 # -----------------------------------------------------
1621 # Draw (create UI interface)
1622 # -----------------------------------------------------
1623 def draw(self, context):
1624 o = context.object
1625 # If the selected object didn't be created with the group 'RoomGenerator', this panel is not created.
1626 # noinspection PyBroadException
1627 try:
1628 if 'RoomGenerator' not in o:
1629 return
1630 except:
1631 return
1633 layout = self.layout
1634 if bpy.context.mode == 'EDIT_MESH':
1635 layout.label(text='Warning: Operator does not work in edit mode.', icon='ERROR')
1636 else:
1637 room = o.RoomGenerator[0]
1638 row = layout.row()
1639 row.prop(room, 'room_height')
1640 row.prop(room, 'wall_width')
1641 row.prop(room, 'inverse')
1643 row = layout.row()
1644 if room.wall_num > 1:
1645 row.prop(room, 'ceiling')
1646 row.prop(room, 'floor')
1647 row.prop(room, 'merge')
1649 # Wall number
1650 row = layout.row()
1651 row.prop(room, 'wall_num')
1653 # Add menu for walls
1654 if room.wall_num > 0:
1655 for wall_index in range(0, room.wall_num):
1656 box = layout.box()
1657 add_wall(wall_index + 1, box, room.walls[wall_index])
1659 box = layout.box()
1660 box.prop(room, 'baseboard')
1661 if room.baseboard is True:
1662 row = box.row()
1663 row.prop(room, 'base_width')
1664 row.prop(room, 'base_height')
1666 box = layout.box()
1667 box.prop(room, 'shell')
1668 if room.shell is True:
1669 row = box.row()
1670 row.prop(room, 'shell_height')
1671 row.prop(room, 'shell_thick')
1672 row = box.row()
1673 row.prop(room, 'shell_factor', slider=True)
1674 row.prop(room, 'shell_bfactor', slider=True)
1676 box = layout.box()
1677 if not context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
1678 box.enabled = False
1679 box.prop(room, 'crt_mat')