# SPDX-License-Identifier: GPL-2.0-or-later # ---------------------------------------------------------- # Main panel for different Archimesh general actions # Author: Antonio Vazquez (antonioya) # # ---------------------------------------------------------- # noinspection PyUnresolvedReferences import bpy # noinspection PyUnresolvedReferences from bpy.types import Operator, Panel, SpaceView3D from math import sqrt, fabs, pi, asin from .achm_tools import * from .achm_gltools import * # ----------------------------------------------------- # Verify if boolean already exist # ----------------------------------------------------- def isboolean(myobject, childobject): flag = False for mod in myobject.modifiers: if mod.type == 'BOOLEAN': if mod.object == childobject: flag = True break return flag # ------------------------------------------------------ # Button: Action to link windows and doors # ------------------------------------------------------ class ARCHIMESH_OT_Hole(Operator): bl_idname = "object.archimesh_cut_holes" bl_label = "Auto Holes" bl_description = "Enable windows and doors holes for any selected object (needs wall thickness)" bl_category = 'View' bl_options = {'UNDO', 'REGISTER'} # ------------------------------ # Execute # ------------------------------ # noinspection PyMethodMayBeStatic def execute(self, context): scene = context.scene listobj = [] # --------------------------------------------------------------------- # Save the list of selected objects because the select flag is missed # only can be windows or doors # --------------------------------------------------------------------- for obj in bpy.context.scene.objects: # noinspection PyBroadException try: if obj["archimesh.hole_enable"]: if obj.select_get() is True or scene.archimesh_select_only is False: listobj.extend([obj]) except: continue # --------------------------- # Get the baseboard object # --------------------------- mybaseboard = None for child in context.object.children: # noinspection PyBroadException try: if child["archimesh.room_baseboard"]: mybaseboard = child except: continue # --------------------------- # Get the shell object # --------------------------- myshell = None for child in context.object.children: # noinspection PyBroadException try: if child["archimesh.room_shell"]: myshell = child except: continue # ----------------------------- # Remove all empty Boolean modifiers # ----------------------------- for mod in context.object.modifiers: if mod.type == 'BOOLEAN': if mod.object is None: bpy.ops.object.modifier_remove(modifier=mod.name) # if thickness is 0, must be > 0 myroom = context.object if myroom.RoomGenerator[0].wall_width == 0: self.report({'WARNING'}, "Walls must have thickness for using autohole function. Change it and run again") # ----------------------------- # Now apply Wall holes # ----------------------------- for obj in listobj: parentobj = context.object # Parent the empty to the room (the parent of frame) if obj.parent is not None: bpy.ops.object.select_all(action='DESELECT') parentobj.select_set(True) obj.parent.select_set(True) # parent of object bpy.ops.object.parent_set(type='OBJECT', keep_transform=False) # --------------------------------------- # Add the modifier to controller # and the scale to use the same thickness # --------------------------------------- for child in obj.parent.children: # noinspection PyBroadException try: if child["archimesh.ctrl_hole"]: # apply scale t = parentobj.RoomGenerator[0].wall_width if t > 0: child.scale.y = (t + 0.45) / (child.dimensions.y / child.scale.y) # Add some gap else: child.scale.y = 1 # add boolean modifier if isboolean(myroom, child) is False: set_modifier_boolean(myroom, child) except: # print("Unexpected error:" + str(sys.exc_info())) pass # --------------------------------------- # Now add the modifiers to baseboard # --------------------------------------- if mybaseboard is not None: for obj in bpy.context.scene.objects: # noinspection PyBroadException try: if obj["archimesh.ctrl_base"]: if obj.select_get() is True or scene.archimesh_select_only is False: # add boolean modifier if isboolean(mybaseboard, obj) is False: set_modifier_boolean(mybaseboard, obj) except: pass # --------------------------------------- # Now add the modifiers to shell # --------------------------------------- if myshell is not None: # Remove all empty Boolean modifiers for mod in myshell.modifiers: if mod.type == 'BOOLEAN': if mod.object is None: bpy.ops.object.modifier_remove(modifier=mod.name) for obj in bpy.context.scene.objects: # noinspection PyBroadException try: if obj["archimesh.ctrl_hole"]: if obj.select_get() is True or scene.archimesh_select_only is False: # add boolean modifier if isboolean(myshell, obj) is False: set_modifier_boolean(myshell, obj) except: pass return {'FINISHED'} # ------------------------------------------------------ # Button: Action to create room from grease pencil # ------------------------------------------------------ class ARCHIMESH_OT_Pencil(Operator): bl_idname = "object.archimesh_pencil_room" bl_label = "Room from Draw" bl_description = "Create a room base on grease pencil strokes (draw from top view (7 key))" bl_category = 'View' bl_options = {'UNDO', 'REGISTER'} # ------------------------------ # Execute # ------------------------------ def execute(self, context): # Enable for debugging code debugmode = False scene = context.scene mypoints = None clearangles = None if debugmode is True: print("======================================================================") print("== ==") print("== Grease pencil strokes analysis ==") print("== ==") print("======================================================================") # ----------------------------------- # Get grease pencil points # ----------------------------------- # noinspection PyBroadException try: # noinspection PyBroadException try: pencil = bpy.context.object.grease_pencil.layers.active except: pencil = bpy.context.scene.grease_pencil.layers.active if pencil.active_frame is not None: for i, stroke in enumerate(pencil.active_frame.strokes): stroke_points = pencil.active_frame.strokes[i].points allpoints = [(point.co.x, point.co.y) for point in stroke_points] mypoints = [] idx = 0 x = 0 y = 0 orientation = None old_orientation = None for point in allpoints: if idx == 0: x = point[0] y = point[1] else: abs_x = abs(point[0] - x) abs_y = abs(point[1] - y) if abs_y > abs_x: orientation = "V" else: orientation = "H" if old_orientation == orientation: x = point[0] y = point[1] else: mypoints.extend([(x, y)]) x = point[0] y = point[1] old_orientation = orientation idx += 1 # Last point mypoints.extend([(x, y)]) if debugmode is True: print("\nPoints\n====================") i = 0 for p in mypoints: print(str(i) + ":" + str(p)) i += 1 # ----------------------------------- # Calculate distance between points # ----------------------------------- if debugmode is True: print("\nDistance\n====================") i = len(mypoints) distlist = [] for e in range(1, i): d = sqrt( ((mypoints[e][0] - mypoints[e - 1][0]) ** 2) + ((mypoints[e][1] - mypoints[e - 1][1]) ** 2)) # Imperial units if needed if bpy.context.scene.unit_settings.system == "IMPERIAL": d *= 3.2808399 distlist.extend([d]) if debugmode is True: print(str(e - 1) + ":" + str(d)) # ----------------------------------- # Calculate angle of walls # clamped to right angles # ----------------------------------- if debugmode is True: print("\nAngle\n====================") i = len(mypoints) anglelist = [] for e in range(1, i): sinv = (mypoints[e][1] - mypoints[e - 1][1]) / sqrt( ((mypoints[e][0] - mypoints[e - 1][0]) ** 2) + ((mypoints[e][1] - mypoints[e - 1][1]) ** 2)) a = asin(sinv) # Clamp to 90 or 0 degrees if fabs(a) > pi / 4: b = pi / 2 else: b = 0 anglelist.extend([b]) # Reverse de distance using angles (inverse angle to axis) for Vertical lines if a < 0.0 and b != 0: distlist[e - 1] *= -1 # reverse distance # Reverse de distance for horizontal lines if b == 0: if mypoints[e - 1][0] > mypoints[e][0]: distlist[e - 1] *= -1 # reverse distance if debugmode is True: print(str(e - 1) + ":" + str((a * 180) / pi) + "...:" + str( (b * 180) / pi) + "--->" + str(distlist[e - 1])) # --------------------------------------- # Verify duplications and reduce noise # --------------------------------------- if len(anglelist) >= 1: clearangles = [] cleardistan = [] i = len(anglelist) oldangle = anglelist[0] olddist = 0 for e in range(0, i): if oldangle != anglelist[e]: clearangles.extend([oldangle]) cleardistan.extend([olddist]) oldangle = anglelist[e] olddist = distlist[e] else: olddist += distlist[e] # last clearangles.extend([oldangle]) cleardistan.extend([olddist]) # ---------------------------- # Create the room # ---------------------------- if len(mypoints) > 1 and len(clearangles) > 0: # Move cursor bpy.context.scene.cursor.location.x = mypoints[0][0] bpy.context.scene.cursor.location.y = mypoints[0][1] bpy.context.scene.cursor.location.z = 0 # always on grid floor # Add room mesh bpy.ops.mesh.archimesh_room() myroom = context.object mydata = myroom.RoomGenerator[0] # Number of walls mydata.wall_num = len(mypoints) - 1 mydata.ceiling = scene.archimesh_ceiling mydata.floor = scene.archimesh_floor mydata.merge = scene.archimesh_merge i = len(mypoints) for e in range(0, i - 1): if clearangles[e] == pi / 2: if cleardistan[e] > 0: mydata.walls[e].w = round(fabs(cleardistan[e]), 2) mydata.walls[e].r = (fabs(clearangles[e]) * 180) / pi # from radians else: mydata.walls[e].w = round(fabs(cleardistan[e]), 2) mydata.walls[e].r = (fabs(clearangles[e]) * 180 * -1) / pi # from radians else: mydata.walls[e].w = round(cleardistan[e], 2) mydata.walls[e].r = (fabs(clearangles[e]) * 180) / pi # from radians # Remove Grease pencil if pencil is not None: for frame in pencil.frames: pencil.frames.remove(frame) self.report({'INFO'}, "Archimesh: Room created from grease pencil strokes") else: self.report({'WARNING'}, "Archimesh: Not enough grease pencil strokes for creating room.") return {'FINISHED'} except: self.report({'WARNING'}, "Archimesh: No grease pencil strokes. Do strokes in top view before creating room") return {'CANCELLED'} # ------------------------------------------------------------------ # Define panel class for main functions. # ------------------------------------------------------------------ class ARCHIMESH_PT_Main(Panel): bl_idname = "ARCHIMESH_PT_main" bl_label = "Archimesh" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Create" bl_context = "objectmode" bl_options = {'DEFAULT_CLOSED'} # ------------------------------ # Draw UI # ------------------------------ def draw(self, context): layout = self.layout scene = context.scene myobj = context.object # ------------------------------------------------------------------------- # If the selected object didn't be created with the group 'RoomGenerator', # this button is not created. # ------------------------------------------------------------------------- # noinspection PyBroadException try: if 'RoomGenerator' in myobj: box = layout.box() box.label(text="Room Tools", icon='MODIFIER') row = box.row(align=False) row.operator("object.archimesh_cut_holes", icon='GRID') row.prop(scene, "archimesh_select_only") # Export/Import row = box.row(align=False) row.operator("io_import.roomdata", text="Import", icon='COPYDOWN') row.operator("io_export.roomdata", text="Export", icon='PASTEDOWN') except: pass # ------------------------------------------------------------------------- # If the selected object isn't a kitchen # this button is not created. # ------------------------------------------------------------------------- # noinspection PyBroadException try: if myobj["archimesh.sku"] is not None: box = layout.box() box.label(text="Kitchen Tools", icon='MODIFIER') # Export row = box.row(align=False) row.operator("io_export.kitchen_inventory", text="Export inventory", icon='PASTEDOWN') except: pass # ------------------------------ # Elements Buttons # ------------------------------ box = layout.box() box.label(text="Elements", icon='GROUP') row = box.row() row.operator("mesh.archimesh_room") row.operator("mesh.archimesh_column") row = box.row() row.operator("mesh.archimesh_door") row = box.row() row.operator("mesh.archimesh_window") row.operator("mesh.archimesh_winpanel") row = box.row() row.operator("mesh.archimesh_kitchen") row.operator("mesh.archimesh_shelves") row = box.row() row.operator("mesh.archimesh_stairs") row.operator("mesh.archimesh_roof") # ------------------------------ # Prop Buttons # ------------------------------ box = layout.box() box.label(text="Props", icon='LIGHT_DATA') row = box.row() row.operator("mesh.archimesh_books") row.operator("mesh.archimesh_light") row = box.row() row.operator("mesh.archimesh_venetian") row.operator("mesh.archimesh_roller") row = box.row() row.operator("mesh.archimesh_japan") # ------------------------------ # OpenGL Buttons # ------------------------------ box = layout.box() box.label(text="Display hints", icon='QUESTION') row = box.row() if context.window_manager.archimesh_run_opengl is False: icon = 'PLAY' txt = 'Show' else: icon = "PAUSE" txt = 'Hide' row.operator("archimesh.runopenglbutton", text=txt, icon=icon) row = box.row() row.prop(scene, "archimesh_gl_measure", toggle=True, icon="ALIGN_CENTER") row.prop(scene, "archimesh_gl_name", toggle=True, icon="OUTLINER_OB_FONT") row.prop(scene, "archimesh_gl_ghost", icon='GHOST_ENABLED') row = box.row() row.prop(scene, "archimesh_text_color", text="") row.prop(scene, "archimesh_walltext_color", text="") row = box.row() row.prop(scene, "archimesh_font_size") row.prop(scene, "archimesh_wfont_size") row = box.row() row.prop(scene, "archimesh_hint_space") # ------------------------------ # Grease pencil tools # ------------------------------ box = layout.box() box.label(text="Pencil Tools", icon='MODIFIER') row = box.row(align=False) row.operator("object.archimesh_pencil_room", icon='GREASEPENCIL') row = box.row(align=False) row.prop(scene, "archimesh_ceiling") row.prop(scene, "archimesh_floor") row.prop(scene, "archimesh_merge") # ------------------------------------------------------------- # Defines button for enable/disable the tip display # # ------------------------------------------------------------- class ARCHIMESH_OT_HintDisplay(Operator): bl_idname = "archimesh.runopenglbutton" bl_label = "Display hint data manager" bl_description = "Display additional information in the viewport" bl_category = 'View' _handle = None # keep function handler # ---------------------------------- # Enable gl drawing adding handler # ---------------------------------- @staticmethod def handle_add(self, context): if ARCHIMESH_OT_HintDisplay._handle is None: ARCHIMESH_OT_HintDisplay._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.archimesh_run_opengl = True # ------------------------------------ # Disable gl drawing removing handler # ------------------------------------ # noinspection PyUnusedLocal @staticmethod def handle_remove(self, context): if ARCHIMESH_OT_HintDisplay._handle is not None: SpaceView3D.draw_handler_remove(ARCHIMESH_OT_HintDisplay._handle, 'WINDOW') ARCHIMESH_OT_HintDisplay._handle = None context.window_manager.archimesh_run_opengl = False # ------------------------------ # Execute button action # ------------------------------ def execute(self, context): if context.area.type == 'VIEW_3D': if context.window_manager.archimesh_run_opengl is False: self.handle_add(self, context) context.area.tag_redraw() else: self.handle_remove(self, context) context.area.tag_redraw() return {'FINISHED'} else: self.report({'WARNING'}, "View3D not found, cannot run operator") return {'CANCELLED'} # ------------------------------------------------------------- # Handler for drawing OpenGl # ------------------------------------------------------------- # noinspection PyUnusedLocal def draw_callback_px(self, context): draw_main(context)