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