From 8ac0d22a95009d52a69ab6e4874e327daf934b39 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 16 Apr 2024 10:53:54 +0500 Subject: [PATCH] spatial.reference_structure - support batching #4474 --- src/blenderbim/blenderbim/core/spatial.py | 2 +- src/blenderbim/test/core/test_spatial.py | 2 +- .../ifcopenshell/api/__init__.py | 3 + .../api/spatial/dereference_structure.py | 4 +- .../api/spatial/reference_structure.py | 63 ++++++++++++------- .../api/spatial/test_dereference_structure.py | 8 +-- .../api/spatial/test_reference_structure.py | 34 +++++++--- src/ifcopenshell-python/test/api/test_api.py | 10 +++ .../test/util/test_element.py | 8 +-- 9 files changed, 88 insertions(+), 46 deletions(-) diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index 57d627eaae..4532b86dba 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -32,7 +32,7 @@ def reference_structure( element: Optional[ifcopenshell.entity_instance] = None, ) -> Union[ifcopenshell.entity_instance, None]: if spatial.can_reference(structure, element): - return ifc.run("spatial.reference_structure", product=element, relating_structure=structure) + return ifc.run("spatial.reference_structure", products=[element], relating_structure=structure) def dereference_structure( diff --git a/src/blenderbim/test/core/test_spatial.py b/src/blenderbim/test/core/test_spatial.py index b77e95dc59..35282397fa 100644 --- a/src/blenderbim/test/core/test_spatial.py +++ b/src/blenderbim/test/core/test_spatial.py @@ -23,7 +23,7 @@ from test.core.bootstrap import ifc, collector, spatial class TestReferenceStructure: def test_run(self, ifc, spatial): spatial.can_reference("structure", "element").should_be_called().will_return(True) - ifc.run("spatial.reference_structure", product="element", relating_structure="structure").should_be_called() + ifc.run("spatial.reference_structure", products=["element"], relating_structure="structure").should_be_called() subject.reference_structure(ifc, spatial, structure="structure", element="element") diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index ff0a786292..0a77fe5da7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -105,6 +105,9 @@ ARGUMENTS_DEPRECATION = { "document.unassign_document": partial( batching_argument_deprecation, prev_argument="product", new_argument="products" ), + "spatial.reference_structure": partial( + batching_argument_deprecation, prev_argument="product", new_argument="products" + ), } diff --git a/src/ifcopenshell-python/ifcopenshell/api/spatial/dereference_structure.py b/src/ifcopenshell-python/ifcopenshell/api/spatial/dereference_structure.py index 4a62532762..a9d4a1ad83 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/spatial/dereference_structure.py +++ b/src/ifcopenshell-python/ifcopenshell/api/spatial/dereference_structure.py @@ -59,8 +59,8 @@ class Usecase: ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1) # And referenced in the others - ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey2) - ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey3) + ifcopenshell.api.run("spatial.reference_structure", model, products=[column], relating_structure=storey2) + ifcopenshell.api.run("spatial.reference_structure", model, products=[column], relating_structure=storey3) # Actually, it only goes up to storey 2. ifcopenshell.api.run("spatial.dereference_structure", model, product=column, relating_structure=storey3) diff --git a/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py b/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py index 187ce20afe..6d8915828a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py +++ b/src/ifcopenshell-python/ifcopenshell/api/spatial/reference_structure.py @@ -18,11 +18,18 @@ import ifcopenshell import ifcopenshell.api +import ifcopenshell.util.element +from typing import Union class Usecase: - def __init__(self, file, product=None, relating_structure=None): - """Denote that a product is related to a spatial structure + def __init__( + self, + file: ifcopenshell.file, + products: list[ifcopenshell.entity_instance], + relating_structure: ifcopenshell.entity_instance, + ): + """Denote that a list products is related to a list of spatial structures This is similar to ifcopenshell.api.spatial.assign_container, except that containment can only occur between a product and a single spatial @@ -39,13 +46,15 @@ class Usecase: Referencing is non-hierarchical, so a door may be referenced in multiple spaces simultaneously. - :param product: The physical IfcElement that exists in the space. - :type product: ifcopenshell.entity_instance.entity_instance + :param products: The list of physical IfcElements that exists in the space. + :type products: list[ifcopenshell.entity_instance.entity_instance] :param relating_structure: The IfcSpatialStructureElement element, such as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element exists in. + :type relating_structure: ifcopenshell.entity_instance.entity_instance :return: The IfcRelReferencedInSpatialStructure relationship instance - :rtype: ifcopenshell.entity_instance.entity_instance + or `None` if `products` was an empty list. + :rtype: Union[ifcopenshell.entity_instance, None] Example: @@ -73,37 +82,43 @@ class Usecase: ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1) # And referenced in the others - ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey2) - ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey3) + ifcopenshell.api.run( + "spatial.reference_structure", model, products=[column], relating_structure=[storey2, storey3] + ) """ self.file = file self.settings = { - "product": product, + "products": products, "relating_structure": relating_structure, } - def execute(self): - referenced_in_structures = self.settings["product"].ReferencedInStructures - references_elements = self.settings["relating_structure"].ReferencesElements + def execute(self) -> Union[ifcopenshell.entity_instance, None]: + structure = self.settings["relating_structure"] + products = set(self.settings["products"]) - for rel in referenced_in_structures: - if rel.RelatingStructure == self.settings["relating_structure"]: - return + if not products: + return - if references_elements: - related_elements = list(references_elements[0].RelatedElements) - related_elements.append(self.settings["product"]) - references_elements[0].RelatedElements = related_elements - ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": references_elements[0]}) - else: - references_elements = self.file.create_entity( + referenced = ifcopenshell.util.element.get_structure_referenced_elements(structure) + products_to_assign = products - referenced + rel = next(iter(structure.ReferencesElements), None) + + if not products_to_assign: + return rel + + if rel is None: + rel = self.file.create_entity( "IfcRelReferencedInSpatialStructure", **{ "GlobalId": ifcopenshell.guid.new(), "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), - "RelatedElements": [self.settings["product"]], - "RelatingStructure": self.settings["relating_structure"], + "RelatedElements": list(products_to_assign), + "RelatingStructure": structure, } ) + else: + related_elements = set(rel.RelatedElements) | products_to_assign + rel.RelatedElements = list(related_elements) + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) - return references_elements + return rel diff --git a/src/ifcopenshell-python/test/api/spatial/test_dereference_structure.py b/src/ifcopenshell-python/test/api/spatial/test_dereference_structure.py index 1cf2f3108e..d4d963222f 100644 --- a/src/ifcopenshell-python/test/api/spatial/test_dereference_structure.py +++ b/src/ifcopenshell-python/test/api/spatial/test_dereference_structure.py @@ -26,7 +26,7 @@ class TestDereferenceStructure(test.bootstrap.IFC4): def test_removing_a_container(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[subelement], relating_structure=element) ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element) assert ifcopenshell.util.element.get_referenced_structures(subelement) == [] @@ -40,14 +40,14 @@ class TestDereferenceStructure(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement1, relating_structure=element) - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement2, relating_structure=element) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[subelement1], relating_structure=element) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[subelement2], relating_structure=element) ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement1, relating_structure=element) assert self.file.by_type("IfcRelReferencedInSpatialStructure")[0].RelatedElements == (subelement2,) def test_deleting_the_rel_when_a_container_is_removed_with_no_elements(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[subelement], relating_structure=element) ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element) assert len(self.file.by_type("IfcRelReferencedInSpatialStructure")) == 0 diff --git a/src/ifcopenshell-python/test/api/spatial/test_reference_structure.py b/src/ifcopenshell-python/test/api/spatial/test_reference_structure.py index 21b6b0e8e8..0ef22d708a 100644 --- a/src/ifcopenshell-python/test/api/spatial/test_reference_structure.py +++ b/src/ifcopenshell-python/test/api/spatial/test_reference_structure.py @@ -26,25 +26,39 @@ class TestReferenceStructure(test.bootstrap.IFC4): def test_referencing_a_structure(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - rel = ifcopenshell.api.run( - "spatial.reference_structure", self.file, product=subelement, relating_structure=element + subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "spatial.reference_structure", self.file, products=[subelement, subelement2], relating_structure=element ) - assert ifcopenshell.util.element.get_referenced_structures(subelement) == [element] - assert rel.is_a("IfcRelReferencedInSpatialStructure") + assert ifcopenshell.util.element.get_structure_referenced_elements(element) == {subelement, subelement2} def test_doing_nothing_if_the_structure_is_already_referenced(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element) + subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "spatial.reference_structure", self.file, products=[subelement, subelement2], relating_structure=element + ) total_elements = len([e for e in self.file]) - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element) + ifcopenshell.api.run( + "spatial.reference_structure", self.file, products=[subelement, subelement2], relating_structure=element + ) assert len([e for e in self.file]) == total_elements def test_that_old_relationships_are_updated_if_they_still_contain_elements(self): - element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "spatial.reference_structure", self.file, products=[subelement1], relating_structure=element + ) subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement1, relating_structure=element1) - ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement2, relating_structure=element1) + subelement3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "spatial.reference_structure", self.file, products=[subelement2, subelement3], relating_structure=element + ) rel = subelement1.ReferencedInStructures[0] - assert len(rel.RelatedElements) == 2 + assert len(rel.RelatedElements) == 3 + + +class TestReferenceStructureIFC2X3(test.bootstrap.IFC2X3, TestReferenceStructure): + pass diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index 68fe0202c4..e7cfeaf85f 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -270,3 +270,13 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference) assert not element.HasAssociations assert not len(self.file.by_type("IfcRelAssociatesDocument")) + + @deprecation_check + def test_referencing_a_structure(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run( + "spatial.reference_structure", self.file, product=subelement, relating_structure=element + ) + assert ifcopenshell.util.element.get_referenced_structures(subelement) == [element] + assert rel.is_a("IfcRelReferencedInSpatialStructure") diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 8efbeda360..ccb548a076 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -719,10 +719,10 @@ class TestGetReferencedStructures(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") assert subject.get_referenced_structures(element) == [] building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=element, relating_structure=building) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element], relating_structure=building) assert subject.get_referenced_structures(element) == [building] building2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=element, relating_structure=building2) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element], relating_structure=building2) assert subject.get_referenced_structures(element) == [building, building2] @@ -735,10 +735,10 @@ class TestGetStructureReferencedElements(test.bootstrap.IFC4): building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") assert subject.get_structure_referenced_elements(building) == set() element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=element, relating_structure=building) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element], relating_structure=building) assert subject.get_structure_referenced_elements(building) == {element} element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("spatial.reference_structure", self.file, product=element2, relating_structure=building) + ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element2], relating_structure=building) assert subject.get_structure_referenced_elements(building) == {element, element2}