diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index 3c399e1d10..06f3d01442 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -443,7 +443,9 @@ class Property(Facet): if isinstance(self.name, str): prop = pset_props.get(self.name) if prop == "UNKNOWN" and [ - p for p in inst.wrapped_data.file.by_id(pset_props["id"]).HasProperties if p.Name == self.name + p + for p in self.get_properties(inst.wrapped_data.file.by_id(pset_props["id"])) + if p.Name == self.name ][0].NominalValue.is_a("IfcLogical"): pass elif prop is not None and prop != "": @@ -457,30 +459,49 @@ class Property(Facet): break pset_entity = inst.wrapped_data.file.by_id(pset_props["id"]) - for prop_entity in pset_entity.HasProperties: - if ( - prop_entity.Name not in props[pset_name].keys() - or not prop_entity.is_a("IfcPropertySingleValue") - or prop_entity.NominalValue is None - ): - continue - data_type = prop_entity.NominalValue.is_a() + for prop_entity in self.get_properties(pset_entity): + if prop_entity.is_a("IfcPropertySingleValue"): + if prop_entity.Name not in props[pset_name].keys() or prop_entity.NominalValue is None: + continue - if data_type != self.measure: - is_pass = False - reason = {"type": "MEASURE", "actual": data_type} - break + data_type = prop_entity.NominalValue.is_a() - unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file) - if unit: - props[pset_name][prop_entity.Name] = ifcopenshell.util.unit.convert( - prop_entity.NominalValue.wrappedValue, - getattr(unit, "Prefix", None), - unit.Name, - None, - ifcopenshell.util.unit.si_type_names[unit.UnitType], - ) + if data_type != self.measure: + is_pass = False + reason = {"type": "MEASURE", "actual": data_type} + break + + unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file) + if unit: + props[pset_name][prop_entity.Name] = ifcopenshell.util.unit.convert( + prop_entity.NominalValue.wrappedValue, + getattr(unit, "Prefix", None), + unit.Name, + None, + ifcopenshell.util.unit.si_type_names[unit.UnitType], + ) + elif prop_entity.is_a("IfcPhysicalSimpleQuantity"): + if prop_entity.Name not in props[pset_name].keys(): + continue + + prop_schema = prop_entity.wrapped_data.declaration().as_entity() + data_type = prop_schema.attribute_by_index(3).type_of_attribute().declared_type().name() + + if data_type != self.measure: + is_pass = False + reason = {"type": "MEASURE", "actual": data_type} + break + + unit = ifcopenshell.util.unit.get_property_unit(prop_entity, inst.wrapped_data.file) + if unit: + props[pset_name][prop_entity.Name] = ifcopenshell.util.unit.convert( + prop_entity[3], + getattr(unit, "Prefix", None), + unit.Name, + None, + ifcopenshell.util.unit.si_type_names[unit.UnitType], + ) if not is_pass: break @@ -512,6 +533,14 @@ class Property(Facet): return PropertyResult(not is_pass, {"type": "PROHIBITED"}) return PropertyResult(is_pass, reason) + def get_properties(self, pset): + if pset.is_a("IfcPropertySet"): + return pset.HasProperties + elif pset.is_a("IfcElementQuantity"): + return pset.Quantities + elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"): + return pset.Properties + class Material(Facet): def __init__(self, value=None, uri=None, minOccurs=None, maxOccurs=None, instructions=None): diff --git a/src/ifctester/test/test_facet.py b/src/ifctester/test/test_facet.py index e0a0ad021f..d00cd5b7ca 100644 --- a/src/ifctester/test/test_facet.py +++ b/src/ifctester/test/test_facet.py @@ -881,6 +881,10 @@ class TestProperty: ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) run("A name check will match any property with any string value", facet=facet, inst=element, expected=True) + facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcLabel") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) run("A required facet checks all parameters as normal", facet=facet, inst=element, expected=True) facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcLabel", minOccurs=0, maxOccurs=0) run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=element, expected=False) @@ -1005,6 +1009,14 @@ class TestProperty: ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcDuration("P2D")}) run("Durations are treated as strings 1/2", facet=facet, inst=element, expected=False) + facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcLengthMeasure") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + qto = ifcopenshell.api.run("pset.add_qto", ifc, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_qto", ifc, qto=qto, properties={"Foo": ifc.createIfcLengthMeasure(42)}) + run("A name check will match any quantity with any value", facet=facet, inst=element, expected=True) + facet = Property(propertySet="Foo_Bar", name="Foo", measure="IfcAreaMeasure") + run("Quantities must also match the appropriate measure", facet=facet, inst=element, expected=False) + restriction = Restriction(options="Foo_.*", type="pattern") facet = Property(propertySet=restriction, name="Foo", measure="IfcLabel") element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") @@ -1288,7 +1300,9 @@ class TestPartOf: run("The aggregated part passes an aggregate relationship", facet=facet, inst=subelement, expected=True) run("A required facet checks all parameters as normal", facet=facet, inst=subelement, expected=True) + facet = PartOf(relation="IfcRelAggregates", minOccurs=0, maxOccurs=0) run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=subelement, expected=False) + facet = PartOf(relation="IfcRelAggregates", minOccurs=0) run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=subelement, expected=True)