From e51d2daf64412c87ceb4415c3c59bbca599c4806 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 30 Nov 2023 16:24:39 +0500 Subject: [PATCH] Fixed issues applying arrays because pset wasn't removed BBIM was recognizing those elements as parts of an array resulting in issues during deletion and duplication. This issue is a regression after ec4f0e5, the root problem was that `keep_objs` wasn't working with `sync_children`. Since `keep_objs` was used only for applying array layers I've replaced it with `array_layers_to_apply` that works for both `sync_children` = True and False. https://community.osarch.org/discussion/1831/duplicating-failed-for-array-items-after-apply-array --- .../blenderbim/bim/module/model/array.py | 2 +- src/blenderbim/blenderbim/tool/model.py | 39 +++++++--- src/blenderbim/test/tool/test_model.py | 78 +++++++++++++++++++ 3 files changed, 106 insertions(+), 13 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/array.py b/src/blenderbim/blenderbim/bim/module/model/array.py index 55820b74a1..4e51931479 100644 --- a/src/blenderbim/blenderbim/bim/module/model/array.py +++ b/src/blenderbim/blenderbim/bim/module/model/array.py @@ -189,7 +189,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator): if not self.keep_objs: data[self.item]["count"] = 1 - tool.Model.regenerate_array(parent, data, self.keep_objs) + tool.Model.regenerate_array(parent, data, array_layers_to_apply=[self.item] if self.keep_objs else []) pset = tool.Ifc.get().by_id(pset["id"]) if len(data) == 1: diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py index dacc901646..a6cb9a58b2 100644 --- a/src/blenderbim/blenderbim/tool/model.py +++ b/src/blenderbim/blenderbim/tool/model.py @@ -624,13 +624,16 @@ class Model(blenderbim.core.tool.Model): tool.Blender.Modifier.Array.constrain_children_to_parent(element) @classmethod - def regenerate_array(cls, parent_obj, data, keep_objs=False): + def regenerate_array(cls, parent_obj, data, array_layers_to_apply=tuple()): + """`array_layers_to_apply` - list of array layer indices to apply""" 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] - for array in data: + for array_i, array in enumerate(data): + # for `sync_children` we remove all previously generated children to regenerate them again + # to assure they are in complete sync (psets, etc) with the array parent if array["sync_children"]: removed_children = set(array["children"]) for removed_child in removed_children: @@ -645,16 +648,21 @@ class Model(blenderbim.core.tool.Model): total_existing_children = len(array["children"]) children_elements = [] children_objs = [] + + # calculate offset if array["method"] == "DISTRIBUTE": divider = 1 if ((array["count"] - 1) == 0) else (array["count"] - 1) - base_offset = Vector([array["x"] / divider, array["y"] / divider, array["z"] / divider]) * unit_scale + base_offset = Vector([array["x"], array["y"], array["z"]]) / divider * unit_scale else: base_offset = Vector([array["x"], array["y"], array["z"]]) * unit_scale + for i in range(array["count"]): if i == 0: continue offset = base_offset * i + for obj in obj_stack: + # get currently proccesed array element and it's object if child_i >= total_existing_children: child_obj = tool.Spatial.duplicate_object_and_data(obj) child_element = tool.Spatial.run_root_copy_class(obj=child_obj) @@ -668,16 +676,17 @@ class Model(blenderbim.core.tool.Model): child_obj = tool.Spatial.duplicate_object_and_data(obj) child_element = tool.Spatial.run_root_copy_class(obj=child_obj) - child_psets = ifcopenshell.util.element.get_psets(child_element) - child_pset = child_psets.get("BBIM_Array") + # add child pset + child_pset = tool.Pset.get_element_pset(child_element, "BBIM_Array") if child_pset: ifcopenshell.api.run( "pset.edit_pset", tool.Ifc.get(), - pset=tool.Ifc.get().by_id(child_pset["id"]), + pset=child_pset, properties={"Data": None}, ) + # set child object position new_matrix = obj.matrix_world.copy() if array["use_local_space"]: current_obj_translation = obj.matrix_world @ offset @@ -685,23 +694,29 @@ class Model(blenderbim.core.tool.Model): current_obj_translation = obj.matrix_world.translation + offset new_matrix.translation = current_obj_translation child_obj.matrix_world = new_matrix + children_objs.append(child_obj) children_elements.append(child_element) child_i += 1 + obj_stack.extend(children_objs) array["children"] = [e.GlobalId for e in children_elements] + # handle elements unused in the array after regeneration removed_children = set(existing_children) - set(array["children"]) for removed_child in removed_children: element = tool.Ifc.get().by_guid(removed_child) obj = tool.Ifc.get_object(element) if obj: - if keep_objs: - pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") - pset = tool.Ifc.get().by_id(pset["id"]) - ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset) - else: - tool.Geometry.delete_ifc_object(obj) + tool.Geometry.delete_ifc_object(obj) + + if array_i in array_layers_to_apply: + for child_element in children_elements: + pset = tool.Pset.get_element_pset(child_element, "BBIM_Array") + ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=child_element, pset=pset) + + array["children"] = [] + array["count"] = 1 bpy.context.view_layer.update() diff --git a/src/blenderbim/test/tool/test_model.py b/src/blenderbim/test/tool/test_model.py index f82f4f8a1c..3137e19ca4 100644 --- a/src/blenderbim/test/tool/test_model.py +++ b/src/blenderbim/test/tool/test_model.py @@ -342,3 +342,81 @@ class TestGenerateStair2DProfile(NewFile): expected_profile = (verts_data, edges_data, faces_data) generated_profile = subject.generate_stair_2d_profile(**kwargs) self.compare_data(generated_profile, expected_profile) + + +class TestUsingArrays(NewFile): + def setup_array(self, add_second_layer=False, sync_children=False): + bpy.context.scene.BIMProjectProperties.template_file = "0" + bpy.ops.bim.create_project() + + bpy.ops.mesh.primitive_cube_add() + obj = bpy.context.active_object + bpy.context.scene.BIMRootProperties.ifc_product = "IfcElement" + bpy.ops.bim.assign_class(ifc_class="IfcActuator", predefined_type="ELECTRICACTUATOR", userdefined_type="") + + bpy.ops.bim.add_array() + bpy.ops.bim.enable_editing_array(item=0) + obj.BIMArrayProperties.count = 4 + obj.BIMArrayProperties.x = 4 + obj.BIMArrayProperties.sync_children = sync_children + bpy.ops.bim.edit_array(item=0) + + if add_second_layer: + bpy.ops.bim.add_array() + bpy.ops.bim.enable_editing_array(item=1) + obj.BIMArrayProperties.count = 3 + obj.BIMArrayProperties.y = 4 + obj.BIMArrayProperties.sync_children = sync_children + bpy.ops.bim.edit_array(item=1) + + def test_remove_array_last_to_first(self): + self.setup_array(add_second_layer=True) + bpy.ops.bim.remove_array(item=1) + assert len(bpy.context.selected_objects) == 4 + bpy.ops.bim.remove_array(item=0) + assert len(bpy.context.selected_objects) == 1 + + def test_remove_array_first_to_last(self): + self.setup_array(add_second_layer=True) + bpy.ops.bim.remove_array(item=0) + assert len(bpy.context.selected_objects) == 3 + bpy.ops.bim.remove_array(item=0) + assert len(bpy.context.selected_objects) == 1 + + def test_apply_array_1_layer(self): + self.setup_array() + bpy.ops.bim.apply_array() + + objs = bpy.context.selected_objects + assert len(objs) == 4 + # check BBIM_Array psets are removed + for obj in objs: + element = tool.Ifc.get_entity(obj) + pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") + assert pset is None, (obj, pset) + + def test_apply_array_multiple_layers(self): + self.setup_array(add_second_layer=True) + bpy.ops.bim.apply_array() # apply second layer + bpy.ops.bim.apply_array() # apply first layer + + objs = bpy.context.selected_objects + assert len(objs) == 12 + + # check BBIM_Array psets are removed + for obj in objs: + element = tool.Ifc.get_entity(obj) + pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") + assert pset is None, (obj, pset) + + def test_apply_array_with_sync_children(self): + self.setup_array(sync_children=True) + bpy.ops.bim.apply_array() + + objs = bpy.context.selected_objects + assert len(objs) == 4 + # check BBIM_Array psets are removed + for obj in objs: + element = tool.Ifc.get_entity(obj) + pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array") + assert pset is None, (obj, pset)