Skip to content

Commit 83d97d7

Browse files
committed
Fix #7616. Make regenerate array an operator instead of an array preference
Sync children was a bit odd because it's not actually an "array parameter" per se, just a way to regenerate. It's now an operator. There was a deeper issue I encountered where the way arrays work is that they duplicate the parent element. (first encountered in e51d2d ) However, the duplication code has special array handling too. To avoid issues with this cyclical coupling the previous solution was to reimplement object duplication (with all sorts of pitfalls that has). Now, I've tried to decouple it further by clearing all array psets prior to any change, and readding the pset after everything has been regenerated. This can be improved upon but I don't feel confident until there is more comprehensive test coverage for the duplicate operator.
1 parent c1aa1de commit 83d97d7

5 files changed

Lines changed: 59 additions & 51 deletions

File tree

src/bonsai/bonsai/bim/module/model/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@
5050
array.EditArray,
5151
array.EnableEditingArray,
5252
array.ApplyArray,
53+
array.RegenerateArray,
5354
array.RemoveArray,
54-
array.SelectArrayParent,
5555
array.SelectAllArrayObjects,
56+
array.SelectArrayParent,
5657
array.Input3DCursorXArray,
5758
array.Input3DCursorYArray,
5859
array.Input3DCursorZArray,

src/bonsai/bonsai/bim/module/model/array.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ def _execute(self, context):
6161
"y": 0.0,
6262
"z": 0.0,
6363
"use_local_space": True,
64-
"sync_children": False,
6564
"method": "OFFSET",
6665
}
6766

@@ -80,28 +79,27 @@ def _execute(self, context):
8079
pset=pset,
8180
properties={"Parent": element.GlobalId, "Data": ifc_file.create_entity("IfcText", json.dumps(data))},
8281
)
83-
return {"FINISHED"}
8482

8583

86-
class DisableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
84+
class DisableEditingArray(bpy.types.Operator):
8785
bl_idname = "bim.disable_editing_array"
8886
bl_label = "Disable Editing Array"
8987
bl_options = {"REGISTER", "UNDO"}
9088

91-
def _execute(self, context):
89+
def execute(self, context):
9290
obj = context.active_object
9391
assert obj
9492
tool.Model.get_array_props(obj).is_editing = -1
9593
return {"FINISHED"}
9694

9795

98-
class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
96+
class EnableEditingArray(bpy.types.Operator):
9997
bl_idname = "bim.enable_editing_array"
10098
bl_label = "Enable Editing Array"
10199
bl_options = {"REGISTER", "UNDO"}
102100
item: bpy.props.IntProperty()
103101

104-
def _execute(self, context):
102+
def execute(self, context):
105103
obj = context.active_object
106104
assert obj
107105
element = tool.Ifc.get_entity(obj)
@@ -122,11 +120,9 @@ def _execute(self, context):
122120
props.y = data["y"] * si_conversion
123121
props.z = data["z"] * si_conversion
124122
props.use_local_space = data.get("use_local_space", False)
125-
props.sync_children = data.get("sync_children", False)
126123
props.method = data.get("method", "OFFSET")
127124

128125
props.is_editing = self.item
129-
130126
return {"FINISHED"}
131127

132128

@@ -151,31 +147,25 @@ def _execute(self, context):
151147
"y": props.y / si_conversion,
152148
"z": props.z / si_conversion,
153149
"use_local_space": props.use_local_space,
154-
"sync_children": props.sync_children,
155150
"method": props.method,
156151
}
157152

158153
props.is_editing = -1
159154

160155
try:
161-
parent = tool.Ifc.get_object(tool.Ifc.get().by_guid(pset["Parent"]))
156+
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
157+
parent = tool.Ifc.get_object(parent_element)
162158
except:
163159
return {"FINISHED"}
164160

161+
tool.Blender.Modifier.Array.remove_constraints(parent_element)
165162
tool.Model.regenerate_array(parent, data)
166-
167-
pset = tool.Ifc.get().by_id(pset["id"])
168-
data = tool.Ifc.get().createIfcText(json.dumps(data))
169-
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Data": data})
170-
171163
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
172164
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
173165

