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# Main panel for different Archimesh general actions 23# Author: Antonio Vazquez (antonioya) 24# 25# ---------------------------------------------------------- 26# noinspection PyUnresolvedReferences 27import bpy 28# noinspection PyUnresolvedReferences 29import bgl 30from bpy.types import Operator, Panel, SpaceView3D 31from math import sqrt, fabs, pi, asin 32from .achm_tools import * 33from .achm_gltools import * 34 35 36# ----------------------------------------------------- 37# Verify if boolean already exist 38# ----------------------------------------------------- 39def isboolean(myobject, childobject): 40 flag = False 41 for mod in myobject.modifiers: 42 if mod.type == 'BOOLEAN': 43 if mod.object == childobject: 44 flag = True 45 break 46 return flag 47 48 49# ------------------------------------------------------ 50# Button: Action to link windows and doors 51# ------------------------------------------------------ 52class ARCHIMESH_OT_Hole(Operator): 53 bl_idname = "object.archimesh_cut_holes" 54 bl_label = "Auto Holes" 55 bl_description = "Enable windows and doors holes for any selected object (needs wall thickness)" 56 bl_category = 'View' 57 58 # ------------------------------ 59 # Execute 60 # ------------------------------ 61 # noinspection PyMethodMayBeStatic 62 def execute(self, context): 63 scene = context.scene 64 listobj = [] 65 # --------------------------------------------------------------------- 66 # Save the list of selected objects because the select flag is missed 67 # only can be windows or doors 68 # --------------------------------------------------------------------- 69 for obj in bpy.context.scene.objects: 70 # noinspection PyBroadException 71 try: 72 if obj["archimesh.hole_enable"]: 73 if obj.select_get() is True or scene.archimesh_select_only is False: 74 listobj.extend([obj]) 75 except: 76 continue 77 # --------------------------- 78 # Get the baseboard object 79 # --------------------------- 80 mybaseboard = None 81 for child in context.object.children: 82 # noinspection PyBroadException 83 try: 84 if child["archimesh.room_baseboard"]: 85 mybaseboard = child 86 except: 87 continue 88 # --------------------------- 89 # Get the shell object 90 # --------------------------- 91 myshell = None 92 for child in context.object.children: 93 # noinspection PyBroadException 94 try: 95 if child["archimesh.room_shell"]: 96 myshell = child 97 except: 98 continue 99 100 # ----------------------------- 101 # Remove all empty Boolean modifiers 102 # ----------------------------- 103 for mod in context.object.modifiers: 104 if mod.type == 'BOOLEAN': 105 if mod.object is None: 106 bpy.ops.object.modifier_remove(modifier=mod.name) 107 108 # if thickness is 0, must be > 0 109 myroom = context.object 110 if myroom.RoomGenerator[0].wall_width == 0: 111 self.report({'WARNING'}, "Walls must have thickness for using autohole function. Change it and run again") 112 # ----------------------------- 113 # Now apply Wall holes 114 # ----------------------------- 115 for obj in listobj: 116 parentobj = context.object 117 # Parent the empty to the room (the parent of frame) 118 if obj.parent is not None: 119 bpy.ops.object.select_all(action='DESELECT') 120 parentobj.select_set(True) 121 obj.parent.select_set(True) # parent of object 122 bpy.ops.object.parent_set(type='OBJECT', keep_transform=False) 123 # --------------------------------------- 124 # Add the modifier to controller 125 # and the scale to use the same thickness 126 # --------------------------------------- 127 for child in obj.parent.children: 128 # noinspection PyBroadException 129 try: 130 if child["archimesh.ctrl_hole"]: 131 # apply scale 132 t = parentobj.RoomGenerator[0].wall_width 133 if t > 0: 134 child.scale.y = (t + 0.45) / (child.dimensions.y / child.scale.y) # Add some gap 135 else: 136 child.scale.y = 1 137 # add boolean modifier 138 if isboolean(myroom, child) is False: 139 set_modifier_boolean(myroom, child) 140 except: 141 # print("Unexpected error:" + str(sys.exc_info())) 142 pass 143 144 # --------------------------------------- 145 # Now add the modifiers to baseboard 146 # --------------------------------------- 147 if mybaseboard is not None: 148 for obj in bpy.context.scene.objects: 149 # noinspection PyBroadException 150 try: 151 if obj["archimesh.ctrl_base"]: 152 if obj.select_get() is True or scene.archimesh_select_only is False: 153 # add boolean modifier 154 if isboolean(mybaseboard, obj) is False: 155 set_modifier_boolean(mybaseboard, obj) 156 except: 157 pass 158 159 # --------------------------------------- 160 # Now add the modifiers to shell 161 # --------------------------------------- 162 if myshell is not None: 163 # Remove all empty Boolean modifiers 164 for mod in myshell.modifiers: 165 if mod.type == 'BOOLEAN': 166 if mod.object is None: 167 bpy.ops.object.modifier_remove(modifier=mod.name) 168 169 for obj in bpy.context.scene.objects: 170 # noinspection PyBroadException 171 try: 172 if obj["archimesh.ctrl_hole"]: 173 if obj.select_get() is True or scene.archimesh_select_only is False: 174 # add boolean modifier 175 if isboolean(myshell, obj) is False: 176 set_modifier_boolean(myshell, obj) 177 except: 178 pass 179 180 return {'FINISHED'} 181 182 183# ------------------------------------------------------ 184# Button: Action to create room from grease pencil 185# ------------------------------------------------------ 186class ARCHIMESH_OT_Pencil(Operator): 187 bl_idname = "object.archimesh_pencil_room" 188 bl_label = "Room from Draw" 189 bl_description = "Create a room base on grease pencil strokes (draw from top view (7 key))" 190 bl_category = 'View' 191 192 # ------------------------------ 193 # Execute 194 # ------------------------------ 195 def execute(self, context): 196 # Enable for debugging code 197 debugmode = False 198 199 scene = context.scene 200 mypoints = None 201 clearangles = None 202 203 if debugmode is True: 204 print("======================================================================") 205 print("== ==") 206 print("== Grease pencil strokes analysis ==") 207 print("== ==") 208 print("======================================================================") 209 210 # ----------------------------------- 211 # Get grease pencil points 212 # ----------------------------------- 213 # noinspection PyBroadException 214 try: 215 216 # noinspection PyBroadException 217 try: 218 pencil = bpy.context.object.grease_pencil.layers.active 219 except: 220 pencil = bpy.context.scene.grease_pencil.layers.active 221 222 if pencil.active_frame is not None: 223 for i, stroke in enumerate(pencil.active_frame.strokes): 224 stroke_points = pencil.active_frame.strokes[i].points 225 allpoints = [(point.co.x, point.co.y) 226 for point in stroke_points] 227 228 mypoints = [] 229 idx = 0 230 x = 0 231 y = 0 232 orientation = None 233 old_orientation = None 234 235 for point in allpoints: 236 if idx == 0: 237 x = point[0] 238 y = point[1] 239 else: 240 abs_x = abs(point[0] - x) 241 abs_y = abs(point[1] - y) 242 243 if abs_y > abs_x: 244 orientation = "V" 245 else: 246 orientation = "H" 247 248 if old_orientation == orientation: 249 x = point[0] 250 y = point[1] 251 else: 252 mypoints.extend([(x, y)]) 253 x = point[0] 254 y = point[1] 255 old_orientation = orientation 256 257 idx += 1 258 # Last point 259 mypoints.extend([(x, y)]) 260 261 if debugmode is True: 262 print("\nPoints\n====================") 263 i = 0 264 for p in mypoints: 265 print(str(i) + ":" + str(p)) 266 i += 1 267 # ----------------------------------- 268 # Calculate distance between points 269 # ----------------------------------- 270 if debugmode is True: 271 print("\nDistance\n====================") 272 i = len(mypoints) 273 distlist = [] 274 for e in range(1, i): 275 d = sqrt( 276 ((mypoints[e][0] - mypoints[e - 1][0]) ** 2) + ((mypoints[e][1] - mypoints[e - 1][1]) ** 2)) 277 # Imperial units if needed 278 if bpy.context.scene.unit_settings.system == "IMPERIAL": 279 d *= 3.2808399 280 281 distlist.extend([d]) 282 283 if debugmode is True: 284 print(str(e - 1) + ":" + str(d)) 285 # ----------------------------------- 286 # Calculate angle of walls 287 # clamped to right angles 288 # ----------------------------------- 289 if debugmode is True: 290 print("\nAngle\n====================") 291 292 i = len(mypoints) 293 anglelist = [] 294 for e in range(1, i): 295 sinv = (mypoints[e][1] - mypoints[e - 1][1]) / sqrt( 296 ((mypoints[e][0] - mypoints[e - 1][0]) ** 2) + ((mypoints[e][1] - mypoints[e - 1][1]) ** 2)) 297 a = asin(sinv) 298 # Clamp to 90 or 0 degrees 299 if fabs(a) > pi / 4: 300 b = pi / 2 301 else: 302 b = 0 303 304 anglelist.extend([b]) 305 # Reverse de distance using angles (inverse angle to axis) for Vertical lines 306 if a < 0.0 and b != 0: 307 distlist[e - 1] *= -1 # reverse distance 308 309 # Reverse de distance for horizontal lines 310 if b == 0: 311 if mypoints[e - 1][0] > mypoints[e][0]: 312 distlist[e - 1] *= -1 # reverse distance 313 314 if debugmode is True: 315 print(str(e - 1) + ":" + str((a * 180) / pi) + "...:" + str( 316 (b * 180) / pi) + "--->" + str(distlist[e - 1])) 317 318 # --------------------------------------- 319 # Verify duplications and reduce noise 320 # --------------------------------------- 321 if len(anglelist) >= 1: 322 clearangles = [] 323 cleardistan = [] 324 i = len(anglelist) 325 oldangle = anglelist[0] 326 olddist = 0 327 for e in range(0, i): 328 if oldangle != anglelist[e]: 329 clearangles.extend([oldangle]) 330 cleardistan.extend([olddist]) 331 oldangle = anglelist[e] 332 olddist = distlist[e] 333 else: 334 olddist += distlist[e] 335 # last 336 clearangles.extend([oldangle]) 337 cleardistan.extend([olddist]) 338 339 # ---------------------------- 340 # Create the room 341 # ---------------------------- 342 if len(mypoints) > 1 and len(clearangles) > 0: 343 # Move cursor 344 bpy.context.scene.cursor.location.x = mypoints[0][0] 345 bpy.context.scene.cursor.location.y = mypoints[0][1] 346 bpy.context.scene.cursor.location.z = 0 # always on grid floor 347 348 # Add room mesh 349 bpy.ops.mesh.archimesh_room() 350 myroom = context.object 351 mydata = myroom.RoomGenerator[0] 352 # Number of walls 353 mydata.wall_num = len(mypoints) - 1 354 mydata.ceiling = scene.archimesh_ceiling 355 mydata.floor = scene.archimesh_floor 356 mydata.merge = scene.archimesh_merge 357 358 i = len(mypoints) 359 for e in range(0, i - 1): 360 if clearangles[e] == pi / 2: 361 if cleardistan[e] > 0: 362 mydata.walls[e].w = round(fabs(cleardistan[e]), 2) 363 mydata.walls[e].r = (fabs(clearangles[e]) * 180) / pi # from radians 364 else: 365 mydata.walls[e].w = round(fabs(cleardistan[e]), 2) 366 mydata.walls[e].r = (fabs(clearangles[e]) * 180 * -1) / pi # from radians 367 368 else: 369 mydata.walls[e].w = round(cleardistan[e], 2) 370 mydata.walls[e].r = (fabs(clearangles[e]) * 180) / pi # from radians 371 372 # Remove Grease pencil 373 if pencil is not None: 374 for frame in pencil.frames: 375 pencil.frames.remove(frame) 376 377 self.report({'INFO'}, "Archimesh: Room created from grease pencil strokes") 378 else: 379 self.report({'WARNING'}, "Archimesh: Not enough grease pencil strokes for creating room.") 380 381 return {'FINISHED'} 382 except: 383 self.report({'WARNING'}, "Archimesh: No grease pencil strokes. Do strokes in top view before creating room") 384 return {'CANCELLED'} 385 386 387# ------------------------------------------------------------------ 388# Define panel class for main functions. 389# ------------------------------------------------------------------ 390class ARCHIMESH_PT_Main(Panel): 391 bl_idname = "ARCHIMESH_PT_main" 392 bl_label = "Archimesh" 393 bl_space_type = "VIEW_3D" 394 bl_region_type = "UI" 395 bl_category = "Create" 396 bl_context = "objectmode" 397 bl_options = {'DEFAULT_CLOSED'} 398 399 # ------------------------------ 400 # Draw UI 401 # ------------------------------ 402 def draw(self, context): 403 layout = self.layout 404 scene = context.scene 405 406 myobj = context.object 407 # ------------------------------------------------------------------------- 408 # If the selected object didn't be created with the group 'RoomGenerator', 409 # this button is not created. 410 # ------------------------------------------------------------------------- 411 # noinspection PyBroadException 412 try: 413 if 'RoomGenerator' in myobj: 414 box = layout.box() 415 box.label(text="Room Tools", icon='MODIFIER') 416 row = box.row(align=False) 417 row.operator("object.archimesh_cut_holes", icon='GRID') 418 row.prop(scene, "archimesh_select_only") 419 420 # Export/Import 421 row = box.row(align=False) 422 row.operator("io_import.roomdata", text="Import", icon='COPYDOWN') 423 row.operator("io_export.roomdata", text="Export", icon='PASTEDOWN') 424 except: 425 pass 426 427 # ------------------------------------------------------------------------- 428 # If the selected object isn't a kitchen 429 # this button is not created. 430 # ------------------------------------------------------------------------- 431 # noinspection PyBroadException 432 try: 433 if myobj["archimesh.sku"] is not None: 434 box = layout.box() 435 box.label(text="Kitchen Tools", icon='MODIFIER') 436 # Export 437 row = box.row(align=False) 438 row.operator("io_export.kitchen_inventory", text="Export inventory", icon='PASTEDOWN') 439 except: 440 pass 441 442 # ------------------------------ 443 # Elements Buttons 444 # ------------------------------ 445 box = layout.box() 446 box.label(text="Elements", icon='GROUP') 447 row = box.row() 448 row.operator("mesh.archimesh_room") 449 row.operator("mesh.archimesh_column") 450 row = box.row() 451 row.operator("mesh.archimesh_door") 452 row = box.row() 453 row.operator("mesh.archimesh_window") 454 row.operator("mesh.archimesh_winpanel") 455 row = box.row() 456 row.operator("mesh.archimesh_kitchen") 457 row.operator("mesh.archimesh_shelves") 458 row = box.row() 459 row.operator("mesh.archimesh_stairs") 460 row.operator("mesh.archimesh_roof") 461 462 # ------------------------------ 463 # Prop Buttons 464 # ------------------------------ 465 box = layout.box() 466 box.label(text="Props", icon='LIGHT_DATA') 467 row = box.row() 468 row.operator("mesh.archimesh_books") 469 row.operator("mesh.archimesh_light") 470 row = box.row() 471 row.operator("mesh.archimesh_venetian") 472 row.operator("mesh.archimesh_roller") 473 row = box.row() 474 row.operator("mesh.archimesh_japan") 475 476 # ------------------------------ 477 # OpenGL Buttons 478 # ------------------------------ 479 box = layout.box() 480 box.label(text="Display hints", icon='QUESTION') 481 row = box.row() 482 if context.window_manager.archimesh_run_opengl is False: 483 icon = 'PLAY' 484 txt = 'Show' 485 else: 486 icon = "PAUSE" 487 txt = 'Hide' 488 row.operator("archimesh.runopenglbutton", text=txt, icon=icon) 489 row = box.row() 490 row.prop(scene, "archimesh_gl_measure", toggle=True, icon="ALIGN_CENTER") 491 row.prop(scene, "archimesh_gl_name", toggle=True, icon="OUTLINER_OB_FONT") 492 row.prop(scene, "archimesh_gl_ghost", icon='GHOST_ENABLED') 493 row = box.row() 494 row.prop(scene, "archimesh_text_color", text="") 495 row.prop(scene, "archimesh_walltext_color", text="") 496 row = box.row() 497 row.prop(scene, "archimesh_font_size") 498 row.prop(scene, "archimesh_wfont_size") 499 row = box.row() 500 row.prop(scene, "archimesh_hint_space") 501 # ------------------------------ 502 # Grease pencil tools 503 # ------------------------------ 504 box = layout.box() 505 box.label(text="Pencil Tools", icon='MODIFIER') 506 row = box.row(align=False) 507 row.operator("object.archimesh_pencil_room", icon='GREASEPENCIL') 508 row = box.row(align=False) 509 row.prop(scene, "archimesh_ceiling") 510 row.prop(scene, "archimesh_floor") 511 row.prop(scene, "archimesh_merge") 512 513 514# ------------------------------------------------------------- 515# Defines button for enable/disable the tip display 516# 517# ------------------------------------------------------------- 518class ARCHIMESH_OT_HintDisplay(Operator): 519 bl_idname = "archimesh.runopenglbutton" 520 bl_label = "Display hint data manager" 521 bl_description = "Display additional information in the viewport" 522 bl_category = 'View' 523 524 _handle = None # keep function handler 525 526 # ---------------------------------- 527 # Enable gl drawing adding handler 528 # ---------------------------------- 529 @staticmethod 530 def handle_add(self, context): 531 if ARCHIMESH_OT_HintDisplay._handle is None: 532 ARCHIMESH_OT_HintDisplay._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 533 'WINDOW', 534 'POST_PIXEL') 535 context.window_manager.archimesh_run_opengl = True 536 537 # ------------------------------------ 538 # Disable gl drawing removing handler 539 # ------------------------------------ 540 # noinspection PyUnusedLocal 541 @staticmethod 542 def handle_remove(self, context): 543 if ARCHIMESH_OT_HintDisplay._handle is not None: 544 SpaceView3D.draw_handler_remove(ARCHIMESH_OT_HintDisplay._handle, 'WINDOW') 545 ARCHIMESH_OT_HintDisplay._handle = None 546 context.window_manager.archimesh_run_opengl = False 547 548 # ------------------------------ 549 # Execute button action 550 # ------------------------------ 551 def execute(self, context): 552 if context.area.type == 'VIEW_3D': 553 if context.window_manager.archimesh_run_opengl is False: 554 self.handle_add(self, context) 555 context.area.tag_redraw() 556 else: 557 self.handle_remove(self, context) 558 context.area.tag_redraw() 559 560 return {'FINISHED'} 561 else: 562 self.report({'WARNING'}, 563 "View3D not found, cannot run operator") 564 565 return {'CANCELLED'} 566 567 568# ------------------------------------------------------------- 569# Handler for drawing OpenGl 570# ------------------------------------------------------------- 571# noinspection PyUnusedLocal 572def draw_callback_px(self, context): 573 draw_main(context) 574