From 305f8c60037e9db69a2fe29a5cfccc8df9cab011 Mon Sep 17 00:00:00 2001 From: carlopav Date: Thu, 23 Jul 2026 14:57:58 +0200 Subject: [PATCH] cost: don't leave copied cost items in the copied schedule (#8851) copy_cost_item appends the copy to the inverse relationships of the original cost item, which for a root cost item includes the source schedule's IfcRelAssignsToControl. copy_cost_schedule then assigned that same cost item to the new schedule as well, so the copies showed up in both schedules and deleting them from one removed them from the other. Unassign the copy from the source schedule before assigning it to the new one. Co-Authored-By: Claude Opus 4.8 --- .../ifcopenshell/api/cost/copy_cost_schedule.py | 1 + .../test/api/cost/test_copy_cost_schedule.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_schedule.py index 3101e820c2..126748af4c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_schedule.py @@ -45,5 +45,6 @@ def copy_cost_schedule( if isinstance(duplicated_cost_item, list): # All other nested items are not connected to the cost schedule explicitly. duplicated_cost_item = duplicated_cost_item[0] + ifcopenshell.api.control.unassign_control(file, cost_schedule, [duplicated_cost_item]) ifcopenshell.api.control.assign_control(file, new_schedule, [duplicated_cost_item]) return new_schedule diff --git a/src/ifcopenshell-python/test/api/cost/test_copy_cost_schedule.py b/src/ifcopenshell-python/test/api/cost/test_copy_cost_schedule.py index 119c24387e..cac94c320f 100644 --- a/src/ifcopenshell-python/test/api/cost/test_copy_cost_schedule.py +++ b/src/ifcopenshell-python/test/api/cost/test_copy_cost_schedule.py @@ -40,6 +40,9 @@ class TestCopyCostSchedule(test.bootstrap.IFC4): assert len(new_cost_items) == 2 assert len(new_cost_items.intersection(old_cost_items)) == 0 + # The copies should be removed from the original schedule. + assert set(ifcopenshell.util.cost.get_schedule_cost_items(schedule)) == old_cost_items + class TestCopyCostScheduleIFC2X3(test.bootstrap.IFC2X3, TestCopyCostSchedule): pass