174166
# clears the relating_array_object so it doesn't show again next time
175167
props.relating_array_object = None
176168

177-
return {"FINISHED"}
178-
179169

180170
class ApplyArray(bpy.types.Operator, tool.Ifc.Operator):
181171
bl_idname = "bim.apply_array"
@@ -192,6 +182,33 @@ def _execute(self, context):
192182
return {"FINISHED"}
193183

194184

185+
class RegenerateArray(bpy.types.Operator, tool.Ifc.Operator):
186+
bl_idname = "bim.regenerate_array"
187+
bl_label = "Regenerate Array"
188+
bl_options = {"REGISTER", "UNDO"}
189+
190+
def _execute(self, context):
191+
obj = context.active_object
192+
element = tool.Ifc.get_entity(obj)
193+
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
194+
try:
195+
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
196+
parent = tool.Ifc.get_object(parent_element)
197+
except:
198+
return {"FINISHED"}
199+
pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array")
200+
arrays = json.loads(pset["Data"])
201+
pset = tool.Ifc.get().by_id(pset["id"])
202+
for array in arrays:
203+
for child in set(array["children"]):
204+
if child_obj := tool.Ifc.get_object(tool.Ifc.get().by_guid(child)):
205+
tool.Geometry.delete_ifc_object(child_obj)
206+
array["children"].clear()
207+
print('cleared array', arrays)
208+
tool.Model.regenerate_array(obj, arrays)
209+
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
210+
211+
195212
class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
196213
bl_idname = "bim.remove_array"
197214
bl_label = "Remove Array"
@@ -216,7 +233,8 @@ def _execute(self, context):
216233
props.is_editing = -1
217234

218235
try:
219-
parent = tool.Ifc.get_object(tool.Ifc.get().by_guid(pset["Parent"]))
236+
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
237+
parent = tool.Ifc.get_object(parent_element)
220238
except:
221239
return {"FINISHED"}
222240

@@ -226,9 +244,10 @@ def _execute(self, context):
226244

227245
if not self.keep_objs:
228246
data[self.item]["count"] = 1
247+
tool.Blender.Modifier.Array.remove_constraints(parent_element)
229248
tool.Model.regenerate_array(parent, data, array_layers_to_apply=[self.item] if self.keep_objs else [])
230249

231-
pset = tool.Ifc.get().by_id(pset["id"])
250+
pset = tool.Pset.get_element_pset(element, "BBIM_Array")
232251
if len(data) == 1:
233252
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
234253
else:
@@ -237,8 +256,6 @@ def _execute(self, context):
237256
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Data": data})
238257
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
239258

240-
return {"FINISHED"}
241-
242259

243260
class SelectArrayParent(bpy.types.Operator):
244261
bl_idname = "bim.select_array_parent"

