From b58c25fdc96eba4b05cd91b0d4983aff521458ab Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 22 Sep 2023 15:24:33 +0500 Subject: [PATCH] Duplicate the array if all it's elements are selected #3763 Demo - https://imgur.com/a/0wYZOPl --- .../bim/module/geometry/operator.py | 68 ++++++++++++++++--- src/blenderbim/blenderbim/tool/model.py | 61 ++++++++++++----- 2 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 272b6706f0..23e620f7a4 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -553,7 +553,8 @@ class OverrideDelete(bpy.types.Operator): with context.temp_override(active_object=array_parent_obj): bpy.ops.bim.remove_array(item=i) else: - break + break # allows to remove only n last layers of an array + class OverrideOutlinerDelete(bpy.types.Operator): bl_idname = "bim.override_outliner_delete" @@ -702,11 +703,20 @@ class OverrideDuplicateMove(bpy.types.Operator): return {"FINISHED"} def _execute(self, context): + objects_to_duplicate = set(context.selected_objects) + + # handle arrays + arrays_to_duplicate, array_children = self.process_arrays(context) + objects_to_duplicate -= array_children + for child in array_children: + child.select_set(False) + self.new_active_obj = None # Track decompositions so they can be recreated after the operation - relationships = tool.Root.get_decomposition_relationships(context.selected_objects) + relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate) old_to_new = {} - for obj in context.selected_objects: + + for obj in objects_to_duplicate: element = tool.Ifc.get_entity(obj) if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING": continue # For now, don't copy drawings until we stabilise a bit more. It's tricky. @@ -737,14 +747,56 @@ class OverrideDuplicateMove(bpy.types.Operator): tool.Blender.remove_data_block(temp_data) if new: - tool.Model.remove_array_from_element(new) - old_to_new[tool.Ifc.get_entity(obj)] = [new] + # TODO: handle array data for other cases of duplication + array_data = arrays_to_duplicate.get(obj, None) + tool.Model.handle_array_on_copied_element(new, array_data) + if array_data: + for child in tool.Blender.Modifier.Array.get_all_children_objects(new): + child.select_set(True) + + # TODO: add new array children to recreate their decomposition too + old_to_new[element] = [new] if new.is_a("IfcRelSpaceBoundary"): tool.Boundary.decorate_boundary(new_obj) + # Recreate decompositions tool.Root.recreate_decompositions(relationships, old_to_new) blenderbim.bim.handler.refresh_ui_data() + def process_arrays(self, context): + selected_objects = set(context.selected_objects) + array_parents = set() + arrays_to_create = dict() + array_children = set() # will be ignored during the duplication + + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + continue + pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") + if not pset: + continue + array_parents.add(tool.Ifc.get().by_guid(pset["Parent"])) + + for array_parent in array_parents: + array_parent_obj = tool.Ifc.get_object(array_parent) + if array_parent_obj not in selected_objects: + continue + + array_data = [] + for modifier_data in tool.Blender.Modifier.Array.get_modifiers_data(array_parent): + children = set(tool.Blender.Modifier.Array.get_children_objects(modifier_data)) + if children.issubset(selected_objects): + modifier_data["children"] = [] + array_data.append(modifier_data) + array_children.update(children) + else: + break # allows to duplicate only n first layers of an array + + if array_data: + arrays_to_create[array_parent_obj] = array_data + + return arrays_to_create, array_children class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro): bl_idname = "bim.override_object_duplicate_move_linked_macro" @@ -804,7 +856,7 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator): # Copy the actual class new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj) if new: - tool.Model.remove_array_from_element(new) + tool.Model.handle_array_on_copied_element(new) old_to_new[tool.Ifc.get_entity(obj)] = new # Recreate decompositions tool.Root.recreate_decompositions(relationships, old_to_new) @@ -1048,7 +1100,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator): ) if new_entity: - tool.Model.remove_array_from_element(new_entity) + tool.Model.handle_array_on_copied_element(new_entity) blenderbim.core.aggregate.unassign_object( tool.Ifc, tool.Aggregate, @@ -1176,7 +1228,7 @@ class RefreshAggregate(bpy.types.Operator): ) if new_entity: - tool.Model.remove_array_from_element(new_entity) + tool.Model.handle_array_on_copied_element(new_entity) blenderbim.core.aggregate.unassign_object( tool.Ifc, tool.Aggregate, diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py index 58f0826a1c..e2a2c29773 100644 --- a/src/blenderbim/blenderbim/tool/model.py +++ b/src/blenderbim/blenderbim/tool/model.py @@ -540,29 +540,56 @@ class Model(blenderbim.core.tool.Model): return axes @classmethod - def remove_array_from_element(cls, element): - array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") - if not array_pset: - return + def handle_array_on_copied_element(cls, element, array_data=None): + """if no `array_data` is provided then an array will be removed from the element""" - array_pset_data = array_pset["Data"] - array_pset = tool.Ifc.get().by_id(array_pset["id"]) - ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=array_pset) + if array_data is None: + array_pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") + if not array_pset: + return - # remove constraints - obj = tool.Ifc.get_object(element) - if not array_pset_data and ( - constraint := next((c for c in obj.constraints if c.type == "CHILD_OF"), None) - ): # skip array parents - obj.constraints.remove(constraint) - tool.Blender.lock_transform(obj, False) + array_pset_data = array_pset["Data"] + array_pset = tool.Ifc.get().by_id(array_pset["id"]) + ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=array_pset) + + # remove constraints + obj = tool.Ifc.get_object(element) + if not array_pset_data: # skip array parents + constraint = next((c for c in obj.constraints if c.type == "CHILD_OF"), None) + if constraint: + matrix = obj.matrix_world.copy() + obj.constraints.remove(constraint) + # keep the matrix before the constraint + # otherwise object will jump to some previous position + obj.matrix_world = matrix + tool.Blender.lock_transform(obj, False) + + else: + obj = tool.Ifc.get_object(element) + array_pset = tool.Pset.get_element_pset(element, "BBIM_Array") + default_data = '[{"children": []}]' + ifcopenshell.api.run( + "pset.edit_pset", + tool.Ifc.get(), + pset=array_pset, + properties={"Parent": element.GlobalId, "Data": default_data}, + ) + + tool.Model.regenerate_array(obj, array_data) + + json_data = json.dumps(array_data) + ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=array_pset, properties={"Data": json_data}) + + for i in range(len(array_data)): + tool.Blender.Modifier.Array.set_children_lock_state(element, i, True) + tool.Blender.Modifier.Array.constrain_children_to_parent(element) @classmethod - def regenerate_array(cls, parent, data, keep_objs=False): - tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent)) + def regenerate_array(cls, parent_obj, data, keep_objs=False): + tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent_obj)) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - obj_stack = [parent] + obj_stack = [parent_obj] for array in data: if array["sync_children"]: