From 4e833e26ca75814604a29feee8cb598d58d68946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Fri, 13 Oct 2023 14:41:21 -0300 Subject: [PATCH 01/22] wip: DuplicateMoveAggregate now calls DuplicateMove function and removes code duplication --- .../bim/module/geometry/operator.py | 75 +++++++------------ 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 157366aeb7..983b21513f 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -819,6 +819,7 @@ class OverrideDuplicateMove(bpy.types.Operator): # Recreate decompositions tool.Root.recreate_decompositions(relationships, old_to_new) blenderbim.bim.handler.refresh_ui_data() + return old_to_new @staticmethod def process_arrays(self, context): @@ -904,6 +905,8 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator): ### Adding the assembly data def add_assembly_data(element, parent, data_to_add): + obj = tool.Ifc.get_object(element) + obj.select_set(True) pset = ifcopenshell.util.element.get_pset(element, "BBIM_Aggregate_Data") data = [data_to_add] @@ -935,30 +938,14 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator): properties={"Parent": parent.GlobalId, "Data": json.dumps(data)}, ) - def add_child_to_assembly_data(new_entity): - pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Aggregate_Data") - parent_element = tool.Ifc.get().by_guid(pset["Parent"]) - parent_pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Aggregate_Data") - data = json.loads(parent_pset["Data"]) - data[0]["children"].append(new_entity.GlobalId) - - ifcopenshell.api.run( - "pset.edit_pset", - tool.Ifc.get(), - pset=tool.Ifc.get().by_id(parent_pset["id"]), - properties={"Data": json.dumps(data)}, - ) - def create_data_structure(entity, level=-1): level += 1 data_children = { - "children": [], "instance_of": [], } data_parent = { - "children": [], "instance_of": [entity.GlobalId], } @@ -973,11 +960,9 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator): for part in parts: if part.is_a("IfcElementAssembly"): add_assembly_data(part, entity, data_children) - add_child_to_assembly_data(part) create_data_structure(part, level) continue add_assembly_data(part, entity, data_children) - add_child_to_assembly_data(part) return @@ -1112,41 +1097,37 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator): return {"FINISHED"} selected_obj = context.selected_objects[0] - selected_root_entity = tool.Ifc.get_entity(selected_obj) + selected_element = tool.Ifc.get_entity(selected_obj) - if not selected_root_entity.is_a("IfcElementAssembly"): + if selected_element.is_a("IfcElementAssembly"): + pass + elif selected_element.Decomposes: + if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"): + selected_element = selected_element.Decomposes[0].RelatingObject + selected_obj = tool.Ifc.get_object(selected_element) + else: + self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") return {"FINISHED"} - pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data") + pset = ifcopenshell.util.element.get_pset(selected_element, "BBIM_Aggregate_Data") if not pset: - create_data_structure(selected_root_entity) + create_data_structure(selected_element) - pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data") + old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True) - selected_root_parent = selected_root_entity - - new_root_entity = duplicate_all(selected_obj) - - recreate_data_structure(new_root_entity) - - # Remove connections with old objects - for new in old_to_new.values(): - for connection in new[0].ConnectedTo: - entity = connection.RelatedElement - if entity in old_to_new.keys(): - core.remove_connection(tool.Geometry, connection=connection) - for connection in new[0].ConnectedFrom: - entity = connection.RelatingElement - if entity in old_to_new.keys(): - core.remove_connection(tool.Geometry, connection=connection) - - old_objs = [] - for old, new in old_to_new.items(): - old_objs.append(tool.Ifc.get_object(old)) - - relationships = tool.Root.get_decomposition_relationships(old_objs) - - tool.Root.recreate_decompositions(relationships, old_to_new) + for old_element, new_element in old_to_new.items(): + print(old_element, new_element) + old_parent = ifcopenshell.util.element.get_aggregate(old_element) + if old_parent: + new_parent = old_to_new[old_parent] + print(new_parent) + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(new_parent[0]), + related_obj=tool.Ifc.get_object(new_element[0]), + ) blenderbim.bim.handler.refresh_ui_data() From f5f9862b786bde09aa568fe1c70ba97c13a46abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 16 Oct 2023 15:31:56 -0300 Subject: [PATCH 02/22] Fix #2187. ifc_duplicate_operator now recreates the IfcElementAssembly structure into a new Assembly --- .../bim/module/geometry/operator.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index bad584b4da..e67ffc3f1e 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -806,6 +806,11 @@ class OverrideDuplicateMove(bpy.types.Operator): if new.is_a("IfcRelSpaceBoundary"): tool.Boundary.decorate_boundary(new_obj) + # Recreate assembly relationship + for old in old_to_new.keys(): + if old.is_a("IfcElementAssembly"): + OverrideDuplicateMove.recreate_assembly(old_to_new) + # Recreate decompositions tool.Root.recreate_decompositions(relationships, old_to_new) blenderbim.bim.handler.refresh_ui_data() @@ -846,6 +851,22 @@ class OverrideDuplicateMove(bpy.types.Operator): return arrays_to_create, array_children + @staticmethod + def recreate_assembly(old_to_new): + for old_element, new_element in old_to_new.items(): + print(old_element, new_element) + old_parent = ifcopenshell.util.element.get_aggregate(old_element) + if old_parent: + new_parent = old_to_new[old_parent] + print(new_parent) + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(new_parent[0]), + related_obj=tool.Ifc.get_object(new_element[0]), + ) + class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro): bl_idname = "bim.override_object_duplicate_move_linked_macro" From 0028fd3e9c9538f5e272ff715da9148a7359fba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 16 Oct 2023 15:39:38 -0300 Subject: [PATCH 03/22] ifc_duplicate_operator now remove the connections with old objects. Similar solutions presented in PR #3789 --- .../blenderbim/bim/module/geometry/operator.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index e67ffc3f1e..820456045a 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -811,6 +811,9 @@ class OverrideDuplicateMove(bpy.types.Operator): if old.is_a("IfcElementAssembly"): OverrideDuplicateMove.recreate_assembly(old_to_new) + # Remove connections with old objects + OverrideDuplicateMove.remove_old_connections(old_to_new) + # Recreate decompositions tool.Root.recreate_decompositions(relationships, old_to_new) blenderbim.bim.handler.refresh_ui_data() @@ -867,6 +870,17 @@ class OverrideDuplicateMove(bpy.types.Operator): related_obj=tool.Ifc.get_object(new_element[0]), ) + def remove_old_connections(old_to_new): + for new in old_to_new.values(): + for connection in new[0].ConnectedTo: + entity = connection.RelatedElement + if entity in old_to_new.keys(): + core.remove_connection(tool.Geometry, connection=connection) + for connection in new[0].ConnectedFrom: + entity = connection.RelatingElement + if entity in old_to_new.keys(): + core.remove_connection(tool.Geometry, connection=connection) + class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro): bl_idname = "bim.override_object_duplicate_move_linked_macro" From f68baee5c3d000d4e24cc43c7a836a3e64e4a9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 16 Oct 2023 22:17:22 -0300 Subject: [PATCH 04/22] small deletions --- src/blenderbim/blenderbim/bim/module/geometry/operator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 820456045a..3786206c47 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -857,11 +857,9 @@ class OverrideDuplicateMove(bpy.types.Operator): @staticmethod def recreate_assembly(old_to_new): for old_element, new_element in old_to_new.items(): - print(old_element, new_element) old_parent = ifcopenshell.util.element.get_aggregate(old_element) if old_parent: new_parent = old_to_new[old_parent] - print(new_parent) blenderbim.core.aggregate.assign_object( tool.Ifc, tool.Aggregate, From 86a234d17e5b7216d4c0b1bea4705a74b5085013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 16 Oct 2023 22:32:09 -0300 Subject: [PATCH 05/22] Changed operator name --- .../blenderbim/bim/module/geometry/__init__.py | 10 +++++----- .../blenderbim/bim/module/geometry/operator.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py index 29503856bc..5a93370d23 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py @@ -24,10 +24,10 @@ classes = ( operator.CopyRepresentation, operator.EditObjectPlacement, operator.GetRepresentationIfcParameters, + operator.DuplicateMoveLinkedAggregate, + operator.DuplicateMoveLinkedAggregateMacro, operator.OverrideDelete, operator.OverrideDuplicateMove, - operator.OverrideDuplicateMoveAggregate, - operator.OverrideDuplicateMoveAggregateMacro, operator.OverrideDuplicateMoveLinked, operator.OverrideDuplicateMoveLinkedMacro, operator.OverrideDuplicateMoveMacro, @@ -66,8 +66,8 @@ def register(): operator.OverrideDuplicateMoveMacro.define("TRANSFORM_OT_translate") operator.OverrideDuplicateMoveLinkedMacro.define("BIM_OT_override_object_duplicate_move_linked") operator.OverrideDuplicateMoveLinkedMacro.define("TRANSFORM_OT_translate") - operator.OverrideDuplicateMoveAggregateMacro.define("BIM_OT_override_object_duplicate_move_aggregate") - operator.OverrideDuplicateMoveAggregateMacro.define("TRANSFORM_OT_translate") + operator.DuplicateMoveLinkedAggregateMacro.define("BIM_OT_object_duplicate_move_linked_aggregate") + operator.DuplicateMoveLinkedAggregateMacro.define("TRANSFORM_OT_translate") bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties) bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties) @@ -85,7 +85,7 @@ def register(): addon_keymaps.append((km, kmi)) kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True) addon_keymaps.append((km, kmi)) - kmi = km.keymap_items.new("bim.override_object_duplicate_move_aggregate_macro", "D", "PRESS", ctrl=True, shift=True) + kmi = km.keymap_items.new("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_paste_buffer", "V", "PRESS", ctrl=True) addon_keymaps.append((km, kmi)) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 983b21513f..ccf4c7fd56 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -880,15 +880,15 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator): return OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True) -class OverrideDuplicateMoveAggregateMacro(bpy.types.Macro): - bl_idname = "bim.override_object_duplicate_move_aggregate_macro" - bl_label = "IFC Duplicate Objects Aggregate" +class DuplicateMoveLinkedAggregateMacro(bpy.types.Macro): + bl_idname = "bim.object_duplicate_move_linked_aggregate_macro" + bl_label = "IFC Duplicate Linked Aggregate" bl_options = {"REGISTER", "UNDO"} -class OverrideDuplicateMoveAggregate(bpy.types.Operator): - bl_idname = "bim.override_object_duplicate_move_aggregate" - bl_label = "IFC Duplicate Objects Aggregate" +class DuplicateMoveLinkedAggregate(bpy.types.Operator): + bl_idname = "bim.object_duplicate_move_linked_aggregate" + bl_label = "IFC Duplicate Linked Aggregate" bl_options = {"REGISTER", "UNDO"} is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True) From 37ab9c80e3bc34620e08199e9026344241bbd495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Tue, 17 Oct 2023 18:45:14 -0300 Subject: [PATCH 06/22] Duplication now recreates connections path #3770 --- .../bim/module/geometry/operator.py | 8 +++-- src/blenderbim/blenderbim/tool/root.py | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 3786206c47..1388111994 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -752,7 +752,8 @@ class OverrideDuplicateMove(bpy.types.Operator): self.new_active_obj = None # Track decompositions so they can be recreated after the operation - relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate) + decomposition_relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate) + connection_relationships = tool.Root.get_connection_relationships(objects_to_duplicate) old_to_new = {} for obj in objects_to_duplicate: @@ -811,11 +812,12 @@ class OverrideDuplicateMove(bpy.types.Operator): if old.is_a("IfcElementAssembly"): OverrideDuplicateMove.recreate_assembly(old_to_new) - # Remove connections with old objects + # Remove connections with old objects and recreates paths OverrideDuplicateMove.remove_old_connections(old_to_new) + tool.Root.recreate_connections(connection_relationships, old_to_new) # Recreate decompositions - tool.Root.recreate_decompositions(relationships, old_to_new) + tool.Root.recreate_decompositions(decomposition_relationships, old_to_new) blenderbim.bim.handler.refresh_ui_data() @staticmethod diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index ce198c9f00..0dc657353f 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -89,6 +89,26 @@ class Root(blenderbim.core.tool.Root): relationships[element] = {"type": "fill", "element": building} return relationships + @classmethod + def get_connection_relationships(cls, objs): + relationships = {} + for obj in objs: + element = tool.Ifc.get_entity(obj) + if not element: + continue + if hasattr(element, "ConnectedTo") and element.ConnectedTo: + paths = [connection for connection in element.ConnectedTo if connection.is_a("IfcRelConnectsPathElements")] + for path in paths: + relationships[element] = {"type": "path", + "related_connection_type": path.RelatedConnectionType, + "related_element": path.RelatedElement, + "related_priorities": path.RelatedPriorities, + "relating_connection_type": path.RelatingConnectionType, + "relating_element": path.RelatingElement, + "relating_priorities": path.RelatingPriorities, + } + return relationships + @classmethod def get_element_representation(cls, element, context): if context.is_a("IfcGeometricRepresentationSubContext"): @@ -197,6 +217,21 @@ class Root(blenderbim.core.tool.Root): should_sync_changes_first=False, ) + @classmethod + def recreate_connections(cls, relationship, old_to_new): + for element, data in relationship.items(): + new_relating_element = old_to_new.get(data["relating_element"])[0] + new_related_element = old_to_new.get(data["related_element"])[0] + ifcopenshell.api.run( + "geometry.connect_path", + tool.Ifc.get(), + relating_element=new_relating_element, + related_element=new_related_element, + relating_connection=data["relating_connection_type"], + related_connection=data["related_connection_type"], + ) + + @classmethod def run_geometry_add_representation( cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None From 1a3aa7f03105447bec0023b0b3526cbf1d858d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Wed, 18 Oct 2023 20:43:41 -0300 Subject: [PATCH 07/22] IfcElementAssembly share a group instead of a pset --- .../bim/module/geometry/operator.py | 244 ++++-------------- 1 file changed, 52 insertions(+), 192 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index ccf4c7fd56..8798875796 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -901,197 +901,60 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): def _execute(self, context): self.new_active_obj = None + self.group_name = "Linked Aggregate" old_to_new = {} - ### Adding the assembly data - def add_assembly_data(element, parent, data_to_add): - obj = tool.Ifc.get_object(element) - obj.select_set(True) - pset = ifcopenshell.util.element.get_pset(element, "BBIM_Aggregate_Data") + # TODO Maybe this will be unnecessary after Refresh refactor + def add_assembly_data(element): + + data_to_add = { + "instance_of": [element.GlobalId], + } data = [data_to_add] - if pset: - if parent == None: - ifcopenshell.api.run( - "pset.edit_pset", - tool.Ifc.get(), - pset=tool.Ifc.get().by_id(pset["id"]), - properties={"Data": json.dumps(data)}, - ) - else: - ifcopenshell.api.run( - "pset.edit_pset", - tool.Ifc.get(), - pset=tool.Ifc.get().by_id(pset["id"]), - properties={"Parent": parent.GlobalId, "Data": json.dumps(data)}, - ) - - else: - pset = ifcopenshell.api.run( - "pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Aggregate_Data" - ) - - ifcopenshell.api.run( - "pset.edit_pset", - tool.Ifc.get(), - pset=pset, - properties={"Parent": parent.GlobalId, "Data": json.dumps(data)}, - ) - - def create_data_structure(entity, level=-1): - level += 1 - - data_children = { - "instance_of": [], - } - - data_parent = { - "instance_of": [entity.GlobalId], - } - - if not entity.is_a("IfcElementAssembly"): - return - else: - if level == 0: - add_assembly_data(entity, entity, data_parent) - else: - add_assembly_data(entity, None, data_parent) - parts = ifcopenshell.util.element.get_parts(entity) - for part in parts: - if part.is_a("IfcElementAssembly"): - add_assembly_data(part, entity, data_children) - create_data_structure(part, level) - continue - add_assembly_data(part, entity, data_children) - - return - - def recreate_data_structure(entity, level=-1): - level += 1 - - pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data") - pset_data = json.loads(pset["Data"])[0] - instance_of = pset_data["instance_of"] - - data_children = { - "children": [], - "instance_of": [], - } - - # Keeps instance ID through all copies - data_parent = { - "children": [], - "instance_of": instance_of, - } - - if not entity.is_a("IfcElementAssembly"): - return - else: - if level == 0: - add_assembly_data(entity, entity, data_parent) - else: - add_assembly_data(entity, None, data_parent) - parts = ifcopenshell.util.element.get_parts(entity) - for part in parts: - if part.is_a("IfcElementAssembly"): - add_child_to_assembly_data(part) - recreate_data_structure(part, level) - continue - add_assembly_data(part, entity, data_children) - add_child_to_assembly_data(part) - - return - - def duplicate_all(obj, level=-1, new_parent=None, parents=[]): - level += 1 - - entity = tool.Ifc.get_entity(obj) - - if level == 0: - new_parent = duplicate_objects(obj) - parents.append(new_parent) - else: - pair = [] - pair.append(new_parent) - new_parent = duplicate_objects(obj) - pair.append(new_parent) - parents.append(pair) - - if not entity.is_a("IfcElementAssembly"): - return - else: - parts = ifcopenshell.util.element.get_parts(entity) - - # Ensures that we are duplication all the IfcElementAssembly - # before duplicating the nested objects - for part in parts: - if part.is_a("IfcElementAssembly"): - pass - else: - part_obj = tool.Ifc.get_object(part) - new_part = duplicate_objects(part_obj) - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(new_parent), - related_obj=tool.Ifc.get_object(new_part), - ) - - for part in parts: - if part.is_a("IfcElementAssembly"): - part_obj = tool.Ifc.get_object(part) - - # Recursion Call - duplicate_all(part_obj, level, new_parent, parents) - - if level == 0: - for p in parents[1:]: - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(p[0]), - related_obj=tool.Ifc.get_object(p[1]), - ) - - return new_parent - return - - def duplicate_objects(obj, is_root=False): - new_obj = obj.copy() - if obj.data: - new_obj.data = obj.data.copy() - if obj == context.active_object: - self.new_active_obj = new_obj - for collection in obj.users_collection: - collection.objects.link(new_obj) - obj.select_set(False) - new_obj.select_set(True) - - # This is needed to make sure the new object gets unlink from - # the old object assembly collection - new_obj.BIMObjectProperties.collection = None - - # Copy the actual class - new_entity = blenderbim.core.root.copy_class( - tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj + pset = ifcopenshell.api.run( + "pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Linked_Aggregate_Data" ) - if new_entity: - tool.Model.handle_array_on_copied_element(new_entity) - blenderbim.core.aggregate.unassign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(selected_root_entity), - related_obj=tool.Ifc.get_object(new_entity), - ) - - old_to_new[tool.Ifc.get_entity(obj)] = [new_entity] - - return new_entity + ifcopenshell.api.run( + "pset.edit_pset", + tool.Ifc.get(), + pset=pset, + properties={"Data": json.dumps(data)}, + ) + + def select_objects_and_add_data(element): + pset = ifcopenshell.util.element.get_pset(selected_element, "BBIM_Linked_Aggregate_Data") + if not pset: # TODO Is this still necessary? + add_assembly_data(element) + + add_linked_aggregate_group(element) + obj = tool.Ifc.get_object(element) + obj.select_set(True) + parts = ifcopenshell.util.element.get_parts(element) + if parts: + for part in parts: + if part.is_a("IfcElementAssembly"): + select_objects_and_add_data(part) + obj = tool.Ifc.get_object(part) + obj.select_set(True) + def add_linked_aggregate_group(element): + linked_aggregate_group = None + product_groups_name = [ + r.RelatingGroup.Name + for r in getattr(element, "HasAssignments", []) or [] + if r.is_a("IfcRelAssignsToGroup") + ] + if self.group_name in product_groups_name: + return + + linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name="Linked Aggregate") + ifcopenshell.api.run( + "group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group + ) + + ### Check if only one element and it's assembly if len(context.selected_objects) != 1: return {"FINISHED"} @@ -1109,18 +972,15 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") return {"FINISHED"} - pset = ifcopenshell.util.element.get_pset(selected_element, "BBIM_Aggregate_Data") - if not pset: - create_data_structure(selected_element) + select_objects_and_add_data(selected_element) old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True) + # TODO Test with a sub aggregate added later for old_element, new_element in old_to_new.items(): - print(old_element, new_element) old_parent = ifcopenshell.util.element.get_aggregate(old_element) if old_parent: new_parent = old_to_new[old_parent] - print(new_parent) blenderbim.core.aggregate.assign_object( tool.Ifc, tool.Aggregate, @@ -1134,9 +994,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): return {"FINISHED"} -class RefreshAggregate(bpy.types.Operator): - bl_idname = "bim.refresh_aggregate" - bl_label = "IFC Refresh Aggregate" +class RefreshLinkedAggregate(bpy.types.Operator): + bl_idname = "bim.refresh_linked_aggregate" + bl_label = "IFC Refresh Linked Aggregate" bl_options = {"REGISTER", "UNDO"} @classmethod From 66f735fde91460179b2de2c3028797c2322ad40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Wed, 18 Oct 2023 21:36:04 -0300 Subject: [PATCH 08/22] wip: refactor of RefreshLinkedAggregate. The operator triggers all the other linked aggregates deletion. --- .../bim/module/geometry/__init__.py | 2 +- .../bim/module/geometry/operator.py | 220 +++--------------- 2 files changed, 37 insertions(+), 185 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py index 5a93370d23..611795167b 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py @@ -39,7 +39,7 @@ classes = ( operator.OverrideOutlinerDelete, operator.OverridePasteBuffer, operator.PurgeUnusedRepresentations, - operator.RefreshAggregate, + operator.RefreshLinkedAggregate, operator.RemoveConnection, operator.RemoveRepresentation, operator.SelectConnection, diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 8798875796..767deeba9a 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -949,13 +949,12 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): if self.group_name in product_groups_name: return - linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name="Linked Aggregate") + linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name) ifcopenshell.api.run( "group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group ) - ### Check if only one element and it's assembly if len(context.selected_objects) != 1: return {"FINISHED"} @@ -976,7 +975,6 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True) - # TODO Test with a sub aggregate added later for old_element, new_element in old_to_new.items(): old_parent = ifcopenshell.util.element.get_aggregate(old_element) if old_parent: @@ -1012,208 +1010,62 @@ class RefreshLinkedAggregate(bpy.types.Operator): return {"FINISHED"} def _execute(self, context): - self.new_active_obj = None - old_to_new = {} + self.group_name = 'Linked Aggregate' - def remove_objects(entity, level=-1, parents=[]): - level += 1 - if level == 0: - parents = [entity] - parts = ifcopenshell.util.element.get_parts(entity) - for part in parts: - if part.is_a("IfcElementAssembly"): - parents.append(part) - remove_objects(part, level, parents) - continue - else: - part_obj = tool.Ifc.get_object(part) - if part_obj: - tool.Geometry.delete_ifc_object(part_obj) - - return parents - - def duplicate_children(entity): - pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data") - pset_data = json.loads(pset["Data"])[0] - instance_of = pset_data["instance_of"][0] - - instance_entity = tool.Ifc.get().by_guid(instance_of) - - if not instance_entity.is_a("IfcElementAssembly"): - return - else: - parts = ifcopenshell.util.element.get_parts(instance_entity) + def delete_objects(element): + parts = ifcopenshell.util.element.get_parts(element) + if parts: for part in parts: - pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data") if part.is_a("IfcElementAssembly"): - if not pset: - data_children = { - "children": [], - "instance_of": [part.GlobalId], - } - data = [data_children] - - pset = ifcopenshell.api.run( - "pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data" - ) - ifcopenshell.api.run( - "pset.edit_pset", - tool.Ifc.get(), - pset=pset, - properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)}, - ) - - part_obj = tool.Ifc.get_object(part) - new_part = duplicate_objects(part_obj) - - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(entity), - related_obj=tool.Ifc.get_object(new_part), - ) - duplicate_children(new_part) - - for part in parts: - pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data") - if part.is_a("IfcElementAssembly"): - pass - - else: - if not pset: - pset = ifcopenshell.api.run( - "pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data" - ) - else: - pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data") - pset = tool.Ifc.get().by_id(pset["id"]) - - data_children = { - "children": [], - "instance_of": [], - } - data = [data_children] + delete_objects(part) + tool.Geometry.delete_ifc_object(tool.Ifc.get_object(part)) - ifcopenshell.api.run( - "pset.edit_pset", - tool.Ifc.get(), - pset=pset, - properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)}, - ) - - part_obj = tool.Ifc.get_object(part) - new_part = duplicate_objects(part_obj) - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(entity), - related_obj=tool.Ifc.get_object(new_part), - ) - - def duplicate_objects(obj, is_root=False): - new_obj = obj.copy() - if obj.data: - new_obj.data = obj.data.copy() - if obj == context.active_object: - self.new_active_obj = new_obj - for collection in obj.users_collection: - collection.objects.link(new_obj) - obj.select_set(False) - new_obj.select_set(True) + tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element)) - # This is needed to make sure the new object gets unlink from - # the old object assembly collection - new_obj.BIMObjectProperties.collection = None - - # Copy the actual class - new_entity = blenderbim.core.root.copy_class( - tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj - ) - - if new_entity: - tool.Model.handle_array_on_copied_element(new_entity) - - if not new_entity.is_a("IfcElementAssembly"): - blenderbim.core.aggregate.unassign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=obj, - related_obj=tool.Ifc.get_object(new_entity), - ) - - old_to_new[tool.Ifc.get_entity(obj)] = [new_entity] - - return new_entity - + if len(context.selected_objects) != 1: - self.report({"INFO"}, "Only 1 object need to be selected.") return {"FINISHED"} - selected_root_obj = context.selected_objects[0] - selected_root_entity = tool.Ifc.get_entity(selected_root_obj) + selected_obj = context.selected_objects[0] + selected_element = tool.Ifc.get_entity(selected_obj) - if selected_root_entity.is_a("IfcElementAssembly"): + if selected_element.is_a("IfcElementAssembly"): pass - elif selected_root_entity.Decomposes: - if selected_root_entity.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"): - selected_root_entity = selected_root_entity.Decomposes[0].RelatingObject - selected_root_obj = tool.Ifc.get_object(selected_root_entity) + elif selected_element.Decomposes: + if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"): + selected_element = selected_element.Decomposes[0].RelatingObject + selected_obj = tool.Ifc.get_object(selected_element) else: self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") return {"FINISHED"} - - pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data") - if not pset: - self.report({"INFO"}, "Object is not part of an assembly aggregate.") - return {"FINISHED"} - - pset_data = json.loads(pset["Data"])[0] - instance_of = pset_data["instance_of"][0] - original_root_entity = tool.Ifc.get().by_guid(instance_of) - if original_root_entity == selected_root_entity: - self.report({"INFO"}, "Cannot refresh original assembly. Select an assembly instance.") - return {"FINISHED"} - - parents = remove_objects(selected_root_entity) - - original_root_object = tool.Ifc.get_object(original_root_entity) - - selected_matrix = selected_root_obj.matrix_world - original_matrix = original_root_object.matrix_world - - for parent in parents: - duplicate_children(parent) - + product_linked_agg_group = [ + r.RelatingGroup + for r in getattr(selected_element, "HasAssignments", []) or [] + if r.is_a("IfcRelAssignsToGroup") + if self.group_name in r.RelatingGroup.Name + ] + selection_group = product_linked_agg_group[0].id() - # Remove connections with old objects - for new in old_to_new.values(): - for connection in new[0].ConnectedTo: - entity = connection.RelatedElement - if entity in old_to_new.keys(): - core.remove_connection(tool.Geometry, connection=connection) - for connection in new[0].ConnectedFrom: - entity = connection.RelatingElement - if entity in old_to_new.keys(): - core.remove_connection(tool.Geometry, connection=connection) + elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(selection_group)) + for element in elements: + if element.GlobalId == selected_element.GlobalId: + continue + matrix = tool.Ifc.get_object(element).matrix_world + delete_objects(element) + # tool.Spatial.select_products(elements) - old_objs = [] - for old, new in old_to_new.items(): - old_objs.append(tool.Ifc.get_object(old)) + # DONE Select all Objects - new_obj = tool.Ifc.get_object(new[0]) + # DONE Store matrix - matrix_diff = Matrix.inverted(original_matrix) @ new_obj.matrix_world - new_matrix = selected_matrix @ matrix_diff + # DONE Delete Objects - new_obj.matrix_world = new_matrix + # Call a Function from DuplicateMoveLinkedAggregate + # Refactor the code into a function to be called - relationships = tool.Root.get_decomposition_relationships(old_objs) + # Old to New change matrix - tool.Root.recreate_decompositions(relationships, old_to_new) blenderbim.bim.handler.refresh_ui_data() From 5960e4153c9f220a6679735c205598dbe2e58d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Fri, 20 Oct 2023 14:34:03 -0300 Subject: [PATCH 09/22] wip: RefreshLinkedAggregate is working with IfcGroup --- .../bim/module/geometry/operator.py | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 767deeba9a..b46da77a53 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -900,6 +900,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False) def _execute(self, context): + return DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) + + def execute_ifc_duplicate_linked_aggregate_operator(self, context): self.new_active_obj = None self.group_name = "Linked Aggregate" old_to_new = {} @@ -989,7 +992,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): blenderbim.bim.handler.refresh_ui_data() - return {"FINISHED"} + return old_to_new class RefreshLinkedAggregate(bpy.types.Operator): @@ -1021,7 +1024,6 @@ class RefreshLinkedAggregate(bpy.types.Operator): tool.Geometry.delete_ifc_object(tool.Ifc.get_object(part)) tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element)) - if len(context.selected_objects) != 1: return {"FINISHED"} @@ -1051,21 +1053,33 @@ class RefreshLinkedAggregate(bpy.types.Operator): for element in elements: if element.GlobalId == selected_element.GlobalId: continue - matrix = tool.Ifc.get_object(element).matrix_world + + selected_matrix = selected_obj.matrix_world + object_duplicate = tool.Ifc.get_object(element) + duplicate_matrix = object_duplicate.matrix_world.decompose() + delete_objects(element) - # tool.Spatial.select_products(elements) + + for obj in context.selected_objects: + obj.select_set(False) + + tool.Ifc.get_object(selected_element).select_set(True) + + old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) + + for old, new in old_to_new.items(): + # if new[0].is_a("IfcColumn"): + new_obj = tool.Ifc.get_object(new[0]) + new_base_matrix = Matrix.LocRotScale(*duplicate_matrix) + matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world + new_obj_matrix = new_base_matrix @ matrix_diff + new_obj.matrix_world = new_obj_matrix - # DONE Select all Objects + # TODO test with adding subaggregates to existing aggregates - # DONE Store matrix - - # DONE Delete Objects - - # Call a Function from DuplicateMoveLinkedAggregate - # Refactor the code into a function to be called - - # Old to New change matrix + # TODO test with ctrl+shift+d in a subaggregates + # TODO think of more edge cases and issues already reported blenderbim.bim.handler.refresh_ui_data() From 4f1e59a6137e45b776034e5c59c095a4bb3a7766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Fri, 20 Oct 2023 21:26:56 -0300 Subject: [PATCH 10/22] improvements on dealing with sub-aggregates --- .../bim/module/geometry/operator.py | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index b46da77a53..553248f917 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -974,21 +974,32 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") return {"FINISHED"} + select_objects_and_add_data(selected_element) old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True) + # TODO check how this will interact with the new code from duplicate operator for old_element, new_element in old_to_new.items(): - old_parent = ifcopenshell.util.element.get_aggregate(old_element) - if old_parent: - new_parent = old_to_new[old_parent] - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(new_parent[0]), - related_obj=tool.Ifc.get_object(new_element[0]), - ) + old_aggregate = ifcopenshell.util.element.get_aggregate(old_element) + if old_aggregate: + if old_element.GlobalId == selected_element.GlobalId: + blenderbim.core.aggregate.unassign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(old_aggregate), + related_obj=tool.Ifc.get_object(new_element[0]), + ) + else: + new_aggregate = old_to_new[old_aggregate] + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(new_aggregate[0]), + related_obj=tool.Ifc.get_object(new_element[0]), + ) blenderbim.bim.handler.refresh_ui_data() @@ -1053,6 +1064,8 @@ class RefreshLinkedAggregate(bpy.types.Operator): for element in elements: if element.GlobalId == selected_element.GlobalId: continue + + element_aggregate = ifcopenshell.util.element.get_aggregate(element) selected_matrix = selected_obj.matrix_world object_duplicate = tool.Ifc.get_object(element) @@ -1066,25 +1079,36 @@ class RefreshLinkedAggregate(bpy.types.Operator): tool.Ifc.get_object(selected_element).select_set(True) old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) - for old, new in old_to_new.items(): - # if new[0].is_a("IfcColumn"): + new_obj = tool.Ifc.get_object(new[0]) new_base_matrix = Matrix.LocRotScale(*duplicate_matrix) matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world new_obj_matrix = new_base_matrix @ matrix_diff new_obj.matrix_world = new_obj_matrix - # TODO test with adding subaggregates to existing aggregates - - # TODO test with ctrl+shift+d in a subaggregates + + if element_aggregate and new[0].is_a("IfcElementAssembly"): + new_aggregate = ifcopenshell.util.element.get_aggregate(new[0]) + print("E-", element_aggregate) + print("A-", new_aggregate) + print("N-", new[0]) + if not new_aggregate: + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(element_aggregate), + related_obj=tool.Ifc.get_object(new[0]), + ) + + # TODO Add a "Mirror" option that treats the matrix differently + # TODO think of more edge cases and issues already reported blenderbim.bim.handler.refresh_ui_data() - return {"FINISHED"} - class OverrideJoin(bpy.types.Operator, Operator): bl_idname = "bim.override_object_join" From ef72cd32505b7ecae1efbe93d84669a6961cd4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 23 Oct 2023 15:46:14 -0300 Subject: [PATCH 11/22] recreates assembly for array children objects --- .../bim/module/geometry/operator.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 1388111994..67e3aa1512 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -858,17 +858,30 @@ class OverrideDuplicateMove(bpy.types.Operator): @staticmethod def recreate_assembly(old_to_new): - for old_element, new_element in old_to_new.items(): - old_parent = ifcopenshell.util.element.get_aggregate(old_element) - if old_parent: - new_parent = old_to_new[old_parent] + for old, new in old_to_new.items(): + old_aggregate = ifcopenshell.util.element.get_aggregate(old) + if old_aggregate: + new_aggregate = old_to_new[old_aggregate] blenderbim.core.aggregate.assign_object( tool.Ifc, tool.Aggregate, tool.Collector, - relating_obj=tool.Ifc.get_object(new_parent[0]), - related_obj=tool.Ifc.get_object(new_element[0]), + relating_obj=tool.Ifc.get_object(new_aggregate[0]), + related_obj=tool.Ifc.get_object(new[0]), ) + + # Make sure that the array children also get reassigned to the correct aggregate + pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Array") + if pset: + array_children = tool.Blender.Modifier.Array.get_all_children_objects(new[0]) + for obj in array_children: + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(new_aggregate[0]), + related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)), + ) def remove_old_connections(old_to_new): for new in old_to_new.values(): From af864e8556c5c6722c5486bbe37aa5e510e31029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 6 Nov 2023 12:15:31 -0300 Subject: [PATCH 12/22] added test scenario for duplicating an aggregate --- .../test/bim/feature/geometry.feature | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature index 206769fc64..db809402d2 100644 --- a/src/blenderbim/test/bim/feature/geometry.feature +++ b/src/blenderbim/test/bim/feature/geometry.feature @@ -418,6 +418,27 @@ Scenario: Override duplicate move - copying a profiled extrusion Then the object "IfcWall/Cube.001" exists Then the object "IfcWall/Cube.001" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" +Scenario: Override duplicate move - copying an aggregate + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + When I press "bim.add_aggregate" + Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly" + And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly" + And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey" + When the object "IfcWall/Cube" is selected + And additionally the object "IfcElementAssembly/Assembly" is selected + When I duplicate the selected objects + Then the object "IfcWall/Cube.001" exists + And the object "IfcWall/Cube.001" is in the collection "IfcElementAssembly/Assembly.001" + And the object "IfcElementAssembly/Assembly.001" exists + And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001" + And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcBuildingStorey/My Storey" + Scenario: Override duplicate move linked - without active IFC data Given an empty Blender session And I add a cube From 1e36ac8740a2a36762aa3c47335946df2b2f1f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 6 Nov 2023 21:38:06 -0300 Subject: [PATCH 13/22] removed the pset and added group to all elements --- .../bim/module/geometry/operator.py | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 553248f917..1f40dd7b98 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -907,30 +907,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): self.group_name = "Linked Aggregate" old_to_new = {} - # TODO Maybe this will be unnecessary after Refresh refactor - def add_assembly_data(element): - - data_to_add = { - "instance_of": [element.GlobalId], - } - data = [data_to_add] - - pset = ifcopenshell.api.run( - "pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Linked_Aggregate_Data" - ) - - ifcopenshell.api.run( - "pset.edit_pset", - tool.Ifc.get(), - pset=pset, - properties={"Data": json.dumps(data)}, - ) - def select_objects_and_add_data(element): - pset = ifcopenshell.util.element.get_pset(selected_element, "BBIM_Linked_Aggregate_Data") - if not pset: # TODO Is this still necessary? - add_assembly_data(element) - add_linked_aggregate_group(element) obj = tool.Ifc.get_object(element) obj.select_set(True) @@ -939,6 +916,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): for part in parts: if part.is_a("IfcElementAssembly"): select_objects_and_add_data(part) + add_linked_aggregate_group(part) obj = tool.Ifc.get_object(part) obj.select_set(True) @@ -1090,9 +1068,6 @@ class RefreshLinkedAggregate(bpy.types.Operator): if element_aggregate and new[0].is_a("IfcElementAssembly"): new_aggregate = ifcopenshell.util.element.get_aggregate(new[0]) - print("E-", element_aggregate) - print("A-", new_aggregate) - print("N-", new[0]) if not new_aggregate: blenderbim.core.aggregate.assign_object( tool.Ifc, @@ -1105,7 +1080,7 @@ class RefreshLinkedAggregate(bpy.types.Operator): # TODO Add a "Mirror" option that treats the matrix differently - # TODO think of more edge cases and issues already reported + # TODO Think of more edge cases and issues already reported blenderbim.bim.handler.refresh_ui_data() From 4bf0d9ccff41137b68ee01bd89a78fdb74e88816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Tue, 7 Nov 2023 18:19:42 -0300 Subject: [PATCH 14/22] added more tests --- .../test/bim/feature/geometry.feature | 56 +++++++++++++++++++ src/blenderbim/test/bim/test_feature.py | 25 +++++++++ 2 files changed, 81 insertions(+) diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature index db809402d2..163f0e5c7d 100644 --- a/src/blenderbim/test/bim/feature/geometry.feature +++ b/src/blenderbim/test/bim/feature/geometry.feature @@ -439,6 +439,62 @@ Scenario: Override duplicate move - copying an aggregate And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001" And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcBuildingStorey/My Storey" +Scenario: Override duplicate move - copying objects with connection + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + When I press "bim.add_constr_type_instance" + Then the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcWall/Wall" dimensions are "1,0.1,3" + And the object "IfcWall/Wall" bottom left corner is at "0,0,0" + When I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR150'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + When I press "bim.add_constr_type_instance" + Then the object "IfcSlab/Slab" is an "IfcSlab" + When the object "IfcSlab/Slab" is selected + And the object "IfcSlab/Slab" is moved to "0,0,4" + When I deselect all objects + And the object "IfcWall/Wall" is selected + And additionally the object "IfcSlab/Slab" is selected + When I press "bim.hotkey(hotkey='S_E')" + Then the object "IfcWall/Wall" dimensions are "1,0.1,4" + When I duplicate the selected objects + Then the object "IfcWall/Wall.001" exists + And the variable "wall_name" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.001'][0]" + Then the object "IfcSlab/Slab.001" exists + And the variable "slab_name" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcSlab/Slab.001'][0]" + Then the object "{wall_name}" has a connection with "{slab_name}" + +Scenario: Override duplicate move - copying walls with mitre joint + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.hotkey(hotkey='S_A')" + And the cursor is at "0.5,0,0" + And I press "bim.hotkey(hotkey='S_A')" + And the object "IfcWall/Wall" is selected + And additionally the object "IfcWall/Wall.001" is selected + When I press "bim.hotkey(hotkey='S_Y')" + Then the object "IfcWall/Wall.001" dimensions are "0.5,0.1,3" + And the object "IfcWall/Wall.001" bottom left corner is at "0.5,0,0" + And the object "IfcWall/Wall" dimensions are "1.1,0.1,3" + And the object "IfcWall/Wall" bottom left corner is at "0.5,0.1,0" + And the object "IfcWall/Wall" top right corner is at "0.6,-1,3" + When I deselect all objects + And the object "IfcWall/Wall" is selected + And additionally the object "IfcWall/Wall.001" is selected + When I duplicate the selected objects + Then the object "IfcWall/Wall.002" exists + And the variable "wall_name1" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.002'][0]" + Then the object "IfcWall/Wall.003" exists + And the variable "wall_name2" is "[o.name for o in bpy.context.selected_objects if o.name == 'IfcWall/Wall.003'][0]" + Then the object "{wall_name1}" has a connection with "{wall_name2}" + Scenario: Override duplicate move linked - without active IFC data Given an empty Blender session And I add a cube diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 9fd1c4317f..ae424f1444 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -837,3 +837,28 @@ def run_pdb(): import pdb pdb.set_trace() + +@then(parsers.parse('the object "{obj_name1}" has a connection with "{obj_name2}"')) +def the_obj1_has_a_connection_with_obj2(obj_name1, obj_name2): + element1 = replace_variables(obj_name1) + element1 = tool.Ifc.get_entity(the_object_name_exists(element1)) + element2 = replace_variables(obj_name2) + element2 = tool.Ifc.get_entity(the_object_name_exists(element2)) + connections = [] + if hasattr(element1, "ConnectedTo") and element1.ConnectedTo: + connections = [connection for connection in element1.ConnectedTo] + elif hasattr(element1, "ConnectedFrom") and element1.ConnectedFrom: + connections = [connection for connection in element1.ConnectedFrom] + else: + assert False, f'Object "{obj_name1}" has no connections' + + + relationships = {} + for conn in connections: + relationships[conn.RelatedElement] = conn.RelatingElement + + for key, value in relationships.items(): + print(key, value) + # assert False, f"1-{key} and {element1.id()} and {key.id()}" + assert (key.id() == element1.id() and value.id() == element2.id()) or (key.id() == element2.id() and value.id() == element1.id()), f"The object {obj_name1} is connected to {obj_name2}" + From 61bd98e7dbf8d5f05a9bffeebe26570f54e5f23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Tue, 7 Nov 2023 18:28:43 -0300 Subject: [PATCH 15/22] moved 'recreate_assembly' to tool/root.py --- .../bim/module/geometry/operator.py | 52 +++++++++---------- src/blenderbim/blenderbim/tool/root.py | 28 ++++++++++ 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 67e3aa1512..92627c7ebc 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -810,7 +810,7 @@ class OverrideDuplicateMove(bpy.types.Operator): # Recreate assembly relationship for old in old_to_new.keys(): if old.is_a("IfcElementAssembly"): - OverrideDuplicateMove.recreate_assembly(old_to_new) + tool.Root.recreate_assembly(old_to_new) # Remove connections with old objects and recreates paths OverrideDuplicateMove.remove_old_connections(old_to_new) @@ -856,32 +856,32 @@ class OverrideDuplicateMove(bpy.types.Operator): return arrays_to_create, array_children - @staticmethod - def recreate_assembly(old_to_new): - for old, new in old_to_new.items(): - old_aggregate = ifcopenshell.util.element.get_aggregate(old) - if old_aggregate: - new_aggregate = old_to_new[old_aggregate] - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(new_aggregate[0]), - related_obj=tool.Ifc.get_object(new[0]), - ) + # @staticmethod + # def recreate_assembly(old_to_new): + # for old, new in old_to_new.items(): + # old_aggregate = ifcopenshell.util.element.get_aggregate(old) + # if old_aggregate: + # new_aggregate = old_to_new[old_aggregate] + # blenderbim.core.aggregate.assign_object( + # tool.Ifc, + # tool.Aggregate, + # tool.Collector, + # relating_obj=tool.Ifc.get_object(new_aggregate[0]), + # related_obj=tool.Ifc.get_object(new[0]), + # ) - # Make sure that the array children also get reassigned to the correct aggregate - pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Array") - if pset: - array_children = tool.Blender.Modifier.Array.get_all_children_objects(new[0]) - for obj in array_children: - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(new_aggregate[0]), - related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)), - ) + # # Make sure that the array children also get reassigned to the correct aggregate + # pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Array") + # if pset: + # array_children = tool.Blender.Modifier.Array.get_all_children_objects(new[0]) + # for obj in array_children: + # blenderbim.core.aggregate.assign_object( + # tool.Ifc, + # tool.Aggregate, + # tool.Collector, + # relating_obj=tool.Ifc.get_object(new_aggregate[0]), + # related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)), + # ) def remove_old_connections(old_to_new): for new in old_to_new.values(): diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index 0dc657353f..364c01a276 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -230,6 +230,34 @@ class Root(blenderbim.core.tool.Root): relating_connection=data["relating_connection_type"], related_connection=data["related_connection_type"], ) + + + @classmethod + def recreate_assembly(cls, old_to_new): + for old, new in old_to_new.items(): + old_aggregate = ifcopenshell.util.element.get_aggregate(old) + if old_aggregate: + new_aggregate = old_to_new[old_aggregate] + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(new_aggregate[0]), + related_obj=tool.Ifc.get_object(new[0]), + ) + + # Make sure that the array children also get reassigned to the correct aggregate + pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Array") + if pset: + array_children = tool.Blender.Modifier.Array.get_all_children_objects(new[0]) + for obj in array_children: + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(new_aggregate[0]), + related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)), + ) @classmethod From 67be2e8ce87e3197e95bfbde47cc42a3cea5520b Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Wed, 15 Nov 2023 21:40:15 -0600 Subject: [PATCH 16/22] Refresh different types of aggregates at one time --- .../bim/module/geometry/operator.py | 115 +++++++++--------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index bf5ae488a0..08cd5d8b74 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1016,70 +1016,69 @@ class RefreshLinkedAggregate(bpy.types.Operator): tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element)) - if len(context.selected_objects) != 1: - return {"FINISHED"} + selected_objects = context.selected_objects - selected_obj = context.selected_objects[0] - selected_element = tool.Ifc.get_entity(selected_obj) + for selected_obj in selected_objects: + selected_element = tool.Ifc.get_entity(selected_obj) - if selected_element.is_a("IfcElementAssembly"): - pass - elif selected_element.Decomposes: - if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"): - selected_element = selected_element.Decomposes[0].RelatingObject - selected_obj = tool.Ifc.get_object(selected_element) - else: - self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") - return {"FINISHED"} + if selected_element.is_a("IfcElementAssembly"): + pass + elif selected_element.Decomposes: + if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"): + selected_element = selected_element.Decomposes[0].RelatingObject + selected_obj = tool.Ifc.get_object(selected_element) + else: + self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") + return {"FINISHED"} - product_linked_agg_group = [ - r.RelatingGroup - for r in getattr(selected_element, "HasAssignments", []) or [] - if r.is_a("IfcRelAssignsToGroup") - if self.group_name in r.RelatingGroup.Name - ] - selection_group = product_linked_agg_group[0].id() - - elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(selection_group)) - for element in elements: - if element.GlobalId == selected_element.GlobalId: - continue - - element_aggregate = ifcopenshell.util.element.get_aggregate(element) + product_linked_agg_group = [ + r.RelatingGroup + for r in getattr(selected_element, "HasAssignments", []) or [] + if r.is_a("IfcRelAssignsToGroup") + if self.group_name in r.RelatingGroup.Name + ] + selection_group = product_linked_agg_group[0].id() - selected_matrix = selected_obj.matrix_world - object_duplicate = tool.Ifc.get_object(element) - duplicate_matrix = object_duplicate.matrix_world.decompose() - - delete_objects(element) - - for obj in context.selected_objects: - obj.select_set(False) + elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(selection_group)) + for element in elements: + if element.GlobalId == selected_element.GlobalId: + continue + + element_aggregate = ifcopenshell.util.element.get_aggregate(element) - tool.Ifc.get_object(selected_element).select_set(True) + selected_matrix = selected_obj.matrix_world + object_duplicate = tool.Ifc.get_object(element) + duplicate_matrix = object_duplicate.matrix_world.decompose() + + delete_objects(element) + + for obj in context.selected_objects: + obj.select_set(False) + + tool.Ifc.get_object(selected_element).select_set(True) + + old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) + for old, new in old_to_new.items(): + + new_obj = tool.Ifc.get_object(new[0]) + new_base_matrix = Matrix.LocRotScale(*duplicate_matrix) + matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world + new_obj_matrix = new_base_matrix @ matrix_diff + new_obj.matrix_world = new_obj_matrix + + + if element_aggregate and new[0].is_a("IfcElementAssembly"): + new_aggregate = ifcopenshell.util.element.get_aggregate(new[0]) + if not new_aggregate: + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(element_aggregate), + related_obj=tool.Ifc.get_object(new[0]), + ) + - old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) - for old, new in old_to_new.items(): - - new_obj = tool.Ifc.get_object(new[0]) - new_base_matrix = Matrix.LocRotScale(*duplicate_matrix) - matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world - new_obj_matrix = new_base_matrix @ matrix_diff - new_obj.matrix_world = new_obj_matrix - - - if element_aggregate and new[0].is_a("IfcElementAssembly"): - new_aggregate = ifcopenshell.util.element.get_aggregate(new[0]) - if not new_aggregate: - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(element_aggregate), - related_obj=tool.Ifc.get_object(new[0]), - ) - - # TODO Add a "Mirror" option that treats the matrix differently # TODO Think of more edge cases and issues already reported From f96d477d2bfc29d64baef7a2ca138ce37f032cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 20 Nov 2023 16:23:34 -0300 Subject: [PATCH 17/22] Groups are only added to the parent. Parts now have a pset with index --- .../bim/module/geometry/operator.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index bf5ae488a0..d92d333ac2 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -903,7 +903,8 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): def execute_ifc_duplicate_linked_aggregate_operator(self, context): self.new_active_obj = None - self.group_name = "Linked Aggregate" + self.group_name = "BBIM_Linked_Aggregate" + self.pset_name = "BBIM_Linked_Aggregate" old_to_new = {} def select_objects_and_add_data(element): @@ -912,10 +913,14 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): obj.select_set(True) parts = ifcopenshell.util.element.get_parts(element) if parts: + index = 0 for part in parts: if part.is_a("IfcElementAssembly"): select_objects_and_add_data(part) - add_linked_aggregate_group(part) + + add_linked_aggregate_pset(part, index) + index += 1 + obj = tool.Ifc.get_object(part) obj.select_set(True) @@ -933,6 +938,25 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): ifcopenshell.api.run( "group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group ) + + def add_linked_aggregate_pset(part, index): + pset = ifcopenshell.util.element.get_pset(part, self.pset_name) + + if not pset: + pset = ifcopenshell.api.run( + "pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name + ) + else: + pset = tool.Ifc.get().by_id(pset["id"]) + + ifcopenshell.api.run( + "pset.edit_pset", + tool.Ifc.get(), + pset=pset, + properties={"Index": index}, + ) + + return index if len(context.selected_objects) != 1: @@ -1001,9 +1025,10 @@ class RefreshLinkedAggregate(bpy.types.Operator): return {"FINISHED"} def _execute(self, context): - self.group_name = 'Linked Aggregate' - refresh_start_time = time() self.new_active_obj = None + self.group_name = 'BBIM_Linked_Aggregate' + self.pset_name = "BBIM_Linked_Aggregate" + refresh_start_time = time() old_to_new = {} def delete_objects(element): @@ -1012,6 +1037,7 @@ class RefreshLinkedAggregate(bpy.types.Operator): for part in parts: if part.is_a("IfcElementAssembly"): delete_objects(part) + tool.Geometry.delete_ifc_object(tool.Ifc.get_object(part)) tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element)) From 52036e9bd40208d31c2b69cdce3d07b3c976c4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 20 Nov 2023 16:38:10 -0300 Subject: [PATCH 18/22] merged the branch 'improvements-on-override-duplicat' --- .../bim/module/geometry/operator.py | 53 +++---------------- 1 file changed, 6 insertions(+), 47 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 2137e742d5..60a5f2b077 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -866,32 +866,6 @@ class OverrideDuplicateMove(bpy.types.Operator): return arrays_to_create, array_children - # @staticmethod - # def recreate_assembly(old_to_new): - # for old, new in old_to_new.items(): - # old_aggregate = ifcopenshell.util.element.get_aggregate(old) - # if old_aggregate: - # new_aggregate = old_to_new[old_aggregate] - # blenderbim.core.aggregate.assign_object( - # tool.Ifc, - # tool.Aggregate, - # tool.Collector, - # relating_obj=tool.Ifc.get_object(new_aggregate[0]), - # related_obj=tool.Ifc.get_object(new[0]), - # ) - - # # Make sure that the array children also get reassigned to the correct aggregate - # pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Array") - # if pset: - # array_children = tool.Blender.Modifier.Array.get_all_children_objects(new[0]) - # for obj in array_children: - # blenderbim.core.aggregate.assign_object( - # tool.Ifc, - # tool.Aggregate, - # tool.Collector, - # relating_obj=tool.Ifc.get_object(new_aggregate[0]), - # related_obj=tool.Ifc.get_object(tool.Ifc.get_entity(obj)), - # ) def remove_old_connections(old_to_new): for new in old_to_new.values(): @@ -1028,27 +1002,12 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True) - # TODO check how this will interact with the new code from duplicate operator - for old_element, new_element in old_to_new.items(): - old_aggregate = ifcopenshell.util.element.get_aggregate(old_element) - if old_aggregate: - if old_element.GlobalId == selected_element.GlobalId: - blenderbim.core.aggregate.unassign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(old_aggregate), - related_obj=tool.Ifc.get_object(new_element[0]), - ) - else: - new_aggregate = old_to_new[old_aggregate] - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(new_aggregate[0]), - related_obj=tool.Ifc.get_object(new_element[0]), - ) + + # Recreate assembly relationship + for old in old_to_new.keys(): + if old.is_a("IfcElementAssembly"): + tool.Root.recreate_assembly(old_to_new) + blenderbim.bim.handler.refresh_ui_data() From fd65009f5a18d1477fc9ba4788165a0f76e71804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 20 Nov 2023 18:34:46 -0300 Subject: [PATCH 19/22] Refresh Linked Aggregate now handles multiple selection --- .../bim/module/geometry/operator.py | 80 +++++++++++-------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 60a5f2b077..5b31445fda 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1048,38 +1048,59 @@ class RefreshLinkedAggregate(bpy.types.Operator): tool.Geometry.delete_ifc_object(tool.Ifc.get_object(part)) tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element)) + + def get_element_assembly(element): + if element.is_a("IfcElementAssembly"): + return element + elif element.Decomposes: + if element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"): + element = element.Decomposes[0].RelatingObject + return element + else: + return None - if len(context.selected_objects) != 1: + + selected_objs = context.selected_objects + selected_elements = [tool.Ifc.get_entity(selected_obj) for selected_obj in selected_objs] + active_element = tool.Ifc.get_entity(context.active_object) + active_element = get_element_assembly(active_element) + + working_selection = [] + for selected_element in selected_elements: + selected_element = get_element_assembly(selected_element) + if not selected_element: + self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") + return {'FINISHED'} + working_selection.append(selected_element) + + working_selection = list(set(working_selection)) + working_ids = [e.id() for e in working_selection] + selection_group = [] + for selected_element in working_selection: + product_linked_agg_group = [ + r.RelatingGroup + for r in getattr(selected_element, "HasAssignments", []) or [] + if r.is_a("IfcRelAssignsToGroup") + if self.group_name in r.RelatingGroup.Name + ] + selection_group.append(product_linked_agg_group[0].id()) + + if len(set(selection_group))> 1: + self.report({"INFO"}, "Objects are not part of the same Linked Aggregate") return {"FINISHED"} - - selected_obj = context.selected_objects[0] - selected_element = tool.Ifc.get_entity(selected_obj) - - if selected_element.is_a("IfcElementAssembly"): - pass - elif selected_element.Decomposes: - if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"): - selected_element = selected_element.Decomposes[0].RelatingObject - selected_obj = tool.Ifc.get_object(selected_element) - else: - self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") - return {"FINISHED"} - - product_linked_agg_group = [ - r.RelatingGroup - for r in getattr(selected_element, "HasAssignments", []) or [] - if r.is_a("IfcRelAssignsToGroup") - if self.group_name in r.RelatingGroup.Name - ] - selection_group = product_linked_agg_group[0].id() - elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(selection_group)) + elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(selection_group[0])) + for element in elements: - if element.GlobalId == selected_element.GlobalId: + if element.GlobalId == active_element.GlobalId: + continue + + if (len(working_selection) > 1) and not (element.id() in working_ids): continue element_aggregate = ifcopenshell.util.element.get_aggregate(element) - + + selected_obj = tool.Ifc.get_object(active_element) selected_matrix = selected_obj.matrix_world object_duplicate = tool.Ifc.get_object(element) duplicate_matrix = object_duplicate.matrix_world.decompose() @@ -1089,7 +1110,7 @@ class RefreshLinkedAggregate(bpy.types.Operator): for obj in context.selected_objects: obj.select_set(False) - tool.Ifc.get_object(selected_element).select_set(True) + tool.Ifc.get_object(active_element).select_set(True) old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) for old, new in old_to_new.items(): @@ -1111,12 +1132,7 @@ class RefreshLinkedAggregate(bpy.types.Operator): relating_obj=tool.Ifc.get_object(element_aggregate), related_obj=tool.Ifc.get_object(new[0]), ) - - - # TODO Add a "Mirror" option that treats the matrix differently - - # TODO Think of more edge cases and issues already reported - + blenderbim.bim.handler.refresh_ui_data() operator_time = time() - refresh_start_time From 8e64b552902bbffcda1f857b0e8860730e7ed17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 20 Nov 2023 22:05:41 -0300 Subject: [PATCH 20/22] bugfix in recreate_aggregate --- .../blenderbim/bim/module/geometry/operator.py | 9 ++++----- src/blenderbim/blenderbim/tool/root.py | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 64309df049..f2660e358a 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -816,10 +816,10 @@ class OverrideDuplicateMove(bpy.types.Operator): if new.is_a("IfcRelSpaceBoundary"): tool.Boundary.decorate_boundary(new_obj) - # Recreate assembly relationship + # Recreate aggregate relationship for old in old_to_new.keys(): if old.is_a("IfcElementAssembly"): - tool.Root.recreate_assembly(old_to_new) + tool.Root.recreate_aggregate(old_to_new) # Remove connections with old objects and recreates paths OverrideDuplicateMove.remove_old_connections(old_to_new) @@ -1003,10 +1003,10 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True) - # Recreate assembly relationship + # Recreate aggregate relationship for old in old_to_new.keys(): if old.is_a("IfcElementAssembly"): - tool.Root.recreate_assembly(old_to_new) + tool.Root.recreate_aggregate(old_to_new) blenderbim.bim.handler.refresh_ui_data() @@ -1082,7 +1082,6 @@ class RefreshLinkedAggregate(bpy.types.Operator): if r.is_a("IfcRelAssignsToGroup") if self.group_name in r.RelatingGroup.Name ] -<<<<<<< HEAD selection_group.append(product_linked_agg_group[0].id()) if len(set(selection_group))> 1: diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index 364c01a276..fe2fa6289f 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -233,11 +233,24 @@ class Root(blenderbim.core.tool.Root): @classmethod - def recreate_assembly(cls, old_to_new): + def recreate_aggregate(cls, old_to_new): + print("O_T_N", old_to_new) for old, new in old_to_new.items(): old_aggregate = ifcopenshell.util.element.get_aggregate(old) + print("Old", old_aggregate) if old_aggregate: - new_aggregate = old_to_new[old_aggregate] + try: + new_aggregate = old_to_new[old_aggregate] + except: + blenderbim.core.aggregate.unassign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(old_aggregate), + related_obj=tool.Ifc.get_object(new[0]), + ) + continue + blenderbim.core.aggregate.assign_object( tool.Ifc, tool.Aggregate, From b8157194771f301693a2443468c876359a2af93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Thu, 23 Nov 2023 10:27:54 -0300 Subject: [PATCH 21/22] added the option to refresh different groups of linked aggregate --- .../bim/module/geometry/operator.py | 139 ++++++++++-------- src/blenderbim/blenderbim/tool/root.py | 2 - 2 files changed, 77 insertions(+), 64 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index f2660e358a..fedf7ffa86 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -923,6 +923,7 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): def _execute(self, context): return DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) + @staticmethod def execute_ifc_duplicate_linked_aggregate_operator(self, context): self.new_active_obj = None self.group_name = "BBIM_Linked_Aggregate" @@ -1058,79 +1059,93 @@ class RefreshLinkedAggregate(bpy.types.Operator): return element else: return None + + def handle_selection(selected_objs): + selected_elements = [tool.Ifc.get_entity(selected_obj) for selected_obj in selected_objs] + + selected_parents = [] + for selected_element in selected_elements: + selected_element = get_element_assembly(selected_element) + if not selected_element: + self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") + return {'FINISHED'} + selected_parents.append(selected_element) + + selected_parents = list(set(selected_parents)) + linked_aggregate_groups = [] + for selected_element in selected_parents: + product_linked_agg_group = [ + r.RelatingGroup + for r in getattr(selected_element, "HasAssignments", []) or [] + if r.is_a("IfcRelAssignsToGroup") + if self.group_name in r.RelatingGroup.Name + ] + linked_aggregate_groups.append(product_linked_agg_group[0].id()) + + return list(set(linked_aggregate_groups)), selected_parents + - selected_objs = context.selected_objects - selected_elements = [tool.Ifc.get_entity(selected_obj) for selected_obj in selected_objs] active_element = tool.Ifc.get_entity(context.active_object) active_element = get_element_assembly(active_element) + selected_objs = context.selected_objects + linked_aggregate_groups, selected_parents = handle_selection(selected_objs) - working_selection = [] - for selected_element in selected_elements: - selected_element = get_element_assembly(selected_element) - if not selected_element: - self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") - return {'FINISHED'} - working_selection.append(selected_element) + if len(linked_aggregate_groups) > 1: + if len(selected_parents) != len(linked_aggregate_groups): + self.report({"INFO"}, "Select only one object from each Linked Aggregate or multiple objects from the same Linked Aggregate.") + return {"FINISHED"} - working_selection = list(set(working_selection)) - working_ids = [e.id() for e in working_selection] - selection_group = [] - for selected_element in working_selection: - product_linked_agg_group = [ - r.RelatingGroup - for r in getattr(selected_element, "HasAssignments", []) or [] - if r.is_a("IfcRelAssignsToGroup") - if self.group_name in r.RelatingGroup.Name - ] - selection_group.append(product_linked_agg_group[0].id()) - - if len(set(selection_group))> 1: - self.report({"INFO"}, "Objects are not part of the same Linked Aggregate") - return {"FINISHED"} + for group in linked_aggregate_groups: + elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(group)) + if len(linked_aggregate_groups) > 1: + base_instance = [e for e in elements if e in selected_parents][0] + instances_to_refresh = elements + + elif (len(linked_aggregate_groups) == 1) and (len(selected_parents) > 1): + base_instance = active_element + instances_to_refresh = [element for element in elements if element in selected_parents] + + else: + base_instance = active_element + instances_to_refresh = elements - elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(selection_group[0])) - - for element in elements: - if element.GlobalId == active_element.GlobalId: - continue - - if (len(working_selection) > 1) and not (element.id() in working_ids): - continue + for element in instances_to_refresh: + if element.GlobalId == base_instance.GlobalId: + continue - element_aggregate = ifcopenshell.util.element.get_aggregate(element) + element_aggregate = ifcopenshell.util.element.get_aggregate(element) - selected_obj = tool.Ifc.get_object(active_element) - selected_matrix = selected_obj.matrix_world - object_duplicate = tool.Ifc.get_object(element) - duplicate_matrix = object_duplicate.matrix_world.decompose() - - delete_objects(element) - - for obj in context.selected_objects: - obj.select_set(False) + selected_obj = tool.Ifc.get_object(base_instance) + selected_matrix = selected_obj.matrix_world + object_duplicate = tool.Ifc.get_object(element) + duplicate_matrix = object_duplicate.matrix_world.decompose() - tool.Ifc.get_object(active_element).select_set(True) + delete_objects(element) - old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) - for old, new in old_to_new.items(): + for obj in context.selected_objects: + obj.select_set(False) - new_obj = tool.Ifc.get_object(new[0]) - new_base_matrix = Matrix.LocRotScale(*duplicate_matrix) - matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world - new_obj_matrix = new_base_matrix @ matrix_diff - new_obj.matrix_world = new_obj_matrix - - - if element_aggregate and new[0].is_a("IfcElementAssembly"): - new_aggregate = ifcopenshell.util.element.get_aggregate(new[0]) - if not new_aggregate: - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=tool.Ifc.get_object(element_aggregate), - related_obj=tool.Ifc.get_object(new[0]), - ) + tool.Ifc.get_object(base_instance).select_set(True) + + old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) + for old, new in old_to_new.items(): + new_obj = tool.Ifc.get_object(new[0]) + new_base_matrix = Matrix.LocRotScale(*duplicate_matrix) + matrix_diff = Matrix.inverted(selected_matrix) @ new_obj.matrix_world + new_obj_matrix = new_base_matrix @ matrix_diff + new_obj.matrix_world = new_obj_matrix + + if element_aggregate and new[0].is_a("IfcElementAssembly"): + new_aggregate = ifcopenshell.util.element.get_aggregate(new[0]) + + if not new_aggregate: + blenderbim.core.aggregate.assign_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + relating_obj=tool.Ifc.get_object(element_aggregate), + related_obj=tool.Ifc.get_object(new[0]), + ) blenderbim.bim.handler.refresh_ui_data() diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index fe2fa6289f..2de7f93c5f 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -234,10 +234,8 @@ class Root(blenderbim.core.tool.Root): @classmethod def recreate_aggregate(cls, old_to_new): - print("O_T_N", old_to_new) for old, new in old_to_new.items(): old_aggregate = ifcopenshell.util.element.get_aggregate(old) - print("Old", old_aggregate) if old_aggregate: try: new_aggregate = old_to_new[old_aggregate] From d23e32c64afcdd90fee77fbe11dfeddc51e659c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Thu, 23 Nov 2023 10:57:17 -0300 Subject: [PATCH 22/22] added report messages --- .../blenderbim/bim/module/geometry/operator.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index fedf7ffa86..437fc615bd 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1062,13 +1062,16 @@ class RefreshLinkedAggregate(bpy.types.Operator): def handle_selection(selected_objs): selected_elements = [tool.Ifc.get_entity(selected_obj) for selected_obj in selected_objs] + if None in selected_elements: + self.report({"INFO"}, "Object has no Ifc Metadata.") + return None, None selected_parents = [] for selected_element in selected_elements: selected_element = get_element_assembly(selected_element) if not selected_element: self.report({"INFO"}, "Object is not part of a IfcElementAssembly.") - return {'FINISHED'} + return None, None selected_parents.append(selected_element) selected_parents = list(set(selected_parents)) @@ -1080,15 +1083,24 @@ class RefreshLinkedAggregate(bpy.types.Operator): if r.is_a("IfcRelAssignsToGroup") if self.group_name in r.RelatingGroup.Name ] - linked_aggregate_groups.append(product_linked_agg_group[0].id()) + try: + linked_aggregate_groups.append(product_linked_agg_group[0].id()) + except: + self.report({"INFO"}, "Object is not part of a Linked Aggregate.") + return None, None return list(set(linked_aggregate_groups)), selected_parents - active_element = tool.Ifc.get_entity(context.active_object) + if not active_element: + self.report({"INFO"}, "Object has no Ifc metadata.") + return {"FINISHED"} + active_element = get_element_assembly(active_element) selected_objs = context.selected_objects linked_aggregate_groups, selected_parents = handle_selection(selected_objs) + if not linked_aggregate_groups or not selected_parents: + return {"FINISHED"} if len(linked_aggregate_groups) > 1: if len(selected_parents) != len(linked_aggregate_groups):