src/bonsai/bonsai/bim/module/model/prop.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,6 @@ class BIMArrayProperties(PropertyGroup):
387387
name="Method",
388388
default="OFFSET",
389389
)
390-
sync_children: bpy.props.BoolProperty(
391-
name="Sync Children",
392-
description="Regenerate all children based on the parent object",
393-
default=False,
394-
)
395390
relating_array_object: bpy.props.PointerProperty(
396391
type=bpy.types.Object,
397392
name="Copy Array Properties",

src/bonsai/bonsai/bim/module/model/ui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ def draw(self, context):
229229
if ArrayData.data["parameters"]:
230230
row = self.layout.row(align=True)
231231
row.label(text=ArrayData.data["parameters"]["parent_name"], icon="CON_CHILDOF")
232+
row.operator("bim.regenerate_array", icon="FILE_REFRESH", text="")
232233
row.operator("bim.select_array_parent", icon="OBJECT_DATA", text="")
233234
row.operator("bim.select_all_array_objects", icon="RESTRICT_SELECT_OFF", text="")
234235

@@ -246,7 +247,6 @@ def draw(self, context):
246247
row.prop(props, "method")
247248
row = box.row(align=True)
248249
row.prop(props, "use_local_space")
249-
row.prop(props, "sync_children")
250250
col = box.column()
251251
row = col.row(align=True)
252252
row.prop(props, "x")

src/bonsai/bonsai/tool/model.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,7 @@ def handle_array_on_copied_element(
10511051

10521052
tool.Model.regenerate_array(obj, array_data)
10531053

1054+
array_pset = tool.Pset.get_element_pset(element, "BBIM_Array")
10541055
json_data = tool.Ifc.get().createIfcText(json.dumps(array_data))
10551056
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=array_pset, properties={"Data": json_data})
10561057

@@ -1063,22 +1064,15 @@ def regenerate_array(
10631064
cls, parent_obj: bpy.types.Object, data: list[dict[str, Any]], array_layers_to_apply: Iterable[int] = tuple()
10641065
) -> None:
10651066
"""`array_layers_to_apply` - list of array layer indices to apply"""
1066-
tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent_obj))
1067+
parent_element = tool.Ifc.get_entity(parent_obj)
1068+
1069+
if pset := ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array"):
1070+
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=parent_element, pset=tool.Ifc.get().by_id(pset["id"]))
10671071

10681072
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
10691073
obj_stack = [parent_obj]
10701074

10711075
for array_i, array in enumerate(data):
1072-
# for `sync_children` we remove all previously generated children to regenerate them again
1073-
# to assure they are in complete sync (psets, etc) with the array parent
1074-
if array["sync_children"]:
1075-
removed_children = set(array["children"])
1076-
for removed_child in removed_children:
1077-
element = tool.Ifc.get().by_guid(removed_child)
1078-
if obj := tool.Ifc.get_object(element):
1079-
tool.Geometry.delete_ifc_object(obj)
1080-
array["children"].clear()
1081-
10821076
child_i = 0
10831077
existing_children = set(array["children"])
10841078
total_existing_children = len(array["children"])
@@ -1104,22 +1098,19 @@ def regenerate_array(
11041098
child_obj = tool.Ifc.get_object(child_element)
11051099
assert child_obj
11061100
except:
1107-
old_to_new, _ = tool.Geometry.duplicate_ifc_objects([obj])
1108-
# TODO Is this correct to assume one child? I really
1109-
# don't understand the linked aggregates and array
1110-
# behaviour.
1101+
old_to_new, _ = tool.Geometry.duplicate_ifc_objects([parent_obj])
11111102
child_element = next(iter(old_to_new.values()))[0]
11121103
child_obj = tool.Ifc.get_object(child_element)
11131104

11141105
# add child pset
1115-
child_pset = tool.Pset.get_element_pset(child_element, "BBIM_Array")
1116-
if child_pset:
1117-
ifcopenshell.api.pset.edit_pset(
1118-
tool.Ifc.get(),
1119-
pset=child_pset,
1120-
properties={"Data": None},
1121-
should_purge=False,
1122-
)
1106+
if not (child_pset := tool.Pset.get_element_pset(child_element, "BBIM_Array")):
1107+
child_pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=child_element, name="BBIM_Array")
1108+
ifcopenshell.api.pset.edit_pset(
1109+
tool.Ifc.get(),
1110+
pset=child_pset,
1111+
properties={"Data": None, "Parent": parent_element.GlobalId},
1112+
should_purge=False,
1113+
)
11231114

11241115
# set child object position
11251116
new_matrix = obj.matrix_world.copy()
@@ -1155,6 +1146,10 @@ def regenerate_array(
11551146

11561147
bpy.context.view_layer.update()
11571148

1149+
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=parent_element, name="BBIM_Array")
1150+
json_data = tool.Ifc.get().createIfcText(json.dumps(data))
1151+
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Data": json_data, "Parent": parent_element.GlobalId})
1152+
11581153
@classmethod
11591154
def replace_object_ifc_representation(
11601155
cls,

0 commit comments

Comments
 (0)