mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-19 11:43:53 +00:00
Tolerate stale array child/parent GUIDs (#8177)
* Tolerate stale array child/parent GUIDs A real-world IFC project (an arrayed door whose host got deleted externally) crashed Bonsai's project load with "Instance with GlobalId not found" inside setup_arrays. tool.Blender.get_object_from_guid declared Optional return but let RuntimeError propagate; callers iterating BBIM_Array child lists then crashed instead of skipping. Honour the documented contract by returning None on miss, matching the convention used by every other by_guid lookup helper in tool/array.py, tool/ifc.py, tool/geometry.py. Sweep the four user-action sites that resolve array child/parent GUIDs without a guard - they shared the same bug class but were reachable from different operators (regenerate_array, RegenerateArray clear, duplicate_ifc_objects, process_arrays). An already-missing entity is the desired terminal state for each, so the fix is try/except RuntimeError: continue/skip. setup_arrays now also collects each parent with at least one stale child GUID into IfcImporter.broken_arrays, surfaced via a new Project panel banner mirroring the existing pending_opening_recut UX. The banner reports the count and offers "Select Elements" to navigate to the affected array parents and a Dismiss button. constrain_children_to_parent was being called once per layer inside setup_arrays' for loop even though it always iterates all layers internally - lifted out of the loop (pre-existing N x perf bug that the stale-GUID print exposed). Regression tests: - test_returns_none_when_guid_not_in_file pins the get_object_from_guid Optional contract. - test_remove_array_tolerates_stale_child_guid injects a fake child GUID into BBIM_Array.Data and asserts bim.remove_array completes cleanly. Generated with the assistance of an AI coding tool. * Black: wrap long bl_description in dismiss_pending_array_repair Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -183,3 +183,16 @@ class TestNpFrombufferLegacy(NewFile):
|
||||
result = subject.np_frombuffer_legacy(data, n)
|
||||
assert result.shape == (n,)
|
||||
np.testing.assert_allclose(result, np.arange(n))
|
||||
|
||||
|
||||
class TestGetObjectFromGuidMissing(NewFile):
|
||||
"""``get_object_from_guid`` must honour its ``Optional[Object]`` return
|
||||
contract: a GUID that does not resolve in the current IFC file yields
|
||||
``None``, not a ``RuntimeError``. Callers iterate stored GUID lists
|
||||
(array children, library refs, …) and rely on the falsy return to
|
||||
skip stale entries."""
|
||||
|
||||
def test_returns_none_when_guid_not_in_file(self):
|
||||
bpy.ops.bim.create_project()
|
||||
assert tool.Ifc.get() is not None
|
||||
assert subject.get_object_from_guid("3iyt7r$Hf4_hQYNhBIDJI4") is None
|
||||
|
||||
@@ -672,6 +672,30 @@ class TestUsingArrays(NewFile):
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
assert pset is None, (obj, pset)
|
||||
|
||||
def test_remove_array_tolerates_stale_child_guid(self):
|
||||
"""``bim.remove_array`` and the underlying ``regenerate_array`` must
|
||||
survive a child GUID in ``BBIM_Array.Data`` that no longer resolves
|
||||
in the file. Real-world IFC files can carry dangling array refs
|
||||
from external edits — the remove path is meant to delete those
|
||||
children, so an already-missing entity is the desired terminal
|
||||
state, not a fatal error."""
|
||||
self.setup_array()
|
||||
parent_obj = bpy.context.active_object
|
||||
parent_element = tool.Ifc.get_entity(parent_obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[0]["children"].append("3iyt7r$Hf4_hQYNhBIDJI4")
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
ifc_file,
|
||||
pset=ifc_file.by_id(pset["id"]),
|
||||
properties={"Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
bpy.ops.bim.remove_array(item=0)
|
||||
assert ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array") is None
|
||||
|
||||
|
||||
class TestApplyIfcMaterialChanges(NewFile):
|
||||
def get_used_styles(self, obj: bpy.types.Object) -> set[ifcopenshell.entity_instance]:
|
||||
|
||||
Reference in New Issue
Block a user