diff --git a/src/blenderbim/blenderbim/bim/module/structural/operator.py b/src/blenderbim/blenderbim/bim/module/structural/operator.py index 47012097c3..8415771d2b 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/operator.py +++ b/src/blenderbim/blenderbim/bim/module/structural/operator.py @@ -492,7 +492,7 @@ class UnassignStructuralLoadCase(bpy.types.Operator, tool.Ifc.Operator): self.file, **{ "relating_object": self.file.by_id(self.work_plan), - "product": self.file.by_id(self.load_case), + "products": [self.file.by_id(self.load_case)], }, ) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/core/aggregate.py b/src/blenderbim/blenderbim/core/aggregate.py index 09c6f1c4e6..58d10814a7 100644 --- a/src/blenderbim/blenderbim/core/aggregate.py +++ b/src/blenderbim/blenderbim/core/aggregate.py @@ -45,7 +45,7 @@ def unassign_object(ifc, aggregate, collector, relating_obj=None, related_obj=No if related_element: relating_obj = ifc.get_object(relating_element) if relating_obj: - ifc.run("aggregate.unassign_object", product=related_element) + ifc.run("aggregate.unassign_object", products=[related_element]) if container: ifc.run("spatial.assign_container", products=[related_element], relating_structure=container) collector.assign(relating_obj) diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index 970b05e38d..9f0b5ab903 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -65,7 +65,7 @@ def assign_work_schedule(ifc, work_plan=None, work_schedule=None): def unassign_work_schedule(ifc, work_schedule=None): - ifc.run("aggregate.unassign_object", product=work_schedule) + ifc.run("aggregate.unassign_object", products=[work_schedule]) def enable_editing_work_schedule(sequence, work_schedule=None): diff --git a/src/blenderbim/test/core/test_aggregate.py b/src/blenderbim/test/core/test_aggregate.py index 2458e48e40..e4f3efe58d 100644 --- a/src/blenderbim/test/core/test_aggregate.py +++ b/src/blenderbim/test/core/test_aggregate.py @@ -54,7 +54,7 @@ class TestUnassignObject: ifc.get_entity("related_obj").should_be_called().will_return("element") aggregate.get_container("element").should_be_called().will_return("container") ifc.run("spatial.assign_container", products=["element"], relating_structure="container").should_be_called() - ifc.run("aggregate.unassign_object", product="element").should_be_called().will_return("rel") + ifc.run("aggregate.unassign_object", products=["element"]).should_be_called() collector.assign("relating_obj").should_be_called() collector.assign("related_obj").should_be_called() subject.unassign_object(ifc, aggregate, collector, relating_obj="relating_obj", related_obj="related_obj") diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index 376fd14f3f..192caa6cad 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -51,6 +51,7 @@ ARGUMENTS_DEPRECATION = { ), "group.unassign_group": partial(batching_argument_deprecation, prev_argument="product", new_argument="products"), "aggregate.assign_object": partial(batching_argument_deprecation, prev_argument="product", new_argument="products"), + "aggregate.unassign_object": partial(batching_argument_deprecation, prev_argument="product", new_argument="products"), "layer.assign_layer": partial(batching_argument_deprecation, prev_argument="item", new_argument="items"), "layer.unassign_layer": partial(batching_argument_deprecation, prev_argument="item", new_argument="items"), } diff --git a/src/ifcopenshell-python/ifcopenshell/api/aggregate/unassign_object.py b/src/ifcopenshell-python/ifcopenshell/api/aggregate/unassign_object.py index f92b9e60cf..e4d0e35def 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/aggregate/unassign_object.py +++ b/src/ifcopenshell-python/ifcopenshell/api/aggregate/unassign_object.py @@ -22,8 +22,8 @@ import ifcopenshell.util.element class Usecase: - def __init__(self, file, product=None): - """Unassigns a product from its aggregate + def __init__(self, file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]): + """Unassigns products from their aggregate A product (i.e. a smaller part of a whole) may be aggregated into zero or one larger space or element. This function will remove that @@ -37,12 +37,11 @@ class Usecase: If the product is not part of an aggregation relationship, nothing will happen. - :param product: The part of the aggregate, typically an IfcElement or + :param products: The list of parts of the aggregate, typically of IfcElements or IfcSpatialStructureElement subclass - :type product: ifcopenshell.entity_instance.entity_instance - :return: The IfcRelAggregate relationship instance, only returned if the - whole still contains any other parts. - :rtype: ifcopenshell.entity_instance.entity_instance, None + :type product: list[ifcopenshell.entity_instance.entity_instance] + :return: None + :rtype: None Example: @@ -53,26 +52,29 @@ class Usecase: subelement2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding") ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement1], relating_object=element) ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement2], relating_object=element) - # The relationship is returned as element still has subelement2 - rel = ifcopenshell.api.run("aggregate.unassign_object", model, product=subelement1) - # Nothing is returned, as element is now empty - ifcopenshell.api.run("aggregate.unassign_object", model, product=subelement2) + # nothing is returned + ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement1]) + # nothing is returned, relationship is removed + ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement2]) """ self.file = file - self.settings = { "product": product } + self.settings = {"products": products} - def execute(self): - for rel in self.settings["product"].Decomposes or []: - if not rel.is_a("IfcRelAggregates"): - continue - if len(rel.RelatedObjects) == 1: + def execute(self) -> None: + products = set(self.settings["products"]) + rels = set( + rel + for product in products + if (rel := next((rel for rel in product.Decomposes if rel.is_a("IfcRelAggregates")), None)) + ) + + for rel in rels: + related_objects = set(rel.RelatedObjects) - products + if related_objects: + rel.RelatedObjects = list(related_objects) + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) + else: history = rel.OwnerHistory self.file.remove(rel) if history: ifcopenshell.util.element.remove_deep2(self.file, history) - return - related_objects = list(rel.RelatedObjects) - related_objects.remove(self.settings["product"]) - rel.RelatedObjects = related_objects - ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) - return rel diff --git a/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py b/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py index 2fee48d67c..70dacdce00 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py +++ b/src/ifcopenshell-python/ifcopenshell/api/spatial/assign_container.py @@ -141,10 +141,7 @@ class Usecase: return structure_rel # can be either only aggregated or only contained at the same time - for product in products_without_containers: - aggregate = ifcopenshell.util.element.get_aggregate(product) - if aggregate: - ifcopenshell.api.run("aggregate.unassign_object", self.file, product=product) + ifcopenshell.api.run("aggregate.unassign_object", self.file, products=products_without_containers) # unassign elements from previous containers for rel in previous_containers_rels: diff --git a/src/ifcopenshell-python/test/api/aggregate/test_unassign_object.py b/src/ifcopenshell-python/test/api/aggregate/test_unassign_object.py index 343fe7deed..64e4fe4fcd 100644 --- a/src/ifcopenshell-python/test/api/aggregate/test_unassign_object.py +++ b/src/ifcopenshell-python/test/api/aggregate/test_unassign_object.py @@ -25,10 +25,14 @@ import ifcopenshell.util.element class TestUnassignObject(test.bootstrap.IFC4): def test_unassigning_an_object(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSite") - subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") - ifcopenshell.api.run("aggregate.assign_object", self.file, products=[subelement], relating_object=element) - ifcopenshell.api.run("aggregate.unassign_object", self.file, product=subelement) - assert ifcopenshell.util.element.get_aggregate(subelement) is None + subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + ifcopenshell.api.run( + "aggregate.assign_object", self.file, products=[subelement1, subelement2], relating_object=element + ) + ifcopenshell.api.run("aggregate.unassign_object", self.file, products=[subelement1, subelement2]) + assert ifcopenshell.util.element.get_aggregate(subelement1) is None + assert ifcopenshell.util.element.get_aggregate(subelement2) is None def test_the_rel_is_kept_if_there_are_more_decomposed_elements(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSite") @@ -36,12 +40,12 @@ class TestUnassignObject(test.bootstrap.IFC4): subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") ifcopenshell.api.run("aggregate.assign_object", self.file, products=[subelement1], relating_object=element) ifcopenshell.api.run("aggregate.assign_object", self.file, products=[subelement2], relating_object=element) - ifcopenshell.api.run("aggregate.unassign_object", self.file, product=subelement1) + ifcopenshell.api.run("aggregate.unassign_object", self.file, products=[subelement1]) assert len(self.file.by_type("IfcRelAggregates")) == 1 def test_the_rel_is_purged_if_there_are_no_more_decomposed_elements(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSite") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") ifcopenshell.api.run("aggregate.assign_object", self.file, products=[subelement], relating_object=element) - ifcopenshell.api.run("aggregate.unassign_object", self.file, product=subelement) + ifcopenshell.api.run("aggregate.unassign_object", self.file, products=[subelement]) assert len(self.file.by_type("IfcRelAggregates")) == 0 diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index 9695849875..b3a1a1ba73 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -91,3 +91,12 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): rel = ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) assert ifcopenshell.util.element.get_aggregate(subelement) == element assert rel.is_a("IfcRelAggregates") + + @deprecation_check + def test_unassigning_an_aggregate(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSite") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + ifcopenshell.api.run("aggregate.unassign_object", self.file, product=subelement) + assert ifcopenshell.util.element.get_aggregate(subelement) is None + diff --git a/src/ifcsverchok/nodes/ifc/add_spatial_element.py b/src/ifcsverchok/nodes/ifc/add_spatial_element.py index bf9e4a6dee..8015b4168c 100644 --- a/src/ifcsverchok/nodes/ifc/add_spatial_element.py +++ b/src/ifcsverchok/nodes/ifc/add_spatial_element.py @@ -173,7 +173,7 @@ class SvIfcAddSpatialElement(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.h ifcopenshell.api.run( "aggregate.unassign_object", self.file, - product=removed_element, + products=[removed_element], relating_object=result, ) else: