mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Feature faster unit method ---> Main (#5257)
* feature_faster_unit_method > main: added constants * feature_faster_unit_method > main: extended `file` class to dynamically save unit information * feature_faster_unit_method > main: refactored `get_property_unit` method 1. split out case that returns the wrong type (dictionary of units) into its own method 2. cleaned up (but preserved) logic 3. refactored common part of all cases (the method which prioritises unit then value-entity then measure_class) --------- Co-authored-by: raj-open <raj-open@users.noreply.github.com> Co-authored-by: Dion Moult <dion@thinkmoult.com>
This commit is contained in:
@@ -24,10 +24,18 @@ import zipfile
|
|||||||
import functools
|
import functools
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Any, Union, Callable, Generator, Literal, TYPE_CHECKING
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
|
from typing import Generator
|
||||||
|
from typing import Optional
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
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.constants import IFC_TYPES
|
||||||
|
from .util.constants import IFC_UNIT_ASSIGNMENT
|
||||||
|
from .util.unit import get_measure_unit_type
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import ifcopenshell.util.schema
|
import ifcopenshell.util.schema
|
||||||
@@ -217,6 +225,7 @@ class file:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
wrapped_data: ifcopenshell_wrapper.file
|
wrapped_data: ifcopenshell_wrapper.file
|
||||||
|
_units: file_units | None = None
|
||||||
history_size: int = 64
|
history_size: int = 64
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -456,6 +465,28 @@ 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:])
|
||||||
@@ -695,3 +726,43 @@ 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(IFC_UNIT_ASSIGNMENT)
|
||||||
|
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)
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# IMPORTS
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# EXPORTS
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"IFC_APPLICATION",
|
||||||
|
"IFC_GRID_AXIS",
|
||||||
|
"IFC_PRODUCT",
|
||||||
|
"IFC_PROJECT",
|
||||||
|
"IFC_PROPERTY_SINGLE_VALUE",
|
||||||
|
"IFC_QUANTITY_AREA",
|
||||||
|
"IFC_QUANTITY_COUNT",
|
||||||
|
"IFC_QUANTITY_LENGTH",
|
||||||
|
"IFC_QUANTITY_TIME",
|
||||||
|
"IFC_QUANTITY_VOLUME",
|
||||||
|
"IFC_QUANTITY_WEIGHT",
|
||||||
|
"IFC_SI_UNIT",
|
||||||
|
"IFC_TYPES",
|
||||||
|
"IFC_UNIT_ASSIGNMENT",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# CONSTANTS
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
|
||||||
|
# TODO: complete this
|
||||||
|
IFC_APPLICATION = "IfcApplication"
|
||||||
|
IFC_GRID_AXIS = "IfcGridAxis"
|
||||||
|
IFC_PRODUCT = "IfcProduct"
|
||||||
|
IFC_PROJECT = "IfcProject"
|
||||||
|
IFC_PROPERTY_SINGLE_VALUE = "IfcPropertySingleValue"
|
||||||
|
IFC_QUANTITY_AREA = "IfcQuantityArea"
|
||||||
|
IFC_QUANTITY_COUNT = "IfcQuantityCount"
|
||||||
|
IFC_QUANTITY_LENGTH = "IfcQuantityLength"
|
||||||
|
IFC_QUANTITY_TIME = "IfcQuantityTime"
|
||||||
|
IFC_QUANTITY_VOLUME = "IfcQuantityVolume"
|
||||||
|
IFC_QUANTITY_WEIGHT = "IfcQuantityWeight"
|
||||||
|
IFC_SI_UNIT = "IfcSIUnit"
|
||||||
|
IFC_UNIT_ASSIGNMENT = "IfcUnitAssignment"
|
||||||
|
|
||||||
|
# TODO: complete this
|
||||||
|
IFC_TYPES = Literal[
|
||||||
|
"IfcApplication",
|
||||||
|
"IfcGridAxis",
|
||||||
|
"IfcProduct",
|
||||||
|
"IfcProject",
|
||||||
|
"IfcPropertySingleValue",
|
||||||
|
"IfcQuantityArea",
|
||||||
|
"IfcQuantityCount",
|
||||||
|
"IfcQuantityLength",
|
||||||
|
"IfcQuantityTime",
|
||||||
|
"IfcQuantityVolume",
|
||||||
|
"IfcQuantityWeight",
|
||||||
|
"IfcSIUnit",
|
||||||
|
"IfcUnitAssignment",
|
||||||
|
]
|
||||||
@@ -18,7 +18,12 @@
|
|||||||
|
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from math import pi
|
from math import pi
|
||||||
from typing import Iterable, Any, Union, Literal, Optional
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Iterable
|
||||||
|
from typing import Literal
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||||
@@ -423,7 +428,8 @@ 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, ifc_file: ifcopenshell.file
|
prop: ifcopenshell.entity_instance,
|
||||||
|
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
|
||||||
|
|
||||||
@@ -439,56 +445,83 @@ def get_property_unit(
|
|||||||
: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 = getattr(prop, "Unit", None)
|
unit = prop.Unit
|
||||||
if unit:
|
if isinstance(unit, ifcopenshell.entity_instance):
|
||||||
return unit
|
return unit
|
||||||
unit_assignment = get_unit_assignment(ifc_file)
|
|
||||||
if not unit_assignment:
|
value = None
|
||||||
return
|
|
||||||
entity = prop.wrapped_data.declaration().as_entity()
|
|
||||||
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()
|
||||||
|
# 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") and prop.NominalValue:
|
|
||||||
measure_class = prop.NominalValue.is_a()
|
elif prop.is_a("IfcPropertySingleValue"):
|
||||||
|
value = prop.NominalValue
|
||||||
|
|
||||||
elif prop.is_a("IfcPropertyEnumeratedValue"):
|
elif prop.is_a("IfcPropertyEnumeratedValue"):
|
||||||
if prop.EnumerationReference:
|
unit = prop.EnumerationReference.Unit
|
||||||
unit = getattr(prop.EnumerationReference, "Unit", None)
|
value = next(iter(prop.EnumerationValues or ()), None)
|
||||||
if unit:
|
|
||||||
return unit
|
elif prop.is_a("IfcPropertyListValue"):
|
||||||
if prop.EnumerationValues:
|
value = next(iter(prop.ListValues or ()), None)
|
||||||
measure_class = prop.EnumerationValues[0].is_a()
|
|
||||||
elif prop.is_a("IfcPropertyListValue") and prop.ListValues:
|
|
||||||
measure_class = prop.ListValues[0].is_a()
|
|
||||||
elif prop.is_a("IfcPropertyBoundedValue"):
|
elif prop.is_a("IfcPropertyBoundedValue"):
|
||||||
if prop.UpperBoundValue:
|
value = prop.UpperBoundValue or prop.LowerBoundValue or prop.SetPointValue
|
||||||
measure_class = prop.UpperBoundValue.is_a()
|
|
||||||
elif prop.LowerBoundValue:
|
unit = _auxiliary_method_compute_unit(
|
||||||
measure_class = prop.LowerBoundValue.is_a()
|
ifc_file,
|
||||||
elif prop.SetPointValue:
|
unit=unit,
|
||||||
measure_class = prop.SetPointValue.is_a()
|
value=value,
|
||||||
elif prop.is_a("IfcPropertyTableValue"):
|
measure_class=measure_class,
|
||||||
table_units = {}
|
)
|
||||||
for attribute in ["Defining", "Defined"]:
|
return unit
|
||||||
if getattr(prop, f"{attribute}Unit"):
|
|
||||||
table_units[f"{attribute}Unit"] = getattr(prop, f"{attribute}Unit")
|
|
||||||
elif getattr(prop, f"{attribute}Values"):
|
def get_property_table_unit(
|
||||||
measure_class = getattr(prop, f"{attribute}Values")[0].is_a()
|
prop: ifcopenshell.entity_instance,
|
||||||
unit_type = get_measure_unit_type(measure_class)
|
ifc_file: ifcopenshell.file
|
||||||
units = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == unit_type]
|
) -> Dict[str, Union[ifcopenshell.entity_instance, None]]:
|
||||||
if units:
|
"""
|
||||||
table_units[f"{attribute}Unit"] = units[0]
|
Gets the unit definition of a property table
|
||||||
else:
|
|
||||||
table_units[f"{attribute}Unit"] = None
|
Properties and quantities in psets and qtos can be associated with a unit.
|
||||||
else:
|
This unit may be defined at the property itself explicitly, or if not
|
||||||
table_units[f"{attribute}Unit"] = None
|
specified, fallback to the project default.
|
||||||
return table_units
|
|
||||||
if measure_class is None:
|
:param prop: The property instance. You can fetch this via the instance ID
|
||||||
return
|
if doing :func:`ifcopenshell.util.element.get_psets` with
|
||||||
unit_type = get_measure_unit_type(measure_class)
|
``verbose=True``.
|
||||||
units = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == unit_type]
|
|
||||||
if units:
|
:param ifc_file: The IFC file being used. This is necessary to check
|
||||||
return units[0]
|
default project units.
|
||||||
|
|
||||||
|
:return: A dictionary containing IFC unit entity by keyword.
|
||||||
|
If a unit-entity is missing,
|
||||||
|
the value associated to the key is `null`.
|
||||||
|
"""
|
||||||
|
if prop.is_a("IfcPropertyTableValue"):
|
||||||
|
unit = prop.DefiningUnit
|
||||||
|
value = next(iter(prop.DefiningValues or ()), None)
|
||||||
|
unit_defining = _auxiliary_method_compute_unit(ifc_file, unit=unit, value=value)
|
||||||
|
|
||||||
|
unit = prop.DefinedUnit
|
||||||
|
value = next(iter(prop.DefinedValues or ()), None)
|
||||||
|
unit_defined = _auxiliary_method_compute_unit(ifc_file, unit=unit, value=value)
|
||||||
|
|
||||||
|
units = {
|
||||||
|
"DefiningUnit": unit_defining,
|
||||||
|
"DefinedUnit": unit_defined,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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:
|
||||||
@@ -843,3 +876,27 @@ 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)
|
||||||
|
|||||||
Reference in New Issue
Block a user