diff --git a/src/blenderbim/blenderbim/tool/container.py b/src/blenderbim/blenderbim/tool/container.py index 8e964554bf..009aab48aa 100644 --- a/src/blenderbim/blenderbim/tool/container.py +++ b/src/blenderbim/blenderbim/tool/container.py @@ -60,12 +60,9 @@ class Container(blenderbim.core.tool.Container): for rel in parent.IsDecomposedBy or []: related_objects = [] for element in rel.RelatedObjects: - if element.ObjectPlacement: - related_objects.append( - (element, ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)[2][3]) - ) - else: - related_objects.append((element, float("-inf"))) + related_objects.append( + (element, ifcopenshell.util.placement.get_storey_elevation(element)) + ) related_objects = sorted(related_objects, key=lambda e: e[1]) for element in related_objects: element = element[0] diff --git a/src/ifcopenshell-python/ifcopenshell/util/placement.py b/src/ifcopenshell-python/ifcopenshell/util/placement.py index 52469d8fdf..1c34e7fe7d 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/placement.py +++ b/src/ifcopenshell-python/ifcopenshell/util/placement.py @@ -24,3 +24,10 @@ def get_local_placement(plc): else: parent = get_local_placement(plc.PlacementRelTo) return np.dot(parent, get_axis2placement(plc.RelativePlacement)) + + +def get_storey_elevation(storey): + if storey.ObjectPlacement: + matrix = get_local_placement(storey.ObjectPlacement) + return matrix[2][3] + return getattr(storey, "Elevation", 0.0) or 0.0 diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 0602014388..818ffd9f12 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -1,26 +1,26 @@ import pytest import test.bootstrap import ifcopenshell.api -import ifcopenshell.util.element +import ifcopenshell.util.element as subject class TestGetPsetsIFC4(test.bootstrap.IFC4): def test_getting_the_psets_of_a_product_as_a_dictionary(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - assert ifcopenshell.util.element.get_psets(element) == {} + assert subject.get_psets(element) == {} pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a": "b"}) - assert ifcopenshell.util.element.get_psets(element) == {"name": {"a": "b"}} + assert subject.get_psets(element) == {"name": {"a": "b"}} def test_getting_the_psets_of_a_product_type_as_a_dictionary(self): type_element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - assert ifcopenshell.util.element.get_psets(type_element) == {} + assert subject.get_psets(type_element) == {} pset = ifcopenshell.api.run("pset.add_pset", self.file, product=type_element, name="name") ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"x": "y"}) - assert ifcopenshell.util.element.get_psets(type_element) == {"name": {"x": "y"}} + assert subject.get_psets(type_element) == {"name": {"x": "y"}} def test_getting_psets_from_an_element_which_cannot_have_psets(self): - assert ifcopenshell.util.element.get_psets(self.file.create_entity("IfcPerson")) == {} + assert subject.get_psets(self.file.create_entity("IfcPerson")) == {} class TestGetPropertyDefinitionIFC4(test.bootstrap.IFC4): @@ -28,18 +28,18 @@ class TestGetPropertyDefinitionIFC4(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a": "b"}) - assert ifcopenshell.util.element.get_property_definition(pset) == {"a": "b"} + assert subject.get_property_definition(pset) == {"a": "b"} def test_getting_the_properties_of_a_qto(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="name") ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"x": 42}) - assert ifcopenshell.util.element.get_property_definition(qto) == {"x": 42} + assert subject.get_property_definition(qto) == {"x": 42} def test_getting_the_properties_of_a_predefined_pset(self): pset = self.file.create_entity("IfcDoorLiningProperties", ifcopenshell.guid.new()) pset.LiningDepth = 42 - assert ifcopenshell.util.element.get_property_definition(pset) == {"LiningDepth": 42} + assert subject.get_property_definition(pset) == {"LiningDepth": 42} class TestGetQuantitiesIFC4(test.bootstrap.IFC4): @@ -47,20 +47,20 @@ class TestGetQuantitiesIFC4(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name="name") ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={"x": 42}) - assert ifcopenshell.util.element.get_quantities(qto.Quantities) == {"x": 42} + assert subject.get_quantities(qto.Quantities) == {"x": 42} class TestGetPropertiesIFC4(test.bootstrap.IFC4): def test_getting_no_properties_when_none_are_available(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") - assert ifcopenshell.util.element.get_properties(pset.HasProperties) == {} + assert subject.get_properties(pset.HasProperties) == {} def test_getting_single_properties_from_a_list_of_properties(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="name") ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a": "b"}) - assert ifcopenshell.util.element.get_properties(pset.HasProperties) == {"a": "b"} + assert subject.get_properties(pset.HasProperties) == {"a": "b"} def test_getting_complex_properties_from_a_list_of_properties(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") @@ -68,7 +68,7 @@ class TestGetPropertiesIFC4(test.bootstrap.IFC4): complex_property = self.file.createIfcComplexProperty(Name="prop", UsageName="usage_name") ifcopenshell.api.run("pset.edit_pset", self.file, pset=complex_property, properties={"a": "b"}) pset.HasProperties = [complex_property] - assert ifcopenshell.util.element.get_properties(pset.HasProperties) == { + assert subject.get_properties(pset.HasProperties) == { "prop": { "UsageName": "usage_name", "id": 4, @@ -83,8 +83,8 @@ class TestGetTypeIFC4(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) - assert ifcopenshell.util.element.get_type(element) == element_type - assert ifcopenshell.util.element.get_type(element_type) == element_type + assert subject.get_type(element) == element_type + assert subject.get_type(element_type) == element_type class TestGetTypeIFC2X3(test.bootstrap.IFC2X3): @@ -92,8 +92,8 @@ class TestGetTypeIFC2X3(test.bootstrap.IFC2X3): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) - assert ifcopenshell.util.element.get_type(element) == element_type - assert ifcopenshell.util.element.get_type(element_type) == element_type + assert subject.get_type(element) == element_type + assert subject.get_type(element_type) == element_type class TestGetMaterial(test.bootstrap.IFC4): @@ -101,7 +101,7 @@ class TestGetMaterial(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") material = ifcopenshell.api.run("material.add_material", self.file) ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material) - assert ifcopenshell.util.element.get_material(element) == material + assert subject.get_material(element) == material def test_getting_a_material_list_of_a_product(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") @@ -109,31 +109,31 @@ class TestGetMaterial(test.bootstrap.IFC4): rel = ifcopenshell.api.run( "material.assign_material", self.file, product=element, type="IfcMaterialList", material=material ) - assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + assert subject.get_material(element) == rel.RelatingMaterial def test_getting_a_material_layer_set_of_a_product(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet") - assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + assert subject.get_material(element) == rel.RelatingMaterial def test_getting_a_material_profile_set_of_a_product(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet") - assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + assert subject.get_material(element) == rel.RelatingMaterial def test_getting_a_material_layer_set_usage_of_a_product(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") rel = ifcopenshell.api.run( "material.assign_material", self.file, product=element, type="IfcMaterialLayerSetUsage" ) - assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + assert subject.get_material(element) == rel.RelatingMaterial def test_getting_a_material_profile_set_usage_of_a_product(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") rel = ifcopenshell.api.run( "material.assign_material", self.file, product=element, type="IfcMaterialProfileSetUsage" ) - assert ifcopenshell.util.element.get_material(element) == rel.RelatingMaterial + assert subject.get_material(element) == rel.RelatingMaterial def test_getting_a_material_layer_set_indirectly_from_an_assigned_usage(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") @@ -141,7 +141,7 @@ class TestGetMaterial(test.bootstrap.IFC4): "material.assign_material", self.file, product=element, type="IfcMaterialLayerSetUsage" ) assert ( - ifcopenshell.util.element.get_material(element, should_skip_usage=True) == rel.RelatingMaterial.ForLayerSet + subject.get_material(element, should_skip_usage=True) == rel.RelatingMaterial.ForLayerSet ) def test_getting_a_material_profile_set_indirectly_from_an_assigned_usage(self): @@ -150,7 +150,7 @@ class TestGetMaterial(test.bootstrap.IFC4): "material.assign_material", self.file, product=element, type="IfcMaterialProfileSetUsage" ) assert ( - ifcopenshell.util.element.get_material(element, should_skip_usage=True) + subject.get_material(element, should_skip_usage=True) == rel.RelatingMaterial.ForProfileSet ) @@ -160,7 +160,7 @@ class TestGetMaterial(test.bootstrap.IFC4): ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) material = ifcopenshell.api.run("material.add_material", self.file) ifcopenshell.api.run("material.assign_material", self.file, product=element_type, material=material) - assert ifcopenshell.util.element.get_material(element) == material + assert subject.get_material(element) == material class TestGetContainerIFC4(test.bootstrap.IFC4): @@ -168,7 +168,7 @@ class TestGetContainerIFC4(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=building) - assert ifcopenshell.util.element.get_container(element) == building + assert subject.get_container(element) == building def test_getting_an_indirect_spatial_container_of_an_element(self): subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") @@ -176,7 +176,7 @@ class TestGetContainerIFC4(test.bootstrap.IFC4): building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=building) ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) - assert ifcopenshell.util.element.get_container(subelement) == building + assert subject.get_container(subelement) == building def test_getting_nothing_if_we_enforce_only_getting_direct_spatial_containers(self): subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") @@ -184,7 +184,7 @@ class TestGetContainerIFC4(test.bootstrap.IFC4): building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=building) ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) - assert ifcopenshell.util.element.get_container(subelement, should_get_direct=True) is None + assert subject.get_container(subelement, should_get_direct=True) is None class TestGetDecompositionIFC4(test.bootstrap.IFC4): @@ -194,7 +194,7 @@ class TestGetDecompositionIFC4(test.bootstrap.IFC4): building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=building) ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) - results = ifcopenshell.util.element.get_decomposition(building) + results = subject.get_decomposition(building) assert element in results assert subelement in results @@ -204,13 +204,13 @@ class TestGetAggregateIFC4(test.bootstrap.IFC4): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcCovering") ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) - assert ifcopenshell.util.element.get_aggregate(subelement) == element + assert subject.get_aggregate(subelement) == element class TestReplaceAttributeIFC4(test.bootstrap.IFC4): def test_replacing_an_elements_attribute(self): element = self.file.createIfcWall("foo") - ifcopenshell.util.element.replace_attribute(element, "foo", "bar") + subject.replace_attribute(element, "foo", "bar") assert element.GlobalId == "bar" def test_replacing_a_value_in_a_list(self): @@ -218,7 +218,7 @@ class TestReplaceAttributeIFC4(test.bootstrap.IFC4): new = self.file.createIfcWall() rel = self.file.createIfcRelAggregates() rel.RelatedObjects = [old] - ifcopenshell.util.element.replace_attribute(rel, old, new) + subject.replace_attribute(rel, old, new) assert rel.RelatedObjects == (new,) @@ -228,15 +228,15 @@ class TestHasElementReferenceIFC4(test.bootstrap.IFC4): new = self.file.createIfcWall() rel = self.file.createIfcRelAggregates() rel.RelatedObjects = [old] - assert ifcopenshell.util.element.has_element_reference(rel.RelatedObjects, old) is True - assert ifcopenshell.util.element.has_element_reference(rel.RelatedObjects, new) is False + assert subject.has_element_reference(rel.RelatedObjects, old) is True + assert subject.has_element_reference(rel.RelatedObjects, new) is False class TestRemoveDeepIFC4(test.bootstrap.IFC4): def test_removing_an_element_along_with_all_direct_attributes_recursively(self): owner = self.file.createIfcOwnerHistory() element = self.file.createIfcWall(GlobalId="id", OwnerHistory=owner) - ifcopenshell.util.element.remove_deep(self.file, element) + subject.remove_deep(self.file, element) with pytest.raises(RuntimeError): self.file.by_id(1) self.file.by_id(2) @@ -245,7 +245,7 @@ class TestRemoveDeepIFC4(test.bootstrap.IFC4): owner = self.file.createIfcOwnerHistory() element = self.file.createIfcWall(GlobalId="id1", OwnerHistory=owner) element2 = self.file.createIfcWall(GlobalId="id2", OwnerHistory=owner) - ifcopenshell.util.element.remove_deep(self.file, element) + subject.remove_deep(self.file, element) with pytest.raises(RuntimeError): self.file.by_guid("id1") assert self.file.by_id(1) @@ -255,7 +255,7 @@ class TestRemoveDeepIFC4(test.bootstrap.IFC4): class TestCopyIFC4(test.bootstrap.IFC4): def test_copying_an_element(self): element = self.file.createIfcWall(GlobalId="id", Name="name") - element2 = ifcopenshell.util.element.copy(self.file, element) + element2 = subject.copy(self.file, element) assert element.is_a() == element2.is_a() assert element.GlobalId != element2.GlobalId assert element.Name == element2.Name @@ -266,7 +266,7 @@ class TestCopyDeepIFC4(test.bootstrap.IFC4): owner = self.file.createIfcOwnerHistory() owner.State = "READWRITE" element = self.file.createIfcWall(GlobalId="id", Name="name", OwnerHistory=owner) - element2 = ifcopenshell.util.element.copy_deep(self.file, element) + element2 = subject.copy_deep(self.file, element) assert element.OwnerHistory != element2.OwnerHistory assert element.OwnerHistory.State == element2.OwnerHistory.State @@ -274,6 +274,6 @@ class TestCopyDeepIFC4(test.bootstrap.IFC4): element = self.file.createIfcWall(Name="name") rel = self.file.createIfcRelAggregates() rel.RelatedObjects = [element] - rel2 = ifcopenshell.util.element.copy_deep(self.file, rel) + rel2 = subject.copy_deep(self.file, rel) assert rel.RelatedObjects != rel2.RelatedObjects assert rel.RelatedObjects[0].Name == rel2.RelatedObjects[0].Name diff --git a/src/ifcopenshell-python/test/util/test_placement.py b/src/ifcopenshell-python/test/util/test_placement.py new file mode 100644 index 0000000000..861ed8f68c --- /dev/null +++ b/src/ifcopenshell-python/test/util/test_placement.py @@ -0,0 +1,25 @@ +import ifcopenshell +import test.bootstrap +import ifcopenshell.util.placement as subject + + +class TestGetStoreyElevationIFC4(test.bootstrap.IFC4): + def test_run(self): + storey = self.file.createIfcBuildingStorey() + placement = self.file.createIfcLocalPlacement() + placement.RelativePlacement = self.file.createIfcAxis2Placement3D( + self.file.createIfcCartesianPoint((0.0, 0.0, 3.0)) + ) + storey.ObjectPlacement = placement + assert subject.get_storey_elevation(storey) == 3.0 + + def test_getting_the_elevation_if_no_z_location(self): + storey = self.file.createIfcBuildingStorey() + storey.Elevation = 3.0 + assert subject.get_storey_elevation(storey) == 3.0 + + def test_returning_0_as_a_fallback(self): + storey = self.file.createIfcBuildingStorey() + assert subject.get_storey_elevation(storey) == 0.0 + building = self.file.createIfcBuilding() + assert subject.get_storey_elevation(building) == 0.0