mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-11 06:18:09 +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 = {}
|
ifc_file.units = {}
|
||||||
if assignment := get_unit_assignment(ifc_file):
|
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:
|
def clear_unit_cache(ifc_file: ifcopenshell.file) -> None:
|
||||||
@@ -482,6 +491,10 @@ def get_project_unit(
|
|||||||
) -> Union[ifcopenshell.entity_instance, None]:
|
) -> Union[ifcopenshell.entity_instance, None]:
|
||||||
"""Get the default project unit of a particular unit type
|
"""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 ifc_file: The IFC file.
|
||||||
:param unit_type: The type of unit, taken from the list of IFC unit types,
|
:param unit_type: The type of unit, taken from the list of IFC unit types,
|
||||||
such as "LENGTHUNIT".
|
such as "LENGTHUNIT".
|
||||||
@@ -493,9 +506,13 @@ def get_project_unit(
|
|||||||
if units := ifc_file.units:
|
if units := ifc_file.units:
|
||||||
return units.get(unit_type, None)
|
return units.get(unit_type, None)
|
||||||
if unit_assignment := get_unit_assignment(ifc_file):
|
if unit_assignment := get_unit_assignment(ifc_file):
|
||||||
|
dimensional_match = None
|
||||||
for unit in unit_assignment.Units or []:
|
for unit in unit_assignment.Units or []:
|
||||||
if getattr(unit, "UnitType", None) == unit_type:
|
if getattr(unit, "UnitType", None) == unit_type:
|
||||||
return unit
|
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]:
|
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 subject.get_project_unit(self.file, "LENGTHUNIT", use_cache=True) == length2
|
||||||
assert self.file.units == {"LENGTHUNIT": length2, "AREAUNIT": area}
|
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):
|
class TestGetCandidateUnits(test.bootstrap.IFC4):
|
||||||
def test_returns_only_units_matching_the_unit_type(self):
|
def test_returns_only_units_matching_the_unit_type(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user