Fix ifcpatch unit conversion for lists of ifc entities #6280

This commit is contained in:
Andrej730
2025-03-06 15:34:23 +05:00
parent 14eb543662
commit 7458040949
2 changed files with 59 additions and 5 deletions
@@ -24,6 +24,7 @@ from typing import Iterable
from typing import Literal from typing import Literal
from typing import Optional from typing import Optional
from typing import Union from typing import Union
from typing import Generator
import ifcopenshell import ifcopenshell
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
@@ -803,9 +804,18 @@ def is_attr_type(
return None return None
def iter_element_and_attributes_per_type( FloatOrSequenceOfFloats = Union[float, tuple["FloatOrSequenceOfFloats", ...]]
ifc_file: ifcopenshell.file, attr_type_name: str
) -> Iterable[tuple[ifcopenshell.entity_instance, ifcopenshell_wrapper.attribute, Any]]:
def iter_element_and_attributes_per_type(ifc_file: ifcopenshell.file, attr_type_name: str) -> Generator[
tuple[
ifcopenshell.entity_instance,
ifcopenshell_wrapper.attribute,
Union[FloatOrSequenceOfFloats, ifcopenshell.entity_instance],
],
None,
None,
]:
schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier) schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema_identifier)
for element in ifc_file: for element in ifc_file:
@@ -826,6 +836,17 @@ def iter_element_and_attributes_per_type(
if isinstance(val, ifcopenshell.entity_instance) and not val.is_a(attr_type_name): if isinstance(val, ifcopenshell.entity_instance) and not val.is_a(attr_type_name):
continue continue
elif isinstance(val, tuple):
if not val:
continue
val_ = val[0]
# If it's a tuple of entities, just yield the entities we need to edit.
if isinstance(val_, ifcopenshell.entity_instance):
for val_ in val:
if not val_.is_a(attr_type_name):
continue
yield element, attr, val_
continue
yield element, attr, val yield element, attr, val
@@ -863,9 +884,10 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str = "
# Traverse all elements and their nested attributes in the file and convert them # Traverse all elements and their nested attributes in the file and convert them
for element, attr, val in iter_element_and_attributes_per_type(file_patched, "IfcLengthMeasure"): for element, attr, val in iter_element_and_attributes_per_type(file_patched, "IfcLengthMeasure"):
# NOTE: There is no risk of editing same entities twice as they're all recreated
# after file is reloaded as `file_patched`.
if isinstance(val, ifcopenshell.entity_instance): if isinstance(val, ifcopenshell.entity_instance):
new_value = convert_value(val.wrappedValue) val.wrappedValue = convert_value(val.wrappedValue)
getattr(element, attr.name()).wrappedValue = new_value
else: else:
new_value = convert_value(val) new_value = convert_value(val)
setattr(element, attr.name(), new_value) setattr(element, attr.name(), new_value)
@@ -247,6 +247,32 @@ class TestConvertFileLengthUnits(test.bootstrap.IFC2X3):
rectangle = builder.rectangle((100, 100)) rectangle = builder.rectangle((100, 100))
extrusion = builder.extrude(rectangle, 1000) extrusion = builder.extrude(rectangle, 1000)
# IfcLengthMeasure entities.
product = ifcopenshell.api.root.create_entity(self.file, "IfcWall")
pset = ifcopenshell.api.pset.add_pset(self.file, product, "TestPset")
# Consider weird case when same entity is reused.
length_measure = self.file.create_entity("IfcLengthMeasure", 50.0)
enum_property = self.file.create_entity(
"IfcPropertyEnumeratedValue",
Name="Enum",
# Not entirely sure if there are real life cases when mixed typed entities used in the list
# but just to be safe.
EnumerationValues=[
length_measure,
self.file.create_entity("IfcLabel", "TEXT"),
self.file.create_entity("IfcLengthMeasure", 250.0),
length_measure,
],
)
ifcopenshell.api.pset.edit_pset(
self.file,
pset,
properties={
"Length": self.file.create_entity("IfcLengthMeasure", 25.0),
"Enum": enum_property,
},
)
ifcopenshell.api.unit.assign_unit(self.file, units=[unit]) ifcopenshell.api.unit.assign_unit(self.file, units=[unit])
output = subject.convert_file_length_units(self.file, target_units="METER") output = subject.convert_file_length_units(self.file, target_units="METER")
assert subject.get_full_unit_name(subject.get_project_unit(output, "LENGTHUNIT")) == "METRE" assert subject.get_full_unit_name(subject.get_project_unit(output, "LENGTHUNIT")) == "METRE"
@@ -267,6 +293,12 @@ class TestConvertFileLengthUnits(test.bootstrap.IFC2X3):
points = rectangle.Points.CoordList points = rectangle.Points.CoordList
assert np.allclose(points, expected_points) assert np.allclose(points, expected_points)
# IfcLengthMeasure entities.
product = output.by_type("IfcWall")[0]
pset_data = ifcopenshell.util.element.get_pset(product, "TestPset")
assert pset_data["Length"] == 0.025
assert pset_data["Enum"] == [0.05, "TEXT", 0.25, 0.05]
def test_converting_map_conversion_if_there_is_no_map_unit(self): def test_converting_map_conversion_if_there_is_no_map_unit(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT", prefix="MILLI") unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT", prefix="MILLI")