mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-10-01 12:40:07 +00:00
Fix #6663: keep a duplicated nested aggregate inside its parent aggregate
Duplicating a linked aggregate (Ctrl+Shift+D) that is itself nested inside another aggregate dropped the copy outside its parent instead of keeping it nested. Root cause: DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator called tool.Root.recreate_aggregate(old_to_new) a second time, after execute_ifc_duplicate_operator had already called it once (via duplicate_ifc_objects) and, for the case where the parent aggregate itself was not part of the duplication set, had already correctly restored the new nested aggregate's assignment to that original parent. On the second, redundant call, recreate_aggregate looks up the parent aggregate in old_to_new again, still does not find it there (since the parent was never duplicated), and its "parent not found" branch actively unassigns the just-restored link, stranding the duplicate outside its parent. Fix: drop the redundant second call. The plain duplicate path already handles both cases correctly (parent duplicated and parent not duplicated). Verified live in headless Blender 5.2 / Bonsai on the repo's own test/files/linked-aggregates.ifc: nested Wall_01/Wall_02 wrapped into a new aggregate "Nested" inside the existing "Assembly" aggregate, then duplicated via bim.object_duplicate_move_linked_aggregate. Before the fix, the new "Nested_01" aggregate had no parent aggregate; after the fix it correctly remains aggregated by "Assembly". Also added a regression scenario to test/bim/feature/geometry.feature covering this exact case, manually traced against the existing step definitions (the full pytest-bdd harness needs a network-provisioned Blender python to run locally, so it hasn't been executed through pytest itself; CI will run it). black + ruff clean. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -1531,8 +1531,13 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
|||||||
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||||
all_old_to_new.update(old_to_new) # Collect all duplicates
|
all_old_to_new.update(old_to_new) # Collect all duplicates
|
||||||
|
|
||||||
# Recreate aggregate structure
|
# Note: execute_ifc_duplicate_operator() above already recreates the aggregate
|
||||||
tool.Root.recreate_aggregate(old_to_new)
|
# structure for us (including reassigning to an unduplicated parent aggregate, see
|
||||||
|
# its "Restore parent aggregate relationships" step). Calling
|
||||||
|
# tool.Root.recreate_aggregate() again here is not just redundant: since the parent
|
||||||
|
# aggregate is never part of `old_to_new` when it wasn't itself duplicated,
|
||||||
|
# recreate_aggregate()'s "parent not found" branch actively *unassigns* the just
|
||||||
|
# restored parent link, stranding a duplicated nested aggregate outside its parent (#6663).
|
||||||
|
|
||||||
# Copy linked aggregate data
|
# Copy linked aggregate data
|
||||||
copy_linked_aggregate_data(old_to_new)
|
copy_linked_aggregate_data(old_to_new)
|
||||||
|
|||||||
@@ -610,6 +610,21 @@ Scenario: Duplicate linked aggregate
|
|||||||
And the object "Assembly_01" exists
|
And the object "Assembly_01" exists
|
||||||
Then the object "IfcElementAssembly/Assembly" and "Assembly_01" belong to the same Linked Aggregate Group
|
Then the object "IfcElementAssembly/Assembly" and "Assembly_01" belong to the same Linked Aggregate Group
|
||||||
|
|
||||||
|
Scenario: Duplicate linked aggregate - a nested aggregate stays inside its parent aggregate
|
||||||
|
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
|
||||||
|
And the object "IfcWall/Wall_01" is selected
|
||||||
|
And additionally the object "IfcWall/Wall_02" is selected
|
||||||
|
When I press "bim.add_aggregate(aggregate_name='Nested')"
|
||||||
|
Then the object "IfcElementAssembly/Nested" is aggregated by object "IfcElementAssembly/Assembly"
|
||||||
|
When the object "IfcWall/Wall_01" is selected
|
||||||
|
And I duplicate linked aggregate the selected objects
|
||||||
|
Then the object "IfcWall/Wall_01.001" exists
|
||||||
|
And the object "IfcWall/Wall_02.001" exists
|
||||||
|
# msgbus updates don't happen in background mode, so obj is not renamed to "IfcElementAssembly/Nested_01"
|
||||||
|
And the object "Nested_01" exists
|
||||||
|
And the object "IfcWall/Wall_01.001" is aggregated by object "Nested_01"
|
||||||
|
And the object "Nested_01" is aggregated by object "IfcElementAssembly/Assembly"
|
||||||
|
|
||||||
Scenario: Refresh linked aggregate
|
Scenario: Refresh linked aggregate
|
||||||
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
|
Given I load the IFC test file "/test/files/linked-aggregates.ifc"
|
||||||
And the object "IfcWall/Wall_01" is selected
|
And the object "IfcWall/Wall_01" is selected
|
||||||
|
|||||||
Reference in New Issue
Block a user