From a9c0c1f4badd8a8d61eb361c82aba91a69e02a49 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 9 Sep 2025 14:40:42 +0500 Subject: [PATCH] control.unassign_control to support batching --- src/bonsai/bonsai/core/cost.py | 7 ++-- src/bonsai/bonsai/core/sequence.py | 2 +- src/ifc5d/ifc5d/csv2ifc.py | 2 +- .../api/control/unassign_control.py | 32 +++++++++---------- .../api/cost/unassign_cost_item_quantity.py | 2 +- .../api/sequence/remove_work_calendar.py | 2 +- .../test/api/control/test_unassign_control.py | 4 +-- src/ifcopenshell-python/test/api/test_api.py | 9 ++++++ 8 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/bonsai/bonsai/core/cost.py b/src/bonsai/bonsai/core/cost.py index adca7e3399..fe55d3c31c 100644 --- a/src/bonsai/bonsai/core/cost.py +++ b/src/bonsai/bonsai/core/cost.py @@ -154,10 +154,7 @@ def unassign_cost_item_type( """ if not product_types: product_types = list(spatial.get_selected_product_types()) - [ - ifc.run("control.unassign_control", relating_control=cost_item, related_object=product_type) - for product_type in product_types - ] + ifc.run("control.unassign_control", relating_control=cost_item, related_objects=product_types) cost.load_cost_item_types(cost_item) return product_types @@ -213,7 +210,7 @@ def assign_cost_value( if existing_cost_rate is None: ifc.run("control.assign_control", relating_control=cost_rate, related_objects=[cost_item]) else: - ifc.run("control.unassign_control", relating_control=existing_cost_rate, related_object=cost_item) + ifc.run("control.unassign_control", relating_control=existing_cost_rate, related_objects=[cost_item]) ifc.run("control.assign_control", relating_control=cost_rate, related_objects=[cost_item]) diff --git a/src/bonsai/bonsai/core/sequence.py b/src/bonsai/bonsai/core/sequence.py index 771bc259c4..610c7095b6 100644 --- a/src/bonsai/bonsai/core/sequence.py +++ b/src/bonsai/bonsai/core/sequence.py @@ -433,7 +433,7 @@ def remove_task_calendar( task: ifcopenshell.entity_instance, work_calendar: ifcopenshell.entity_instance, ) -> None: - ifc.run("control.unassign_control", relating_control=work_calendar, related_object=task) + ifc.run("control.unassign_control", relating_control=work_calendar, related_objects=[task]) ifc.run("sequence.cascade_schedule", task=task) sequence.load_task_properties() diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py index 35a31df751..b391ced659 100644 --- a/src/ifc5d/ifc5d/csv2ifc.py +++ b/src/ifc5d/ifc5d/csv2ifc.py @@ -358,7 +358,7 @@ class Csv2Ifc: ) else: ifcopenshell.api.control.unassign_control( - self.file, relating_control=existing_cost_rate, related_object=cost_item["ifc"] + self.file, relating_control=existing_cost_rate, related_objects=[cost_item["ifc"]] ) ifcopenshell.api.control.assign_control( self.file, relating_control=rate_cost_item, related_objects=[cost_item["ifc"]] diff --git a/src/ifcopenshell-python/ifcopenshell/api/control/unassign_control.py b/src/ifcopenshell-python/ifcopenshell/api/control/unassign_control.py index 0672b49f59..57e47418ef 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/control/unassign_control.py +++ b/src/ifcopenshell-python/ifcopenshell/api/control/unassign_control.py @@ -19,21 +19,19 @@ import ifcopenshell import ifcopenshell.api.owner import ifcopenshell.util.element -from typing import Union def unassign_control( file: ifcopenshell.file, relating_control: ifcopenshell.entity_instance, - related_object: ifcopenshell.entity_instance, -) -> Union[ifcopenshell.entity_instance, None]: + related_objects: list[ifcopenshell.entity_instance], +) -> None: """Unassigns a planning control or constraint to an object :param relating_control: The IfcControl entity that is creating the control or constraint - :param related_object: The IfcObjectDefinition that is being controlled - :return: If the control still is related to other objects, the - IfcRelAssignsToControl is returned, otherwise None. + :param related_objects: The list IfcObjectDefinitions that is being controlled + :return: None Example: @@ -49,19 +47,19 @@ def unassign_control( # And now let's change our mind ifcopenshell.api.control.unassign_control(model, - relating_control=cost_item, related_object=wall) + relating_control=cost_item, related_objects=[wall]) """ - for rel in related_object.HasAssignments or []: - if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != relating_control: - continue - if len(rel.RelatedObjects) == 1: + related_objects_set = set(related_objects) + control_assignments = set(relating_control.Controls) + rels = set(rel for obj in related_objects_set for rel in obj.HasAssignments if rel in control_assignments) + + for rel in rels: + related_objects_new = set(rel.RelatedObjects) - related_objects_set + if related_objects_new: + rel.RelatedObjects = list(related_objects_new) + ifcopenshell.api.owner.update_owner_history(file, element=rel) + else: history = rel.OwnerHistory file.remove(rel) if history: ifcopenshell.util.element.remove_deep2(file, history) - return - related_objects = list(rel.RelatedObjects) - related_objects.remove(related_object) - rel.RelatedObjects = related_objects - ifcopenshell.api.owner.update_owner_history(file, element=rel) - return rel diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py index de180d65ef..1119935ccd 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py @@ -86,7 +86,7 @@ class Usecase: for product in products: ifcopenshell.api.control.unassign_control( self.file, - related_object=product, + related_objects=[product], relating_control=cost_item, ) self.update_cost_item_count(cost_item) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py index 0ff9c5109d..1b52d4ef54 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py @@ -54,7 +54,7 @@ def remove_work_calendar(file: ifcopenshell.file, work_calendar: ifcopenshell.en ifcopenshell.api.control.unassign_control( file, relating_control=work_calendar, - related_object=related_object, + related_objects=[related_object], ) # Currently in API work times are created already attached diff --git a/src/ifcopenshell-python/test/api/control/test_unassign_control.py b/src/ifcopenshell-python/test/api/control/test_unassign_control.py index d730d6d261..ecaebdae32 100644 --- a/src/ifcopenshell-python/test/api/control/test_unassign_control.py +++ b/src/ifcopenshell-python/test/api/control/test_unassign_control.py @@ -29,7 +29,7 @@ class TestUnassignControl(test.bootstrap.IFC4): # assign and unassign relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall]) assert relation - ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_object=wall) + ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_objects=[wall]) assert len(self.file.by_type("IfcRelAssignsToControl")) == 0 # 1 control 2 related objects @@ -37,7 +37,7 @@ class TestUnassignControl(test.bootstrap.IFC4): relation = ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall]) assert relation ifcopenshell.api.control.assign_control(self.file, relating_control=control, related_objects=[wall1]) - ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_object=wall1) + ifcopenshell.api.control.unassign_control(self.file, relating_control=control, related_objects=[wall1]) assert len(self.file.by_type("IfcRelAssignsToControl")) == 1 assert relation.RelatedObjects == (wall,) diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index 3c43d8e54f..8dca81d32f 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -43,3 +43,12 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): control = ifcopenshell.api.cost.add_cost_schedule(model) ifcopenshell.api.control.assign_control(model, relating_control=control, related_objects=[element]) assert list(ifcopenshell.util.element.get_controls(element)) == [control] + + @deprecation_check + def test_unassigning_control(self): + TestTemporarySupportForDeprecatedAPIArguments.test_assigning_control(self) + model = self.file + element = model.by_type("IfcWall")[0] + control = model.by_type("IfcCostSchedule")[0] + ifcopenshell.api.control.unassign_control(model, relating_control=control, related_objects=[element]) + assert list(ifcopenshell.util.element.get_controls(element)) == []