diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 5f383e7154..656337cf5a 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -525,7 +525,8 @@ def get_property_unit( entity = prop.wrapped_data.declaration().as_entity() measure_class = entity.attribute_by_index(3).type_of_attribute().declared_type().name() elif prop.is_a("IfcPropertySingleValue"): - measure_class = prop.NominalValue.is_a() + if value := prop.NominalValue: + measure_class = value.is_a() elif prop.is_a("IfcPropertyEnumeratedValue"): if prop.EnumerationReference: if unit := prop.EnumerationReference.Unit: diff --git a/src/ifcopenshell-python/test/util/test_unit.py b/src/ifcopenshell-python/test/util/test_unit.py index d0ae57ab41..512d8b5ef7 100644 --- a/src/ifcopenshell-python/test/util/test_unit.py +++ b/src/ifcopenshell-python/test/util/test_unit.py @@ -123,6 +123,12 @@ class TestGetPropertyUnit(test.bootstrap.IFC4): prop.Unit = length2 assert subject.get_property_unit(prop, self.file) == length2 + def test_single_value_with_no_nominal_value(self): + # NominalValue is optional -- a property may be null. Must not crash. + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + prop = self.file.createIfcPropertySingleValue(Name="Foo", NominalValue=None) + assert subject.get_property_unit(prop, self.file) is None + def test_enumerated_value(self): ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") length = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT", prefix="MILLI") @@ -311,6 +317,13 @@ class TestGetUnitSymbol(test.bootstrap.IFC4): weird = ifcopenshell.api.unit.add_derived_unit(self.file, "USERDEFINED", "force per time", {force: 1, time: -1}) assert subject.get_unit_symbol(weird) == "N/s" + def test_context_dependent_userdefined_unit_is_not_shadowed_by_the_derived_unit_check(self): + # IfcContextDependentUnit (e.g. "each", "boxes") is a distinct entity + # from IfcDerivedUnit, so the is_a("IfcDerivedUnit") check added for + # derived-unit symbol composition must not shadow this fallback. + each = ifcopenshell.api.unit.add_context_dependent_unit(self.file, name="EACH") + assert subject.get_unit_symbol(each) == "EACH" + class TestIdentifyUnitDimensions(test.bootstrap.IFC4): def test_matches_a_named_unit_type_by_dimension(self):