mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
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.
This commit is contained in:
@@ -50,9 +50,10 @@ classes = (
|
||||
array.EditArray,
|
||||
array.EnableEditingArray,
|
||||
array.ApplyArray,
|
||||
array.RegenerateArray,
|
||||
array.RemoveArray,
|
||||
array.SelectArrayParent,
|
||||
array.SelectAllArrayObjects,
|
||||
array.SelectArrayParent,
|
||||
array.Input3DCursorXArray,
|
||||
array.Input3DCursorYArray,
|
||||
array.Input3DCursorZArray,
|
||||
|
||||
@@ -61,7 +61,6 @@ class AddArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"use_local_space": True,
|
||||
"sync_children": False,
|
||||
"method": "OFFSET",
|
||||
}
|
||||
|
||||
@@ -80,28 +79,27 @@ class AddArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset=pset,
|
||||
properties={"Parent": element.GlobalId, "Data": ifc_file.create_entity("IfcText", json.dumps(data))},
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DisableEditingArray(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_array"
|
||||
bl_label = "Disable Editing Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
tool.Model.get_array_props(obj).is_editing = -1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableEditingArray(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_array"
|
||||
bl_label = "Enable Editing Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -122,11 +120,9 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.y = data["y"] * si_conversion
|
||||
props.z = data["z"] * si_conversion
|
||||
props.use_local_space = data.get("use_local_space", False)
|
||||
props.sync_children = data.get("sync_children", False)
|
||||
props.method = data.get("method", "OFFSET")
|
||||
|
||||
props.is_editing = self.item
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -151,31 +147,25 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"y": props.y / si_conversion,
|
||||
"z": props.z / si_conversion,
|
||||
"use_local_space": props.use_local_space,
|
||||
"sync_children": props.sync_children,
|
||||
"method": props.method,
|
||||
}
|
||||
|
||||
props.is_editing = -1
|
||||
|
||||
try:
|
||||
parent = tool.Ifc.get_object(tool.Ifc.get().by_guid(pset["Parent"]))
|
||||
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
|
||||
parent = tool.Ifc.get_object(parent_element)
|
||||
except:
|
||||
return {"FINISHED"}
|
||||
|
||||
tool.Blender.Modifier.Array.remove_constraints(parent_element)
|
||||
tool.Model.regenerate_array(parent, data)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
data = tool.Ifc.get().createIfcText(json.dumps(data))
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Data": data})
|
||||
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
# clears the relating_array_object so it doesn't show again next time
|
||||
props.relating_array_object = None
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.apply_array"
|
||||
@@ -192,6 +182,33 @@ class ApplyArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RegenerateArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.regenerate_array"
|
||||
bl_label = "Regenerate Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
try:
|
||||
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
|
||||
parent = tool.Ifc.get_object(parent_element)
|
||||
except:
|
||||
return {"FINISHED"}
|
||||
pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array")
|
||||
arrays = json.loads(pset["Data"])
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
for array in arrays:
|
||||
for child in set(array["children"]):
|
||||
if child_obj := tool.Ifc.get_object(tool.Ifc.get().by_guid(child)):
|
||||
tool.Geometry.delete_ifc_object(child_obj)
|
||||
array["children"].clear()
|
||||
print('cleared array', arrays)
|
||||
tool.Model.regenerate_array(obj, arrays)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
|
||||
class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_array"
|
||||
bl_label = "Remove Array"
|
||||
@@ -216,7 +233,8 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.is_editing = -1
|
||||
|
||||
try:
|
||||
parent = tool.Ifc.get_object(tool.Ifc.get().by_guid(pset["Parent"]))
|
||||
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
|
||||
parent = tool.Ifc.get_object(parent_element)
|
||||
except:
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -226,9 +244,10 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
if not self.keep_objs:
|
||||
data[self.item]["count"] = 1
|
||||
tool.Blender.Modifier.Array.remove_constraints(parent_element)
|
||||
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"])
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Array")
|
||||
if len(data) == 1:
|
||||
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
|
||||
else:
|
||||
@@ -237,8 +256,6 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Data": data})
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectArrayParent(bpy.types.Operator):
|
||||
bl_idname = "bim.select_array_parent"
|
||||
|
||||
@@ -387,11 +387,6 @@ class BIMArrayProperties(PropertyGroup):
|
||||
name="Method",
|
||||
default="OFFSET",
|
||||
)
|
||||
sync_children: bpy.props.BoolProperty(
|
||||
name="Sync Children",
|
||||
description="Regenerate all children based on the parent object",
|
||||
default=False,
|
||||
)
|
||||
relating_array_object: bpy.props.PointerProperty(
|
||||
type=bpy.types.Object,
|
||||
name="Copy Array Properties",
|
||||
|
||||
@@ -229,6 +229,7 @@ class BIM_PT_array(bpy.types.Panel):
|
||||
if ArrayData.data["parameters"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=ArrayData.data["parameters"]["parent_name"], icon="CON_CHILDOF")
|
||||
row.operator("bim.regenerate_array", icon="FILE_REFRESH", text="")
|
||||
row.operator("bim.select_array_parent", icon="OBJECT_DATA", text="")
|
||||
row.operator("bim.select_all_array_objects", icon="RESTRICT_SELECT_OFF", text="")
|
||||
|
||||
@@ -246,7 +247,6 @@ class BIM_PT_array(bpy.types.Panel):
|
||||
row.prop(props, "method")
|
||||
row = box.row(align=True)
|
||||
row.prop(props, "use_local_space")
|
||||
row.prop(props, "sync_children")
|
||||
col = box.column()
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "x")
|
||||
|
||||
@@ -1051,6 +1051,7 @@ class Model(bonsai.core.tool.Model):
|
||||
|
||||
tool.Model.regenerate_array(obj, array_data)
|
||||
|
||||
array_pset = tool.Pset.get_element_pset(element, "BBIM_Array")
|
||||
json_data = tool.Ifc.get().createIfcText(json.dumps(array_data))
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=array_pset, properties={"Data": json_data})
|
||||
|
||||
@@ -1063,22 +1064,15 @@ class Model(bonsai.core.tool.Model):
|
||||
cls, parent_obj: bpy.types.Object, data: list[dict[str, Any]], array_layers_to_apply: Iterable[int] = tuple()
|
||||
) -> None:
|
||||
"""`array_layers_to_apply` - list of array layer indices to apply"""
|
||||
tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent_obj))
|
||||
parent_element = tool.Ifc.get_entity(parent_obj)
|
||||
|
||||
if pset := ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array"):
|
||||
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=parent_element, pset=tool.Ifc.get().by_id(pset["id"]))
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
obj_stack = [parent_obj]
|
||||
|
||||
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:
|
||||
element = tool.Ifc.get().by_guid(removed_child)
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
array["children"].clear()
|
||||
|
||||
child_i = 0
|
||||
existing_children = set(array["children"])
|
||||
total_existing_children = len(array["children"])
|
||||
@@ -1104,22 +1098,19 @@ class Model(bonsai.core.tool.Model):
|
||||
child_obj = tool.Ifc.get_object(child_element)
|
||||
assert child_obj
|
||||
except:
|
||||
old_to_new, _ = tool.Geometry.duplicate_ifc_objects([obj])
|
||||
# TODO Is this correct to assume one child? I really
|
||||
# don't understand the linked aggregates and array
|
||||
# behaviour.
|
||||
old_to_new, _ = tool.Geometry.duplicate_ifc_objects([parent_obj])
|
||||
child_element = next(iter(old_to_new.values()))[0]
|
||||
child_obj = tool.Ifc.get_object(child_element)
|
||||
|
||||
# add child pset
|
||||
child_pset = tool.Pset.get_element_pset(child_element, "BBIM_Array")
|
||||
if child_pset:
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
tool.Ifc.get(),
|
||||
pset=child_pset,
|
||||
properties={"Data": None},
|
||||
should_purge=False,
|
||||
)
|
||||
if not (child_pset := tool.Pset.get_element_pset(child_element, "BBIM_Array")):
|
||||
child_pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=child_element, name="BBIM_Array")
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
tool.Ifc.get(),
|
||||
pset=child_pset,
|
||||
properties={"Data": None, "Parent": parent_element.GlobalId},
|
||||
should_purge=False,
|
||||
)
|
||||
|
||||
# set child object position
|
||||
new_matrix = obj.matrix_world.copy()
|
||||
@@ -1155,6 +1146,10 @@ class Model(bonsai.core.tool.Model):
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=parent_element, name="BBIM_Array")
|
||||
json_data = tool.Ifc.get().createIfcText(json.dumps(data))
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Data": json_data, "Parent": parent_element.GlobalId})
|
||||
|
||||
@classmethod
|
||||
def replace_object_ifc_representation(
|
||||
cls,
|
||||
|
||||
Reference in New Issue
Block a user