From 3dcded819faef8499050b739e8580193cf35426a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Fri, 22 Nov 2024 11:05:09 -0300 Subject: [PATCH] Add new way to move objects that are a part of an aggregate. Now, when trying to move an object that is a part of an aggregate, the `OverrideMoveAggregate` will be called, meaning that it will move the whole aggregation. It works by creating constraints and moving the aggregate instead of the selected object. It also introduces the idea of editing an aggregate (no hotkey yet). When active, the user will be able to move the parts of the aggregate instead of the whole aggregation: https://imgur.com/IcCGtPN --- .../bonsai/bim/module/aggregate/__init__.py | 6 ++ .../bonsai/bim/module/aggregate/operator.py | 76 +++++++++++++++++++ .../bonsai/bim/module/aggregate/prop.py | 8 ++ .../bonsai/bim/module/geometry/__init__.py | 6 ++ .../bonsai/bim/module/geometry/operator.py | 48 ++++++++++++ src/bonsai/bonsai/tool/aggregate.py | 11 +-- 6 files changed, 150 insertions(+), 5 deletions(-) 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)