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