Skip to content

Commit

Permalink
Fixed issues applying arrays
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Andrej730 committed Nov 30, 2023
1 parent 7828508 commit 93ef2d0
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/blenderbim/blenderbim/bim/module/model/array.py
Expand Up @@ -189,7 +189,7 @@ def _execute(self, context):

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:
Expand Down
39 changes: 27 additions & 12 deletions src/blenderbim/blenderbim/tool/model.py
Expand Up @@ -624,13 +624,16 @@ def handle_array_on_copied_element(cls, element, array_data=None):
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:
Expand All @@ -645,16 +648,21 @@ def regenerate_array(cls, parent_obj, data, keep_objs=False):
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)
Expand All @@ -668,40 +676,47 @@ def regenerate_array(cls, parent_obj, data, keep_objs=False):
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
else:
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()

Expand Down
78 changes: 78 additions & 0 deletions src/blenderbim/test/tool/test_model.py
Expand Up @@ -342,3 +342,81 @@ def test_create_generic_stair(self):
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)

0 comments on commit 93ef2d0

Please sign in to comment.