Completely untested refactor of #5257.

Removes dependency of file to util (util should depend on file, not vice
versa). Removes auxiliary method.
This commit is contained in:
Dion Moult
2025-01-26 21:11:11 +11:00
parent ecd72912aa
commit 4af6c24314
2 changed files with 50 additions and 129 deletions
+1 -60
View File
@@ -33,7 +33,6 @@ from typing import Union
from . import ifcopenshell_wrapper from . import ifcopenshell_wrapper
from .entity_instance import entity_instance from .entity_instance import entity_instance
from .util.unit import get_measure_unit_type
if TYPE_CHECKING: if TYPE_CHECKING:
import ifcopenshell.util.schema import ifcopenshell.util.schema
@@ -223,7 +222,7 @@ class file:
""" """
wrapped_data: ifcopenshell_wrapper.file wrapped_data: ifcopenshell_wrapper.file
_units: file_units | None = None units: dict[str: ifcopenshell.entity_instance] = None
history_size: int = 64 history_size: int = 64
def __init__( def __init__(
@@ -463,28 +462,6 @@ class file:
version.append(int(number.group(1)) if number else 0) version.append(int(number.group(1)) if number else 0)
return tuple(version) return tuple(version)
@property
def units(self) -> file_units:
"""
A class which dynamically stores unit information
and provides methods to compute unit-entities associated to types.
"""
if self._units is None:
self._units = file_units(self)
return self._units
def unit_by_measure_class(
self,
measure_class: str | None = None,
/,
) -> ifcopenshell.entity_instance | None:
"""
Helper method to obtain unit-entity either directly
or indirectly via measure class.
"""
if measure_class is not None:
return self.units.by_measure_type(measure_class)
def __getattr__(self, attr) -> Union[Any, Callable[..., ifcopenshell.entity_instance]]: def __getattr__(self, attr) -> Union[Any, Callable[..., ifcopenshell.entity_instance]]:
if attr[0:6] == "create": if attr[0:6] == "create":
return functools.partial(self.create_entity, attr[6:]) return functools.partial(self.create_entity, attr[6:])
@@ -724,39 +701,3 @@ class file:
def to_string(self) -> str: def to_string(self) -> str:
return self.wrapped_data.to_string() return self.wrapped_data.to_string()
class file_units:
"""
A class which provides methods to compute unit-entities associated to types.
"""
_unit_assignment: dict[str, entity_instance] = {}
def __init__(self, ifc_file: file):
self._load_unit_assignment(ifc_file)
def _load_unit_assignment(self, ifc_file: file, /):
"""
A mapping as dictionary which associates
to basic UnitTypes a unit-entity in the IFC file.
"""
entities = self.by_type("IfcUnitAssignment")
base = next(iter(entities), None)
units = getattr(base, "Units", None) or ()
units_and_types = [(u, getattr(u, "UnitType", None)) for u in units if isinstance(u, entity_instance)]
self._unit_assignment = {t: u for u, t in units_and_types if isinstance(t, str)}
return
def by_type(self, t: str, /) -> entity_instance | None:
"""
Returns the unit entity associated to a basic type
"""
return self._unit_assignment.get(t, None)
def by_measure_type(self, t: str, /) -> entity_instance | None:
"""
Returns the unit entity associated to a measure type
"""
t = get_measure_unit_type(t)
return self.by_type(t)
@@ -412,7 +412,19 @@ def get_unit_assignment(ifc_file: ifcopenshell.file) -> Union[ifcopenshell.entit
return ifc_file.by_type("IfcProject")[0].UnitsInContext return ifc_file.by_type("IfcProject")[0].UnitsInContext
def get_project_unit(ifc_file: ifcopenshell.file, unit_type: str) -> Union[ifcopenshell.entity_instance, None]: 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)}
def clear_unit_cache(ifc_file: ifcopenshell.file) -> None:
ifc_file.units = {}
def get_project_unit(
ifc_file: ifcopenshell.file, unit_type: str, use_cache: bool = False
) -> 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
:param ifc_file: The IFC file. :param ifc_file: The IFC file.
@@ -421,6 +433,10 @@ def get_project_unit(ifc_file: ifcopenshell.file, unit_type: str) -> Union[ifcop
:return: The IFC unit entity, or nothing if there is no default project :return: The IFC unit entity, or nothing if there is no default project
unit defined. unit defined.
""" """
if use_cache and not ifc_file.units:
cache_units(ifc_file)
if units := ifc_file.units:
return units.get(unit_type, None)
if unit_assignment := get_unit_assignment(ifc_file): if unit_assignment := get_unit_assignment(ifc_file):
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:
@@ -428,8 +444,7 @@ def get_project_unit(ifc_file: ifcopenshell.file, unit_type: str) -> Union[ifcop
def get_property_unit( def get_property_unit(
prop: ifcopenshell.entity_instance, prop: ifcopenshell.entity_instance, ifc_file: ifcopenshell.file, use_cache: bool = False
ifc_file: ifcopenshell.file
) -> Union[ifcopenshell.entity_instance, None]: ) -> Union[ifcopenshell.entity_instance, None]:
"""Gets the unit definition of a property or quantity """Gets the unit definition of a property or quantity
@@ -437,53 +452,43 @@ def get_property_unit(
This unit may be defined at the property itself explicitly, or if not This unit may be defined at the property itself explicitly, or if not
specified, fallback to the project default. specified, fallback to the project default.
:param prop: The property instance. You can fetch this via the instance ID :param prop: The IfcProperty instance. You can fetch this via the instance
if doing :func:`ifcopenshell.util.element.get_psets` with ID if doing :func:`ifcopenshell.util.element.get_psets` with
``verbose=True``. ``verbose=True``.
:param ifc_file: The IFC file being used. This is necessary to check :param ifc_file: The IFC file being used. This is necessary to check
default project units. default project units.
:return: The IFC unit entity, or nothing if there is no default project :return: The IFC unit entity, or nothing if there is no default project
unit defined. unit defined.
""" """
unit = prop.Unit if unit := getattr(prop, "Unit", None):
if isinstance(unit, ifcopenshell.entity_instance):
return unit return unit
value = None value = None
measure_class = None measure_class = None
# DEV-NOTE: Using .is_a() is wrong, as it tells us nothing about super class membership
if prop.is_a("IfcPhysicalSimpleQuantity"): if prop.is_a("IfcPhysicalSimpleQuantity"):
# get underlying object
entity = prop.wrapped_data.declaration().as_entity() entity = prop.wrapped_data.declaration().as_entity()
# extract measure class
measure_class = entity.attribute_by_index(3).type_of_attribute().declared_type().name() measure_class = entity.attribute_by_index(3).type_of_attribute().declared_type().name()
elif prop.is_a("IfcPropertySingleValue"): elif prop.is_a("IfcPropertySingleValue"):
value = prop.NominalValue measure_class = prop.NominalValue.is_a()
elif prop.is_a("IfcPropertyEnumeratedValue"): elif prop.is_a("IfcPropertyEnumeratedValue"):
unit = prop.EnumerationReference.Unit if unit := prop.EnumerationReference.Unit:
value = next(iter(prop.EnumerationValues or ()), None) return unit
if value := next(iter(prop.EnumerationValues or ()), None):
measure_class = value.is_a()
elif prop.is_a("IfcPropertyListValue"): elif prop.is_a("IfcPropertyListValue"):
value = next(iter(prop.ListValues or ()), None) if value := next(iter(prop.ListValues or ()), None):
measure_class = value.is_a()
elif prop.is_a("IfcPropertyBoundedValue"): elif prop.is_a("IfcPropertyBoundedValue"):
value = prop.UpperBoundValue or prop.LowerBoundValue or prop.SetPointValue if value := (prop.UpperBoundValue or prop.LowerBoundValue or prop.SetPointValue):
measure_class = value.is_a()
unit = _auxiliary_method_compute_unit( if measure_class and (unit_type := get_measure_unit_type(measure_class)):
ifc_file, return get_project_unit(ifc_file, unit_type)
unit=unit,
value=value,
measure_class=measure_class,
)
return unit
def get_property_table_unit( def get_property_table_unit(
prop: ifcopenshell.entity_instance, prop: ifcopenshell.entity_instance, ifc_file: ifcopenshell.file, use_cache: bool = False
ifc_file: ifcopenshell.file
) -> Dict[str, Union[ifcopenshell.entity_instance, None]]: ) -> Dict[str, Union[ifcopenshell.entity_instance, None]]:
""" """
Gets the unit definition of a property table Gets the unit definition of a property table
@@ -503,25 +508,24 @@ def get_property_table_unit(
If a unit-entity is missing, If a unit-entity is missing,
the value associated to the key is `null`. the value associated to the key is `null`.
""" """
if prop.is_a("IfcPropertyTableValue"): defining_unit = None
unit = prop.DefiningUnit if unit := prop.DefiningUnit:
value = next(iter(prop.DefiningValues or ()), None) defining_unit = unit
unit_defining = _auxiliary_method_compute_unit(ifc_file, unit=unit, value=value) elif value := next(iter(prop.DefiningValues or ()), None):
if unit_type := get_measure_unit_type(value.is_a()):
defining_unit = get_project_unit(ifc_file, unit_type)
unit = prop.DefinedUnit defined_unit = None
value = next(iter(prop.DefinedValues or ()), None) if unit := prop.DefinedUnit:
unit_defined = _auxiliary_method_compute_unit(ifc_file, unit=unit, value=value) defined_unit = unit
elif value := next(iter(prop.DefinedValues or ()), None):
if unit_type := get_measure_unit_type(value.is_a()):
defining_unit = get_project_unit(ifc_file, unit_type)
units = { return {
"DefiningUnit": unit_defining, "DefiningUnit": defining_unit,
"DefinedUnit": unit_defined, "DefinedUnit": defined_unit,
} }
# currently no other case
else:
units = {}
return units
def get_unit_measure_class(unit_type: str) -> MEASURE_CLASS: def get_unit_measure_class(unit_type: str) -> MEASURE_CLASS:
@@ -876,27 +880,3 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str = "
ifcopenshell.util.element.remove_deep2(file_patched, old_length) ifcopenshell.util.element.remove_deep2(file_patched, old_length)
return file_patched return file_patched
# ----------------------------------------------------------------
# AUXILIARY METHODS
# ----------------------------------------------------------------
def _auxiliary_method_compute_unit(
ifc_file: ifcopenshell.file,
/,
*,
unit: ifcopenshell.entity_instance | None = None,
value: ifcopenshell.entity_instance | None = None,
measure_class: str | None = None,
) -> ifcopenshell.entity_instance | None:
"""
Helper method to obtain unit-entity either directly
or indirectly via measure class.
"""
if isinstance(unit, ifcopenshell.entity_instance):
return unit
if isinstance(value, ifcopenshell.entity_instance):
measure_class = measure_class or value.is_a()
return ifc_file.unit_by_measure_class(measure_class)