diff --git a/src/bonsai/bonsai/bim/module/aggregate/__init__.py b/src/bonsai/bonsai/bim/module/aggregate/__init__.py index 774117c582..e4cb16d760 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/__init__.py +++ b/src/bonsai/bonsai/bim/module/aggregate/__init__.py @@ -30,7 +30,11 @@ classes = ( operator.BIM_OT_aggregate_unassign_object, operator.BIM_OT_break_link_to_other_aggregates, operator.BIM_OT_select_linked_aggregates, + operator.BIM_OT_aggregate_mode_set_edit, + operator.BIM_OT_disable_aggregate_mode_set_edit, prop.BIMObjectAggregateProperties, + prop.NotEditingObjects, + prop.BIMAggregateProperties, ui.BIM_PT_aggregate, ui.BIM_PT_linked_aggregate, ) @@ -38,7 +42,9 @@ classes = ( def register(): bpy.types.Object.BIMObjectAggregateProperties = bpy.props.PointerProperty(type=prop.BIMObjectAggregateProperties) + bpy.types.Scene.BIMAggregateProperties = bpy.props.PointerProperty(type=prop.BIMAggregateProperties) def unregister(): del bpy.types.Object.BIMObjectAggregateProperties + del bpy.types.Scene.BIMAggregateProperties diff --git a/src/bonsai/bonsai/bim/module/aggregate/operator.py b/src/bonsai/bonsai/bim/module/aggregate/operator.py index 7a4c6ae3a7..049dfeb14c 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/operator.py +++ b/src/bonsai/bonsai/bim/module/aggregate/operator.py @@ -23,6 +23,8 @@ import ifcopenshell.util.element import bonsai.tool as tool import bonsai.core.aggregate as core import bonsai.core.spatial +from bonsai.bim.ifc import IfcStore +from bonsai.bim.module.geometry.decorator import ItemDecorator class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator): @@ -369,3 +371,77 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator): obj.select_set(True) return {"FINISHED"} + +class BIM_OT_aggregate_mode_set_edit(bpy.types.Operator, tool.Ifc.Operator): + bl_description = "Switch from Object to Aggregate Edit mode" + bl_idname = "bim.aggregate_mode_set_edit" + bl_label = "IFC Aggregate Mode Set Edit" + bl_options = {"REGISTER", "UNDO"} + + def invoke(self, context, event): + return IfcStore.execute_ifc_operator(self, context, is_invoke=True) + + def _invoke(self, context, event): + if not tool.Ifc.get(): + return {"FINISHED"} + return self.execute(context) + + def _execute(self, context): + active_object = context.active_object + props = context.scene.BIMAggregateProperties + if props.in_edit_mode == True: + BIM_OT_disable_aggregate_mode_set_edit._execute(self, context) + + element = tool.Ifc.get_entity(active_object) + if not element: + return {"FINISHED"} + aggregate = ifcopenshell.util.element.get_aggregate(element) + parts = ifcopenshell.util.element.get_parts(element) + if not aggregate and not parts: + return {"FINISHED"} + if not parts: + parts = ifcopenshell.util.element.get_parts(aggregate) + if parts: + parts_objs = [tool.Ifc.get_object(part) for part in parts] + objs = [] + visible_objects = tool.Raycast.get_visible_objects(context) + for obj in visible_objects: + if obj.visible_in_viewport_get(context.space_data): + objs.append(obj.original) + for obj in objs: + if obj.original not in parts_objs: + if not obj.data: + continue + obj.original.display_type = "BOUNDS" + data = ItemDecorator.get_obj_data(obj.original) + not_editing_obj = props.not_editing_objects.add() + not_editing_obj.obj = obj.original + + props.in_edit_mode = True + return {"FINISHED"} + +class BIM_OT_disable_aggregate_mode_set_edit(bpy.types.Operator, tool.Ifc.Operator): + bl_description = "Switch from Aggregate to Object Edit mode" + bl_idname = "bim.disable_aggregate_mode_set_edit" + bl_label = "IFC Disable Aggregate Mode Set Edit" + bl_options = {"REGISTER", "UNDO"} + + def invoke(self, context, event): + return IfcStore.execute_ifc_operator(self, context, is_invoke=True) + + def _invoke(self, context, event): + if not tool.Ifc.get(): + return {"FINISHED"} + return self.execute(context) + + def _execute(self, context): + props = context.scene.BIMAggregateProperties + objs = [o.obj for o in props.not_editing_objects] + for obj in objs: + obj.original.display_type = "TEXTURED" + element = tool.Ifc.get_entity(obj) + if not element: + continue + + props.in_edit_mode = False + props.not_editing_objects.clear() diff --git a/src/bonsai/bonsai/bim/module/aggregate/prop.py b/src/bonsai/bonsai/bim/module/aggregate/prop.py index ee6c4d38a2..b5cf9c5f98 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/prop.py +++ b/src/bonsai/bonsai/bim/module/aggregate/prop.py @@ -82,3 +82,11 @@ class BIMObjectAggregateProperties(PropertyGroup): type=bpy.types.Object, poll=poll_related_object, ) + +class NotEditingObjects(bpy.types.PropertyGroup): + obj: PointerProperty(type=bpy.types.Object) + +class BIMAggregateProperties(PropertyGroup): + in_edit_mode: BoolProperty(name="In Edit Mode") + not_editing_objects: CollectionProperty(type=NotEditingObjects) + diff --git a/src/bonsai/bonsai/bim/module/geometry/__init__.py b/src/bonsai/bonsai/bim/module/geometry/__init__.py index 44478124c6..82ff57feb2 100644 --- a/src/bonsai/bonsai/bim/module/geometry/__init__.py +++ b/src/bonsai/bonsai/bim/module/geometry/__init__.py @@ -50,6 +50,8 @@ classes = ( operator.OverrideMeshSeparate, operator.OverrideModeSetEdit, operator.OverrideModeSetObject, + operator.OverrideMoveAggregate, + operator.OverrideMoveAggregateMacro, operator.OverrideOriginSet, operator.OverrideOutlinerDelete, operator.OverridePasteBuffer, @@ -94,6 +96,8 @@ def register(): operator.OverrideDuplicateMoveLinkedMacro.define("TRANSFORM_OT_translate") operator.DuplicateMoveLinkedAggregateMacro.define("BIM_OT_object_duplicate_move_linked_aggregate") operator.DuplicateMoveLinkedAggregateMacro.define("TRANSFORM_OT_translate") + operator.OverrideMoveAggregateMacro.define("BIM_OT_override_move_aggregate") + operator.OverrideMoveAggregateMacro.define("TRANSFORM_OT_translate") bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties) bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties) @@ -116,6 +120,8 @@ def register(): "bim.object_duplicate_move_linked_aggregate_macro", "D", "PRESS", ctrl=True, shift=True ) addon_keymaps.append((km, kmi)) + kmi = km.keymap_items.new("bim.override_move_aggregate_macro", "G", "PRESS") + addon_keymaps.append((km, kmi)) kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True) addon_keymaps.append((km, kmi)) kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS") diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 1845330ef9..05cb39ce1d 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -2864,3 +2864,51 @@ class AddCurvelikeItem(bpy.types.Operator, tool.Ifc.Operator): obj.data.BIMMeshProperties.ifc_definition_id = item.id() tool.Geometry.import_item(obj) tool.Geometry.import_item_attributes(obj) + + +class OverrideMoveAggregateMacro(bpy.types.Macro): + bl_idname = "bim.override_move_aggregate_macro" + bl_label = "IFC Move Aggregate" + bl_options = {"REGISTER", "UNDO"} + +class OverrideMoveAggregate(bpy.types.Operator): + bl_idname = "bim.override_move_aggregate" + bl_label = "IFC Move Aggregate" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return len(context.selected_objects) > 0 + + def execute(self, context): + # Deep magick from the dawn of time + if IfcStore.get_file(): + IfcStore.execute_ifc_operator(self, context) + if self.new_active_obj: + context.view_layer.objects.active = self.new_active_obj + return {"FINISHED"} + + return {"FINISHED"} + + def _execute(self, context): + props = context.scene.BIMAggregateProperties + not_editing_objs = [o.obj for o in props.not_editing_objects] + aggregates_to_move = [] + for obj in context.selected_objects: + self.new_active_obj = None + if obj in not_editing_objs: + obj.select_set(False) + print("foi") + break + element = tool.Ifc.get_entity(obj) + if not element or props.in_edit_mode: + continue + aggregate = ifcopenshell.util.element.get_aggregate(element) + if aggregate: + aggregates_to_move.append(tool.Ifc.get_object(aggregate)) + obj.select_set(False) + aggregates_to_move = set(aggregates_to_move) + for obj in aggregates_to_move: + obj.select_set(True) + self.new_active_obj = obj + return {"FINISHED"} diff --git a/src/bonsai/bonsai/tool/aggregate.py b/src/bonsai/bonsai/tool/aggregate.py index 8e767dac35..fed5047758 100644 --- a/src/bonsai/bonsai/tool/aggregate.py +++ b/src/bonsai/bonsai/tool/aggregate.py @@ -90,12 +90,13 @@ class Aggregate(bonsai.core.tool.Aggregate): parts = ifcopenshell.util.element.get_parts(agg_element) parts_objs = [tool.Ifc.get_object(p) for p in parts] for obj in parts_objs: - if not obj.data: - continue - tool.Geometry.lock_object(obj) constraint = next((c for c in obj.constraints if c.type == "CHILD_OF"), None) if constraint: - obj.constraints.remove(constraint) + try: + bpy.context.view_layer.objects.active = obj + bpy.ops.constraint.apply(constraint="Child Of") + except: + pass constraint = obj.constraints.new("CHILD_OF") constraint.target = aggregate @@ -103,5 +104,5 @@ class Aggregate(bonsai.core.tool.Aggregate): def apply_constraints(cls, part: bpy.types.Object): constraint = next((c for c in part.constraints if c.type == "CHILD_OF"), None) if constraint: + bpy.context.view_layer.objects.active = part bpy.ops.constraint.apply(constraint=constraint.name) - tool.Geometry.unlock_object(part)