mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-29 00:03:17 +00:00
Add dimensional-analysis fallback to get_project_unit() for IfcDerivedUnit
This commit is contained in:
@@ -466,7 +466,16 @@ def cache_units(ifc_file: ifcopenshell.file) -> None:
|
||||
"""
|
||||
ifc_file.units = {}
|
||||
if assignment := get_unit_assignment(ifc_file):
|
||||
ifc_file.units = {u.UnitType: u for u in assignment.Units if getattr(u, "UnitType", None)}
|
||||
all_units = assignment.Units or []
|
||||
units = {u.UnitType: u for u in all_units if getattr(u, "UnitType", None)}
|
||||
# As in get_project_unit(): a literal match always wins; an IfcDerivedUnit with no
|
||||
# literal UnitType match is matched dimensionally instead, only to fill a gap.
|
||||
for unit in all_units:
|
||||
if unit.is_a("IfcDerivedUnit"):
|
||||
dimension = identify_unit_dimensions(unit)
|
||||
if dimension and dimension not in units:
|
||||
units[dimension] = unit
|
||||
ifc_file.units = units
|
||||
|
||||
|
||||
def clear_unit_cache(ifc_file: ifcopenshell.file) -> None:
|
||||
@@ -482,6 +491,10 @@ def get_project_unit(
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get the default project unit of a particular unit type
|
||||
|
||||
IfcDerivedUnit is matched first by a literal `UnitType` match, then, as a fallback, by
|
||||
dimensional analysis (:func:`identify_unit_dimensions`), mirroring
|
||||
:func:`get_candidate_units`.
|
||||
|
||||
:param ifc_file: The IFC file.
|
||||
:param unit_type: The type of unit, taken from the list of IFC unit types,
|
||||
such as "LENGTHUNIT".
|
||||
@@ -493,9 +506,13 @@ def get_project_unit(
|
||||
if units := ifc_file.units:
|
||||
return units.get(unit_type, None)
|
||||
if unit_assignment := get_unit_assignment(ifc_file):
|
||||
dimensional_match = None
|
||||
for unit in unit_assignment.Units or []:
|
||||
if getattr(unit, "UnitType", None) == unit_type:
|
||||
return unit
|
||||
if dimensional_match is None and unit.is_a("IfcDerivedUnit") and identify_unit_dimensions(unit) == unit_type:
|
||||
dimensional_match = unit
|
||||
return dimensional_match
|
||||
|
||||
|
||||
def get_candidate_units(ifc_file: ifcopenshell.file, unit_type: str) -> list[ifcopenshell.entity_instance]:
|
||||
|
||||
@@ -94,6 +94,37 @@ class TestGetProjectUnit(test.bootstrap.IFC4):
|
||||
assert subject.get_project_unit(self.file, "LENGTHUNIT", use_cache=True) == length2
|
||||
assert self.file.units == {"LENGTHUNIT": length2, "AREAUNIT": area}
|
||||
|
||||
def test_area_and_volume_derived_from_length_are_matched_dimensionally(self):
|
||||
# AREAUNIT/VOLUMEUNIT have no IfcDerivedUnitEnum member, so a project whose area/volume
|
||||
# default is an IfcDerivedUnit has no literal UnitType match for either -- get_project_unit
|
||||
# must still resolve them by dimensional analysis, like get_candidate_units already does.
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
length = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT")
|
||||
area = ifcopenshell.api.unit.add_derived_unit(self.file, "USERDEFINED", "area-ish", {length: 2})
|
||||
volume = ifcopenshell.api.unit.add_derived_unit(self.file, "USERDEFINED", "volume-ish", {length: 3})
|
||||
ifcopenshell.api.unit.assign_unit(self.file, units=[length, area, volume])
|
||||
|
||||
assert subject.get_project_unit(self.file, "AREAUNIT") == area
|
||||
assert subject.get_project_unit(self.file, "VOLUMEUNIT") == volume
|
||||
|
||||
def test_literal_unit_type_match_takes_priority_over_dimensional(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
length = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT")
|
||||
literal_area = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="AREAUNIT")
|
||||
derived_area = ifcopenshell.api.unit.add_derived_unit(self.file, "USERDEFINED", "area-ish", {length: 2})
|
||||
ifcopenshell.api.unit.assign_unit(self.file, units=[length, literal_area, derived_area])
|
||||
|
||||
assert subject.get_project_unit(self.file, "AREAUNIT") == literal_area
|
||||
|
||||
def test_dimensional_fallback_also_applies_when_using_a_cache(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
length = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT")
|
||||
area = ifcopenshell.api.unit.add_derived_unit(self.file, "USERDEFINED", "area-ish", {length: 2})
|
||||
ifcopenshell.api.unit.assign_unit(self.file, units=[length, area])
|
||||
|
||||
assert subject.get_project_unit(self.file, "AREAUNIT", use_cache=True) == area
|
||||
assert self.file.units["AREAUNIT"] == area
|
||||
|
||||
|
||||
class TestGetCandidateUnits(test.bootstrap.IFC4):
|
||||
def test_returns_only_units_matching_the_unit_type(self):
|
||||
|
||||
Reference in New Issue
Block a user