From 4ad3d6562820bd2fbd025dd32470922643442e21 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 8 Mar 2023 16:56:29 +0500 Subject: [PATCH] UI fixes for railing and roof modifiers Changes: 1) Added editing railing / roof path as hotkey to bim / cad tools. 2) Added profile decorator 3) Some switching modes and tools for convenience 4) Fixed nasty bug in tool.Blender.apply_bmesh - method wasn't updating current mesh if it was in edit mode. --- .../blenderbim/bim/module/cad/workspace.py | 41 ++++++++++++++- .../blenderbim/bim/module/model/door.py | 1 + .../blenderbim/bim/module/model/railing.py | 32 ++++++++++-- .../blenderbim/bim/module/model/roof.py | 25 ++++++++-- .../blenderbim/bim/module/model/slab.py | 1 + .../blenderbim/bim/module/model/ui.py | 4 +- .../blenderbim/bim/module/model/workspace.py | 50 ++++++++++++++++++- src/blenderbim/blenderbim/tool/blender.py | 1 + 8 files changed, 142 insertions(+), 13 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cad/workspace.py b/src/blenderbim/blenderbim/bim/module/cad/workspace.py index a8507e92e7..976cff7f4d 100644 --- a/src/blenderbim/blenderbim/bim/module/cad/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/cad/workspace.py @@ -21,7 +21,7 @@ import bpy import blenderbim.tool as tool import blenderbim.bim.module.type.prop as type_prop from bpy.types import WorkSpaceTool -from blenderbim.bim.module.model.data import AuthoringData +from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData class CadTool(WorkSpaceTool): @@ -102,6 +102,7 @@ class CadTool(WorkSpaceTool): row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_X") row.operator("bim.reset_vertex", text="Reset Vertex") + elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS": row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") @@ -128,7 +129,30 @@ class CadTool(WorkSpaceTool): row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_O") row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O" + else: + if ( + (RailingData.is_loaded or not RailingData.load()) + and RailingData.data["parameters"] + and context.active_object.BIMRailingProperties.is_editing_path + ): + row = layout.row(align=True) + row.label(text="", icon="EVENT_SHIFT") + row.label(text="", icon="EVENT_Q") + row.operator("bim.hotkey", text="Apply Railing Path").hotkey = "S_Q" + row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="") + + elif ( + (RoofData.is_loaded or not RoofData.load()) + and RoofData.data["parameters"] + and context.active_object.BIMRoofProperties.is_editing_path + ): + row = layout.row(align=True) + row.label(text="", icon="EVENT_SHIFT") + row.label(text="", icon="EVENT_Q") + row.operator("bim.hotkey", text="Apply Roof Path").hotkey = "S_Q" + row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="") + row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_E") @@ -219,6 +243,21 @@ class CadHotkey(bpy.types.Operator): bpy.ops.bim.edit_extrusion_profile() elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS": bpy.ops.bim.edit_extrusion_axis() + + elif ( + (RailingData.is_loaded or not RailingData.load()) + and RailingData.data["parameters"] + and bpy.context.active_object.BIMRailingProperties.is_editing_path + ): + bpy.ops.bim.finish_editing_railing_path() + + elif ( + (RoofData.is_loaded or not RoofData.load()) + and RoofData.data["parameters"] + and bpy.context.active_object.BIMRoofProperties.is_editing_path + ): + bpy.ops.bim.finish_editing_roof_path() + else: bpy.ops.bim.edit_arbitrary_profile() diff --git a/src/blenderbim/blenderbim/bim/module/model/door.py b/src/blenderbim/blenderbim/bim/module/model/door.py index 69b6e28829..8bdf168b42 100644 --- a/src/blenderbim/blenderbim/bim/module/model/door.py +++ b/src/blenderbim/blenderbim/bim/module/model/door.py @@ -118,6 +118,7 @@ def update_door_modifier_representation(context): update_simple_openings(element, props.overall_width, props.overall_height) +# TODO: move it out to tools def bm_sort_out_geom(geom_data): geom_dict = {"verts": [], "edges": [], "faces": []} diff --git a/src/blenderbim/blenderbim/bim/module/model/railing.py b/src/blenderbim/blenderbim/bim/module/model/railing.py index 881a4b2828..9cc10c9125 100644 --- a/src/blenderbim/blenderbim/bim/module/model/railing.py +++ b/src/blenderbim/blenderbim/bim/module/model/railing.py @@ -30,6 +30,7 @@ from blenderbim.bim.helper import convert_property_group_from_si from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.model.door import bm_sort_out_geom from blenderbim.bim.module.model.data import RailingData, refresh +from blenderbim.bim.module.model.decorator import ProfileDecorator from mathutils import Vector, Matrix from pprint import pprint @@ -40,6 +41,18 @@ import json # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm +# TODO: move it out to tools +def blender_get_viewport_context(): + """Get viewport area context for context overriding. + + It's a bit naive since it's just taking the first available `VIEW_3D` area + when in real life you can have a couple of those. + """ + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + context_override = {"area": area} + return context_override + + def bm_split_edge_at_offset(edge, offset): v0, v1 = edge.verts @@ -352,10 +365,14 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object props = obj.BIMRailingProperties - element = tool.Ifc.get_entity(obj) props.is_editing_path = True update_railing_modifier_bmesh(context) + + if bpy.context.object.mode != "EDIT": + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.wm.tool_set_by_id(blender_get_viewport_context(), name="bim.cad_tool") + ProfileDecorator.install(context) return {"FINISHED"} @@ -366,10 +383,14 @@ class CancelEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object - element = tool.Ifc.get_entity(obj) props = obj.BIMRailingProperties - update_railing_modifier_bmesh(context) + + ProfileDecorator.uninstall() props.is_editing_path = False + + update_railing_modifier_bmesh(context) + if bpy.context.object.mode == "EDIT": + bpy.ops.object.mode_set(mode="OBJECT") return {"FINISHED"} @@ -386,11 +407,14 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): railing_data = props.get_general_kwargs() path_data = get_path_data(obj) railing_data["path_data"] = path_data + ProfileDecorator.uninstall() props.is_editing_path = False update_bbim_railing_pset(element, railing_data) - refresh() # need RailingData to be updated before update_railing_modifier_bmesh + refresh() # RailingData has to be updated before run update_railing_modifier_bmesh update_railing_modifier_bmesh(context) + if bpy.context.object.mode == "EDIT": + bpy.ops.object.mode_set(mode="OBJECT") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py index 543bf3c41b..2769040934 100644 --- a/src/blenderbim/blenderbim/bim/module/model/roof.py +++ b/src/blenderbim/blenderbim/bim/module/model/roof.py @@ -25,7 +25,9 @@ import blenderbim import blenderbim.tool as tool from blenderbim.bim.helper import convert_property_group_from_si from blenderbim.bim.module.model.door import bm_sort_out_geom +from blenderbim.bim.module.model.railing import blender_get_viewport_context from blenderbim.bim.module.model.data import RoofData, refresh +from blenderbim.bim.module.model.decorator import ProfileDecorator import json from math import tan, radians @@ -135,13 +137,15 @@ def update_roof_modifier_ifc_data(context): obj = context.active_object props = obj.BIMRoofProperties element = tool.Ifc.get_entity(obj) - ifc_file = tool.Ifc.get() # type attributes element.PredefinedType = props.roof_type # occurences attributes # occurences = tool.Ifc.get_all_element_occurences(element) + # TODO: add Qto_RoofBaseQuantities, need to calculate GrossArea, NetArea, ProjectedArea + # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/Qto_RoofBaseQuantities.htm + def update_bbim_roof_pset(element, roof_data): pset = tool.Pset.get_element_pset(element, "BBIM_Roof") @@ -378,10 +382,14 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object props = obj.BIMRoofProperties - element = tool.Ifc.get_entity(obj) props.is_editing_path = True update_roof_modifier_bmesh(context) + + if bpy.context.object.mode != "EDIT": + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.wm.tool_set_by_id(blender_get_viewport_context(), name="bim.cad_tool") + ProfileDecorator.install(context) return {"FINISHED"} @@ -392,10 +400,14 @@ class CancelEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object - element = tool.Ifc.get_entity(obj) props = obj.BIMRoofProperties - update_roof_modifier_bmesh(context) + + ProfileDecorator.uninstall() props.is_editing_path = False + + update_roof_modifier_bmesh(context) + if bpy.context.object.mode == "EDIT": + bpy.ops.object.mode_set(mode="OBJECT") return {"FINISHED"} @@ -412,11 +424,14 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): roof_data = props.get_general_kwargs() path_data = get_path_data(obj) roof_data["path_data"] = path_data + ProfileDecorator.uninstall() props.is_editing_path = False update_bbim_roof_pset(element, roof_data) - refresh() # need RoofData to be updated before update_roof_modifier_bmesh + refresh() # RoofData has to be updated before run update_roof_modifier_bmesh update_roof_modifier_bmesh(context) + if bpy.context.object.mode == "EDIT": + bpy.ops.object.mode_set(mode="OBJECT") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index e376a07c66..0de5f190da 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -612,6 +612,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.object.mode_set(mode="EDIT") ProfileDecorator.install(context) + # TODO: test it from properties panel? if not bpy.app.background: bpy.ops.wm.tool_set_by_id(name="bim.cad_tool") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 110b88b26f..02c14968f9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -577,7 +577,7 @@ class BIM_PT_railing(bpy.types.Panel): update_railing_modifier_bmesh(context) elif props.is_editing_path: - row.operator("bim.finish_editing_railing_path", icon="CHECKMARK", text="Finish editing path") + row.operator("bim.finish_editing_railing_path", icon="CHECKMARK", text="") row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="") else: @@ -636,7 +636,7 @@ class BIM_PT_roof(bpy.types.Panel): update_roof_modifier_bmesh(context) elif props.is_editing_path: - row.operator("bim.finish_editing_roof_path", icon="CHECKMARK", text="Finish editing path") + row.operator("bim.finish_editing_roof_path", icon="CHECKMARK", text="") row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="") else: diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 9a8ed666de..b3c88a2923 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -24,7 +24,7 @@ import blenderbim.tool as tool import blenderbim.bim.module.type.prop as type_prop from blenderbim.bim.helper import prop_with_search, close_operator_panel from bpy.types import WorkSpaceTool -from blenderbim.bim.module.model.data import AuthoringData +from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData from blenderbim.bim.module.model import prop @@ -225,6 +225,7 @@ class BimToolUI: row.operator("bim.join_wall", icon="X", text="").join_type = "" elif AuthoringData.data["active_material_usage"] == "LAYER3": + # unnecessary check because BIM Tool is not available in EDIT mode? if context.active_object.mode == "OBJECT": row = cls.layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") @@ -274,6 +275,7 @@ class BimToolUI: row.label(text="", icon="EVENT_G") add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__) row.operator("bim.extend_profile", icon="X", text="").join_type = "" + elif AuthoringData.data["active_class"] in ( "IfcWindow", "IfcWindowStandardCase", @@ -295,11 +297,13 @@ class BimToolUI: row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_F") row.operator("bim.hotkey", text="Flip").hotkey = "S_F" + elif AuthoringData.data["active_class"] in ("IfcSpace",): row = cls.layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_G") add_layout_hotkey_operator(row, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__) + elif AuthoringData.data["active_class"] in ( "IfcCableCarrierSegmentType", "IfcCableSegmentType", @@ -310,6 +314,26 @@ class BimToolUI: row.label(text="", icon="EVENT_SHIFT") row.label(text="Extend", icon="EVENT_E") + elif ( + (RailingData.is_loaded or not RailingData.load()) + and RailingData.data["parameters"] + and not context.active_object.BIMRailingProperties.is_editing_path + ): + row = cls.layout.row(align=True) + row.label(text="", icon="EVENT_SHIFT") + row.label(text="", icon="EVENT_E") + row.operator("bim.hotkey", text="Edit Railing Path").hotkey = "S_E" + + elif ( + (RoofData.is_loaded or not RoofData.load()) + and RoofData.data["parameters"] + and not context.active_object.BIMRoofProperties.is_editing_path + ): + row = cls.layout.row(align=True) + row.label(text="", icon="EVENT_SHIFT") + row.label(text="", icon="EVENT_E") + row.operator("bim.hotkey", text="Edit Roof Path").hotkey = "S_E" + row = cls.layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_O") @@ -317,6 +341,7 @@ class BimToolUI: row.operator("bim.add_opening", text="Apply Void") else: row.operator("bim.add_potential_opening", text="Add Void") + if AuthoringData.data["is_voidable_element"]: if AuthoringData.data["has_visible_openings"]: row.operator("bim.edit_openings", icon="CHECKMARK", text="") @@ -462,6 +487,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): def hotkey_S_Q(self): if not bpy.context.selected_objects: return + bpy.ops.bim.calculate_all_quantities() def hotkey_S_C(self): @@ -499,27 +525,49 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): elif self.active_material_usage == "PROFILE": # Extend PROFILE to cursor bpy.ops.bim.extend_profile(join_type="T") + elif self.active_material_usage == "LAYER2" and selected_usages.get("PROFILE", []): # Extend PROFILEs to LAYER2 [o.select_set(False) for o in selected_usages.get("LAYER3", [])] [o.select_set(False) for o in selected_usages.get("LAYER2", []) if o != bpy.context.active_object] bpy.ops.bim.extend_profile(join_type="T") + elif self.active_material_usage == "LAYER3" and selected_usages.get("LAYER2", []): # Extend LAYER2s to LAYER3 [o.select_set(False) for o in selected_usages.get("PROFILE", [])] [o.select_set(False) for o in selected_usages.get("LAYER3", []) if o != bpy.context.active_object] bpy.ops.bim.join_wall(join_type="T") + elif self.active_material_usage == "LAYER2": # Extend LAYER2s to LAYER2 [o.select_set(False) for o in selected_usages.get("LAYER3", [])] [o.select_set(False) for o in selected_usages.get("PROFILE", [])] bpy.ops.bim.join_wall(join_type="T") + elif self.active_material_usage == "PROFILE": # Extend PROFILEs to PROFILE [o.select_set(False) for o in selected_usages.get("LAYER3", [])] [o.select_set(False) for o in selected_usages.get("LAYER2", [])] bpy.ops.bim.extend_profile(join_type="T") + elif ( + (RailingData.is_loaded or not RailingData.load()) + and RailingData.data["parameters"] + and not bpy.context.active_object.BIMRailingProperties.is_editing_path + ): + # undo the unselection done above because railing has no usage type 🙃 + bpy.context.object.select_set(True) + bpy.ops.bim.enable_editing_railing_path() + + elif ( + (RoofData.is_loaded or not RoofData.load()) + and RoofData.data["parameters"] + and not bpy.context.active_object.BIMRoofProperties.is_editing_path + ): + # undo the unselection done above because roof has no usage type + bpy.context.object.select_set(True) + bpy.ops.bim.enable_editing_roof_path() + def hotkey_S_F(self): if not bpy.context.selected_objects: return diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index 5efcf891e3..eb48c31e4c 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -106,6 +106,7 @@ class Blender: "For applying bmesh in edit mode bmesh should be acquired with `bmesh.from_edit_mesh(me)`." ) bmesh.update_edit_mesh(mesh) + bpy.context.object.update_from_editmode() else: bm.to_mesh(mesh)