diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 2611383ca0..09927210d9 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -95,9 +95,9 @@ def get_properties(properties): if prop.is_a("IfcPropertySingleValue"): results[prop.Name] = prop.NominalValue.wrappedValue if prop.NominalValue else None elif prop.is_a("IfcPropertyEnumeratedValue"): - values = [v.wrappedValue for v in prop.EnumerationValues] results[prop.Name] = [v.wrappedValue for v in prop.EnumerationValues] or None - + elif prop.is_a("IfcPropertyListValue"): + results[prop.Name] = [v.wrappedValue for v in prop.ListValues] or None elif prop.is_a("IfcComplexProperty"): data = {k: v for k, v in prop.get_info().items() if v is not None and k != "Name"} data["properties"] = get_properties(prop.HasProperties) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 102c87314c..37d67442d3 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -337,6 +337,10 @@ def get_property_unit(prop, ifc_file): measure_class = entity.attribute_by_index(3).type_of_attribute().declared_type().name() elif prop.is_a("IfcPropertySingleValue") and prop.NominalValue: measure_class = prop.NominalValue.is_a() + elif prop.is_a("IfcPropertyEnumeratedValue") and prop.EnumerationValues: + measure_class = prop.EnumerationValues[0].is_a() + elif prop.is_a("IfcPropertyListValue") and prop.ListValues: + measure_class = prop.ListValues[0].is_a() unit_type = get_measure_unit_type(measure_class) units = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == unit_type] if units: diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index a392fe36b9..e0667cd100 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -460,13 +460,11 @@ class Property(Facet): pset_entity = inst.wrapped_data.file.by_id(pset_props["id"]) - is_property_supported_class = False + is_property_supported_class = True for prop_entity in self.get_properties(pset_entity): + if prop_entity.Name not in props[pset_name].keys(): + continue if prop_entity.is_a("IfcPropertySingleValue"): - if prop_entity.Name not in props[pset_name].keys() or prop_entity.NominalValue is None: - continue - is_property_supported_class = True - data_type = prop_entity.NominalValue.is_a() if data_type != self.measure: @@ -484,10 +482,6 @@ class Property(Facet): 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 - is_property_supported_class = True - prop_schema = prop_entity.wrapped_data.declaration().as_entity() data_type = prop_schema.attribute_by_index(3).type_of_attribute().declared_type().name() @@ -505,6 +499,27 @@ class Property(Facet): None, ifcopenshell.util.unit.si_type_names[unit.UnitType], ) + elif prop_entity.is_a("IfcPropertyEnumeratedValue"): + data_type = prop_entity.EnumerationValues[0].is_a() + if data_type != self.measure: + is_pass = False + reason = {"type": "MEASURE", "actual": data_type} + break + elif prop_entity.is_a("IfcPropertyListValue"): + if not prop_entity.ListValues: + is_pass = False + reason = {"type": "NOVALUE"} + break + data_type = prop_entity.ListValues[0].is_a() + 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(v, getattr(unit, "Prefix", None), unit.Name, None, ifcopenshell.util.unit.si_type_names[unit.UnitType]) for v in props[pset_name][prop_entity.Name]] + else: + is_property_supported_class = False if not is_property_supported_class: is_pass = False @@ -516,11 +531,26 @@ class Property(Facet): if self.value: for value in props[pset_name].values(): if isinstance(self.value, str) and isinstance(value, str): + # "i_require_foo" = "i_have_bar" if value != self.value: is_pass = False reason = {"type": "VALUE", "actual": value} break + elif isinstance(self.value, str) and isinstance(value, list): + # "i_require_foo" = ["a", "b"] such as in enumerated properties + if self.value not in value: + is_pass = False + reason = {"type": "VALUE", "actual": value} + break + elif not isinstance(self.value, str) and isinstance(value, list): + # XSD restriction = ["a", "b"] such as in enumerated properties + does_any_pass = [v for v in value if v == self.value] + if not does_any_pass: + is_pass = False + reason = {"type": "VALUE", "actual": value} + break elif isinstance(self.value, str): + # "42" = 42 cast_value = cast_to_value(self.value, value) if isinstance(value, float) and isinstance(cast_value, float): if value < cast_value * (1.0 - 1e-6) or value > cast_value * (1.0 + 1e-6): @@ -532,6 +562,7 @@ class Property(Facet): reason = {"type": "VALUE", "actual": value} break elif value != self.value: + # XSD restriction = whatever is_pass = False reason = {"type": "VALUE", "actual": value} break diff --git a/src/ifctester/test/test_facet.py b/src/ifctester/test/test_facet.py index 424a1aaa5a..39ddb3ec9c 100644 --- a/src/ifctester/test/test_facet.py +++ b/src/ifctester/test/test_facet.py @@ -1009,6 +1009,36 @@ 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) + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Pset_WallCommon") + pset_template = ifcopenshell.util.pset.get_template("IFC4").get_by_name("Pset_WallCommon") + ifcopenshell.api.run( + "pset.edit_pset", + ifc, + pset=pset, + properties={"Status": ["EXISTING", "DEMOLISH"]}, + pset_template=pset_template, + ) + facet = Property(propertySet="Pset_WallCommon", name="Status", value="EXISTING", measure="IfcLabel") + run("Any matching value in an enumerated property will pass 1/3", facet=facet, inst=element, expected=True) + facet = Property(propertySet="Pset_WallCommon", name="Status", value="DEMOLISH", measure="IfcLabel") + run("Any matching value in an enumerated property will pass 2/3", facet=facet, inst=element, expected=True) + facet = Property(propertySet="Pset_WallCommon", name="Status", value="NEW", measure="IfcLabel") + run("Any matching value in an enumerated property will pass 3/3", facet=facet, inst=element, expected=False) + + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + list_property = ifc.createIfcPropertyListValue( + Name="Foo", ListValues=[ifc.createIfcLabel("X"), ifc.createIfcLabel("Y")] + ) + pset.HasProperties = [list_property] + facet = Property(propertySet="Foo_Bar", name="Foo", value="X", measure="IfcLabel") + run("Any matching value in a list property will pass 1/3", facet=facet, inst=element, expected=True) + facet = Property(propertySet="Foo_Bar", name="Foo", value="Y", measure="IfcLabel") + run("Any matching value in a list property will pass 2/3", facet=facet, inst=element, expected=True) + facet = Property(propertySet="Foo_Bar", name="Foo", value="Z", measure="IfcLabel") + run("Any matching value in a list property will pass 3/3", 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")