diff --git a/src/bonsai/bonsai/bim/module/model/opening.py b/src/bonsai/bonsai/bim/module/model/opening.py index d1641eac90..cb0d612cff 100644 --- a/src/bonsai/bonsai/bim/module/model/opening.py +++ b/src/bonsai/bonsai/bim/module/model/opening.py @@ -26,6 +26,7 @@ import numpy as np import ifcopenshell import ifcopenshell.api import ifcopenshell.api.geometry +import ifcopenshell.api.void import ifcopenshell.geom import ifcopenshell.util.shape import ifcopenshell.util.element @@ -1097,20 +1098,28 @@ class EditOpenings(Operator, tool.Ifc.Operator): class CloneOpening(Operator, tool.Ifc.Operator): bl_idname = "bim.clone_opening" bl_label = "Clone Opening" - bl_description = "Clone the selected Opening and assign to the selected Element" + bl_description = "Clone the active Opening object and assign to the selected Element" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): - objects = bpy.context.selected_objects + @classmethod + def poll(cls, context): + if len(context.selected_objects) != 2: + cls.poll_message_set("Exactly 2 objects must be selected.") + return False + return True - for obj in objects: - entity = tool.Ifc.get_entity(obj) - if entity.is_a() == "IfcWall": - wall = entity - continue - if entity.is_a() == "IfcOpeningElement": - opening = entity - continue + def _execute(self, context): + # NOTE: Operator displayed in UI only with IfcOpeningElement being active. + ifc_file = tool.Ifc.get() + objects = bpy.context.selected_objects + opening_obj = context.active_object + assert opening_obj + opening = tool.Ifc.get_entity(opening_obj) + assert opening and opening.is_a("IfcOpeningElement") + + voided_obj = next(o for o in objects if o != opening_obj) + voided_element = tool.Ifc.get_entity(voided_obj) + assert voided_element opening_placement = opening.ObjectPlacement opening_representation = opening.Representation @@ -1118,7 +1127,7 @@ class CloneOpening(Operator, tool.Ifc.Operator): new_opening = ifcopenshell.api.run("root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement") new_opening.Representation = opening_representation - ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=new_opening, element=wall) + ifcopenshell.api.void.add_opening(ifc_file, opening=new_opening, element=voided_element) new_opening.ObjectPlacement = opening_placement return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 2b20c003c9..1b6b06a449 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -217,8 +217,18 @@ class CableTool(BimTool): def add_layout_hotkey_operator( - layout: bpy.types.UILayout, text: str, hotkey: str, description: Union[str, None], ui_context: str = "" + layout: bpy.types.UILayout, + text: str, + hotkey: str, + description: Union[str, None], + ui_context: str = "", + *, + operator: str = "bim.hotkey", ) -> bpy.types.OperatorProperties: + """ + :param operator: Operator to display in UI. Displaying the specific operator in UI can be useful + to provide poll error messages. + """ parts = hotkey.split("_") if hotkey else [] modifier, key = (parts + ["", ""])[:2] @@ -227,7 +237,7 @@ def add_layout_hotkey_operator( modifier_icon, modifier_str = MODIFIERS.get(modifier, ("NONE", "")) row = layout.row(align=True) - op = row.operator("bim.hotkey", text=op_text, icon_value=custom_icon) + op = row.operator(operator, text=op_text, icon_value=custom_icon) if ui_context != "TOOL_HEADER": row.label(text="", icon=modifier_icon) @@ -236,11 +246,12 @@ def add_layout_hotkey_operator( hotkey_description = f"Hotkey: {modifier_str} {key}".strip() description = "\n\n".join(filter(None, [description, hotkey_description])) - op.hotkey = hotkey - if ui_context == "TOOL_HEADER": - op.description = text + "\n" + description - else: - op.description = description + if operator == "bim.hotkey": + op.hotkey = hotkey + if ui_context == "TOOL_HEADER": + op.description = text + "\n" + description + else: + op.description = description return op @@ -804,12 +815,8 @@ class EditObjectUI: op_text = "" if IS_TOOL_HEADER else "Edit Openings" row.operator("bim.edit_openings", icon="CHECKMARK", text=op_text) row.operator("bim.hide_openings", icon="CANCEL", text="") - if len(context.selected_objects) == 2: - row = cls.layout.row(align=True) - row.label(text="", icon="EVENT_SHIFT") - row.label(text="", icon="EVENT_L") - row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row - row.operator("bim.clone_opening", text="Clone Opening") + row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row + add_layout_hotkey_operator(row, "Clone Opening", "S_L", "", ui_context, operator="bim.clone_opening") @classmethod def draw_align(cls, context):