diff --git a/src/blenderbim/blenderbim/tool/qto.py b/src/blenderbim/blenderbim/tool/qto.py index 7132618401..24e565e757 100644 --- a/src/blenderbim/blenderbim/tool/qto.py +++ b/src/blenderbim/blenderbim/tool/qto.py @@ -79,6 +79,7 @@ class Qto(blenderbim.core.tool.Qto): product.is_a(), ifcopenshell.util.element.get_predefined_type(product), qto_only=True ) # See https://github.com/buildingSMART/IFC4.3.x-development/issues/851 for anomalies in Qto naming + # Should be in sync with cls.get_base_qto. applicable_qto: Union[str, None] = None for qto_name in applicable_qto_names: # No need for "Qto_" check since we use qto_only=True. @@ -181,14 +182,24 @@ class Qto(blenderbim.core.tool.Qto): def get_base_qto(cls, product: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: if not hasattr(product, "IsDefinedBy"): return + # Should be in sync with cls.get_applicable_base_quantity_name. + base_qto_definition = None + base_qto_definition_name: Union[str, None] = None for rel in product.IsDefinedBy or []: - if not ( - rel.is_a("IfcRelDefinesByProperties") - and "Base" in rel.RelatingPropertyDefinition.Name - and "Qto_" in rel.RelatingPropertyDefinition.Name - ): + definition = rel.RelatingPropertyDefinition + if not rel.is_a("IfcRelDefinesByProperties"): continue - return rel.RelatingPropertyDefinition + definition = rel.RelatingPropertyDefinition + definition_name = definition.Name + if "Qto_" not in definition_name: + continue + if "Base" in definition_name: + return definition + if base_qto_definition and "BodyGeometryValidation" not in base_qto_definition_name: + continue + base_qto_definition = definition + base_qto_definition_name = definition_name + return base_qto_definition @classmethod def get_related_cost_item_quantities(cls, product: ifcopenshell.entity_instance) -> list[dict]: diff --git a/src/blenderbim/test/tool/test_qto.py b/src/blenderbim/test/tool/test_qto.py index 93a998e5e7..4713fcea4b 100644 --- a/src/blenderbim/test/tool/test_qto.py +++ b/src/blenderbim/test/tool/test_qto.py @@ -226,7 +226,7 @@ class TestGetBaseQto(test.bim.bootstrap.NewFile): assert subject.get_base_qto(product).id() == pset_qto.get_info()["id"] assert subject.get_base_qto(product).Name == pset_qto.Name - def test_isempty(self): + def test_no_quantities(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) wall = ifc.createIfcWall() @@ -235,6 +235,46 @@ class TestGetBaseQto(test.bim.bootstrap.NewFile): product = tool.Ifc.get_entity(wall_obj) assert not subject.get_base_qto(product) == True + def test_anomaly_named_quantities(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + product = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBuildingElementProxy") + tool.Ifc.run( + "pset.add_qto", + product=product, + name="EQto_BodyGeometryValidation", + ) + tool.Ifc.run( + "pset.add_qto", + product=product, + name="Qto_BuildingElementProxyQuantities", + ) + # Prioritized over Qto_BodyGeometryValidation. + base_qto_name = subject.get_base_qto(product).Name + assert base_qto_name == "Qto_BuildingElementProxyQuantities" + # Ensure methods are in sync. + assert base_qto_name == subject.get_applicable_base_quantity_name(product) + + def test_prioritize_base_over_other_qto(self): + ifc = ifcopenshell.file(schema="IFC4X3") + tool.Ifc.set(ifc) + product = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + tool.Ifc.run( + "pset.add_qto", + product=product, + name="Qto_BodyGeometryValidation", + ) + tool.Ifc.run( + "pset.add_qto", + product=product, + name="Qto_WallBaseQuantities", + ) + # Prioritized over Qto_BodyGeometryValidation. + base_qto_name = subject.get_base_qto(product).Name + assert base_qto_name == "Qto_WallBaseQuantities" + # Ensure methods are in sync. + assert base_qto_name == subject.get_applicable_base_quantity_name(product) + class TestGetRelatedCostItemQuantities(test.bim.bootstrap.NewFile): def test_run(self):