mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 01:31:00 +00:00
Add per-property/quantity Unit-override support to edit_pset/edit_qto, plus unit-scale and candidate-unit helpers
edit_pset()'s unpack_unit_value() couldn't distinguish "no Unit dict was
passed" from "{"Unit": None, ...} passed to explicitly clear an existing
override" -- both collapsed to a bare None, and every consuming call site
checked truthiness, so there was no way to actually clear a previously-set
property Unit override once one existed. Fixed with a private _NO_UNIT
sentinel; bare (unwrapped) values still leave Unit untouched exactly as
before.
edit_qto() had no Unit-handling capability at all: neither
update_existing_property() nor add_new_properties() ever read or wrote a
quantity's Unit attribute. Added the same {"Unit": ..., "NominalValue": ...}
wrapped-dict convention edit_pset() already supports, disambiguated from
the pre-existing IfcPhysicalComplexQuantity dict convention
({"Discrimination": ..., "HasQuantities": ...}) by checking for a "Unit"
key -- a complex-quantity spec never contains one.
ifcopenshell.util.unit gains two small helpers:
- get_unit_scale(unit): dispatches to get_derived_unit_scale/
get_named_unit_scale depending on unit type, also used to de-duplicate
calculate_unit_scale()'s own inline dispatch of the same logic.
- get_candidate_units(ifc_file, unit_type): all units in a file matching a
given unit type, unlike get_project_unit()'s single-default lookup.
Adds regression tests for all of the above, including explicit-clear,
bare-value-preserves-override, and complex-quantity-routing-unaffected
cases.
This commit is contained in:
@@ -498,6 +498,32 @@ def get_project_unit(
|
||||
return unit
|
||||
|
||||
|
||||
def get_candidate_units(ifc_file: ifcopenshell.file, unit_type: str) -> list[ifcopenshell.entity_instance]:
|
||||
"""Get all units in the file usable as an override for `unit_type`.
|
||||
|
||||
Unlike :func:`get_project_unit`, this returns every matching unit defined
|
||||
in the file (e.g. both an mm and an m IfcSIUnit might be present), not
|
||||
just the one currently assigned as the project default.
|
||||
|
||||
IfcDerivedUnit is matched first by a literal `UnitType` match, then, as a
|
||||
fallback, by dimensional analysis (:func:`identify_unit_dimensions`) --
|
||||
that fallback only helps for the dimension families covered by
|
||||
`named_dimensions` (the core `IfcUnitEnum` types); it won't match e.g.
|
||||
`"MODULUSOFELASTICITYUNIT"` by dimension alone, only by literal `UnitType`.
|
||||
|
||||
:param ifc_file: The IFC file.
|
||||
:param unit_type: The type of unit, taken from the list of IFC unit
|
||||
types, such as "LENGTHUNIT", or an IfcDerivedUnitEnum value such as
|
||||
"MODULUSOFELASTICITYUNIT".
|
||||
:return: All matching IfcNamedUnit / IfcDerivedUnit entities in the file.
|
||||
"""
|
||||
candidates = [u for u in ifc_file.by_type("IfcNamedUnit") if getattr(u, "UnitType", None) == unit_type]
|
||||
for unit in ifc_file.by_type("IfcDerivedUnit"):
|
||||
if getattr(unit, "UnitType", None) == unit_type or identify_unit_dimensions(unit) == unit_type:
|
||||
candidates.append(unit)
|
||||
return candidates
|
||||
|
||||
|
||||
def get_property_unit(
|
||||
prop: ifcopenshell.entity_instance, ifc_file: Union[ifcopenshell.file, None], use_cache: bool = False
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
@@ -803,6 +829,21 @@ def get_derived_unit_scale(unit: ifcopenshell.entity_instance) -> float:
|
||||
return scale
|
||||
|
||||
|
||||
def get_unit_scale(unit: ifcopenshell.entity_instance) -> float:
|
||||
"""Get the scale factor to convert a value in `unit` to SI units.
|
||||
|
||||
Dispatches to :func:`get_derived_unit_scale` for IfcDerivedUnit, or
|
||||
:func:`get_named_unit_scale` otherwise (IfcSIUnit / IfcConversionBasedUnit,
|
||||
including chains).
|
||||
|
||||
:param unit: The unit to get the scale factor for.
|
||||
:returns: The scale factor.
|
||||
"""
|
||||
if unit.is_a("IfcDerivedUnit"):
|
||||
return get_derived_unit_scale(unit)
|
||||
return get_named_unit_scale(unit)
|
||||
|
||||
|
||||
def calculate_unit_scale(ifc_file: ifcopenshell.file, unit_type: str = "LENGTHUNIT") -> float:
|
||||
"""Returns a unit scale factor to convert to and from IFC project units and SI units.
|
||||
|
||||
@@ -834,10 +875,7 @@ def calculate_unit_scale(ifc_file: ifcopenshell.file, unit_type: str = "LENGTHUN
|
||||
for unit in units.Units:
|
||||
if getattr(unit, "UnitType", ...) != unit_type:
|
||||
continue
|
||||
if unit.is_a("IfcDerivedUnit"):
|
||||
unit_scale *= get_derived_unit_scale(unit)
|
||||
else:
|
||||
unit_scale *= get_named_unit_scale(unit)
|
||||
unit_scale *= get_unit_scale(unit)
|
||||
return unit_scale
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user