From 547c5fb8a948c7af13e581dd4a07488460150154 Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 6 Jan 2025 11:06:41 +0100 Subject: [PATCH 1/2] TAB now applies modifier parameters on the active object. ESCAPE now cancels editing modifier parameters on the active object. --- src/bonsai/bonsai/bim/module/geometry/data.py | 3 - .../bonsai/bim/module/geometry/operator.py | 14 +-- src/bonsai/bonsai/tool/blender.py | 102 +++++++++++++++--- 3 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/data.py b/src/bonsai/bonsai/bim/module/geometry/data.py index a7f6c718fe..fa83a2bc33 100644 --- a/src/bonsai/bonsai/bim/module/geometry/data.py +++ b/src/bonsai/bonsai/bim/module/geometry/data.py @@ -78,9 +78,6 @@ class ViewportData: modes.append(edit_mode) elif element.is_a("IfcGridAxis"): modes.append(edit_mode) - elif tool.Blender.Modifier.is_editing_parameters(obj): - # This should go BEFORE the modifiers - pass elif tool.Blender.Modifier.is_roof(element): modes.append(edit_mode) elif tool.Blender.Modifier.is_railing(element): diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index f0dd3e29cd..9208ba302a 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -1858,8 +1858,9 @@ class OverrideEscape(bpy.types.Operator): bpy.ops.bim.hide_all_openings() elif context.scene.BIMAggregateProperties.in_aggregate_mode: bpy.ops.bim.disable_aggregate_mode() - elif context.active_object and context.active_object.BIMRailingProperties.is_editing_path: - bpy.ops.bim.cancel_editing_railing_path() + elif active_object:=context.active_object: + if tool.Blender.Modifier.try_canceling_editing_modifier_parameters_or_path(active_object): + pass return {"FINISHED"} @@ -1921,13 +1922,8 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.bim.enable_editing_boundary_geometry() elif element.is_a("IfcGridAxis"): self.enable_edit_mode(context) - elif tool.Blender.Modifier.is_editing_parameters(obj): - # This should go BEFORE the modifiers - self.report({"INFO"}, "Can't edit while modifier parameters are being modified") - elif tool.Blender.Modifier.is_roof(element): - bpy.ops.bim.enable_editing_roof_path() - elif tool.Blender.Modifier.is_railing(element): - bpy.ops.bim.enable_editing_railing_path() + elif tool.Blender.Modifier.try_applying_edit_mode(obj, element): + pass else: bpy.ops.bim.import_representation_items() elif tool.Geometry.is_representation_item(obj): diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index ee10c6dc76..ec1665eb5c 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -21,6 +21,7 @@ import bpy import bmesh import json import os +from ifcopenshell import entity_instance import ifcopenshell.api import ifcopenshell.util.element import bonsai.core.tool @@ -922,58 +923,131 @@ class Blender(bonsai.core.tool.Blender): class Modifier: @classmethod - def is_eligible_for_railing_modifier(cls, obj): + def try_applying_edit_mode(cls, obj: bpy.types.Object, element: entity_instance) -> bool: + """Tries to validate the current BIM modifier parameters for the active object + Goes into path editing mode if the modifier supports it + + Returns True if an action was taken, False otherwise + """ + if cls.is_roof(element): + if cls.is_editing_roof_parameters(obj): + bpy.ops.bim.finish_editing_roof() + bpy.ops.bim.enable_editing_roof_path() + elif cls.is_railing(element): + if cls.is_editing_railing_parameters(obj): + bpy.ops.bim.finish_editing_railing() + bpy.ops.bim.enable_editing_railing_path() + elif cls.is_editing_stair_parameters(obj): + bpy.ops.bim.finish_editing_stair() + elif cls.is_editing_door_parameters(obj): + bpy.ops.bim.finish_editing_door() + elif cls.is_editing_window_parameters(obj): + bpy.ops.bim.finish_editing_window() + else: + return False + return True + + @classmethod + def try_canceling_editing_modifier_parameters_or_path(cls, obj: bpy.types.Object) -> bool: + """Tries to cancel the current BIM modifier parameters or path edition for the active object + + Returns True if an action was taken, False otherwise + """ + if cls.is_editing_railing_path(obj): + bpy.ops.bim.cancel_editing_railing_path() + elif cls.is_editing_roof_path(obj): + bpy.ops.bim.cancel_editing_roof_path() + elif cls.is_editing_railing_parameters(obj): + bpy.ops.bim.cancel_editing_railing() + elif cls.is_editing_door_parameters(obj): + bpy.ops.bim.cancel_editing_door() + elif cls.is_editing_window_parameters(obj): + bpy.ops.bim.cancel_editing_window() + elif cls.is_editing_roof_parameters(obj): + bpy.ops.bim.cancel_editing_roof() + elif cls.is_editing_stair_parameters(obj): + bpy.ops.bim.cancel_editing_stair() + else: + return False + return True + + @classmethod + def is_eligible_for_railing_modifier(cls, obj: bpy.types.Object)-> bool: return tool.Blender.is_object_an_ifc_class(obj, ("IfcRailing", "IfcRailingType")) @classmethod - def is_eligible_for_stair_modifier(cls, obj): + def is_eligible_for_stair_modifier(cls, obj: bpy.types.Object)-> bool: return tool.Blender.is_object_an_ifc_class( obj, ("IfcStairFlight", "IfcStairFlightType", "IfcMember", "IfcMemberType", "IfcStair", "IfcStairType") ) @classmethod - def is_eligible_for_window_modifier(cls, obj): + def is_eligible_for_window_modifier(cls, obj: bpy.types.Object)-> bool: return tool.Blender.is_object_an_ifc_class(obj, ("IfcWindow", "IfcWindowType", "IfcWindowStyle")) @classmethod - def is_eligible_for_door_modifier(cls, obj): + def is_eligible_for_door_modifier(cls, obj: bpy.types.Object)-> bool: return tool.Blender.is_object_an_ifc_class(obj, ("IfcDoor", "IfcDoorType", "IfcDoorStyle")) @classmethod - def is_eligible_for_roof_modifier(cls, obj): + def is_eligible_for_roof_modifier(cls, obj: bpy.types.Object)-> bool: return tool.Blender.is_object_an_ifc_class(obj, ("IfcRoof", "IfcRoofType")) @classmethod - def is_railing(cls, element): + def is_railing(cls, element: entity_instance)-> bool: return tool.Pset.get_element_pset(element, "BBIM_Railing") @classmethod - def is_roof(cls, element): + def is_roof(cls, element: entity_instance)-> bool: return tool.Pset.get_element_pset(element, "BBIM_Roof") @classmethod - def is_window(cls, element): + def is_window(cls, element: entity_instance)-> bool: return tool.Pset.get_element_pset(element, "BBIM_Window") @classmethod - def is_door(cls, element): + def is_door(cls, element: entity_instance)-> bool: return tool.Pset.get_element_pset(element, "BBIM_Door") @classmethod - def is_stair(cls, element): + def is_stair(cls, element: entity_instance)-> bool: return tool.Pset.get_element_pset(element, "BBIM_Stair") @classmethod - def is_editing_parameters(cls, obj): - return obj.BIMRailingProperties.is_editing or obj.BIMRoofProperties.is_editing + def is_editing_railing_path(cls, obj: bpy.types.Object): + return obj.BIMRailingProperties.is_editing_path @classmethod - def is_modifier_with_non_editable_path(cls, element): + def is_editing_roof_path(cls, obj: bpy.types.Object)-> bool: + return obj.BIMRoofProperties.is_editing_path + + @classmethod + def is_editing_railing_parameters(cls, obj: bpy.types.Object)-> bool: + return obj.BIMRailingProperties.is_editing + + @classmethod + def is_editing_roof_parameters(cls, obj: bpy.types.Object)-> bool: + return obj.BIMRoofProperties.is_editing + + @classmethod + def is_editing_window_parameters(cls, obj: bpy.types.Object)-> bool: + return obj.BIMWindowProperties.is_editing + + @classmethod + def is_editing_door_parameters(cls, obj: bpy.types.Object)-> bool: + return obj.BIMDoorProperties.is_editing + + @classmethod + def is_editing_stair_parameters(cls, obj: bpy.types.Object)-> bool: + return obj.BIMStairProperties.is_editing + + @classmethod + def is_modifier_with_non_editable_path(cls, element: entity_instance)-> bool: return cls.is_stair(element) or cls.is_door(element) or cls.is_window(element) class Array: @classmethod - def bake_children_transform(cls, parent_element, item): + def bake_children_transform(cls, parent_element: entity_instance, item): modifier_data = list(cls.get_modifiers_data(parent_element))[item] children = cls.get_children_objects(modifier_data) for child in children: From f6e588b48ceac0c1570cc57f7199c5d89fc4410c Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 6 Jan 2025 11:35:17 +0100 Subject: [PATCH 2/2] You can now copy roof parameters from active to selected objects --- .../bonsai/bim/module/model/__init__.py | 1 + src/bonsai/bonsai/bim/module/model/data.py | 5 +++ src/bonsai/bonsai/bim/module/model/roof.py | 37 ++++++++++++++++++- src/bonsai/bonsai/bim/module/model/ui.py | 3 +- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/__init__.py b/src/bonsai/bonsai/bim/module/model/__init__.py index fccf21ecbe..2be31b25c1 100644 --- a/src/bonsai/bonsai/bim/module/model/__init__.py +++ b/src/bonsai/bonsai/bim/module/model/__init__.py @@ -186,6 +186,7 @@ classes = ( roof.BIM_OT_add_roof, roof.AddRoof, roof.CancelEditingRoof, + roof.CopyRoofParameters, roof.FinishEditingRoof, roof.EnableEditingRoof, roof.CancelEditingRoofPath, diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index 17b66f49f4..bfe7c729e2 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -569,6 +569,7 @@ class RoofData: if not cls.data["pset_data"]: return cls.data["general_params"] = cls.general_params() + cls.data["path_data"] = cls.path_data() @classmethod def pset_data(cls): @@ -589,6 +590,10 @@ class RoofData: general_params[prop_readable_name] = prop_value return general_params + @classmethod + def path_data(cls): + return cls.data["pset_data"]["data_dict"]["path_data"] + class ItemData: data = {} diff --git a/src/bonsai/bonsai/bim/module/model/roof.py b/src/bonsai/bonsai/bim/module/model/roof.py index 289a64f8ab..e60fdc65a7 100644 --- a/src/bonsai/bonsai/bim/module/model/roof.py +++ b/src/bonsai/bonsai/bim/module/model/roof.py @@ -438,7 +438,7 @@ def update_roof_modifier_bmesh(obj: bpy.types.Object) -> None: # NOTE: using Data since bmesh update will hapen very often if not RoofData.is_loaded: RoofData.load() - path_data = RoofData.data["pset_data"]["data_dict"]["path_data"] + path_data = RoofData.data["path_data"] angle_layer_data = path_data.get("gable_roof_angles", None) separate_verts_data = path_data.get("gable_roof_separate_verts", None) @@ -739,6 +739,41 @@ class CancelEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): return cancel_editing_roof_path(context) +class CopyRoofParameters(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.copy_roof_parameters" + bl_label = "Copy Roof Parameters from Active to Selected" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return context.active_object and len(context.selected_objects) > 1 + + def _execute(self, context): + source_obj = context.active_object + source_props = source_obj.BIMRoofProperties + data = source_props.get_general_kwargs(convert_to_project_units=True) + + for target_obj in context.selected_objects: + if target_obj == source_obj: + continue + context.view_layer.objects.active = target_obj + RoofData.load() + if not "path_data" in RoofData.data: + continue + data["path_data"] = RoofData.data["path_data"] + target_element = tool.Ifc.get_entity(target_obj) + target_props = target_obj.BIMRoofProperties + + target_props.set_props_kwargs_from_ifc_data(data) + update_bbim_roof_pset(target_element, data) + refresh() + update_roof_modifier_bmesh(target_obj) + update_roof_modifier_ifc_data(context) + + context.view_layer.objects.active = source_obj + return {"FINISHED"} + + class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.finish_editing_roof_path" bl_label = "Finish Editing Roof Path" diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 0e111514b6..0563c03a5b 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -649,13 +649,12 @@ class BIM_PT_roof(bpy.types.Panel): self.layout.prop(props, prop) update_roof_modifier_bmesh(obj) - elif props.is_editing_path: row.operator("bim.finish_editing_roof_path", icon="CHECKMARK", text="") row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="") - else: row.operator("bim.enable_editing_roof", icon="GREASEPENCIL", text="") + row.operator("bim.copy_roof_parameters", icon="COPYDOWN", text="") row.operator("bim.enable_editing_roof_path", icon="ANIM", text="") row.operator("bim.remove_roof", icon="X", text="")