From bade0647e82baef32d5d680723d2bd4f698abd81 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 5 Jul 2026 20:17:32 +0300 Subject: [PATCH] util.unit: scale RepresentationContext Precision on unit conversion #6127 IfcGeometricRepresentationContext.Precision is typed as a plain IfcReal but is interpreted in the project length unit, so the IfcLengthMeasure traversal in convert_file_length_units never touched it. A model converted from mm to m kept a Precision of e.g. 0.01 (fine in mm, huge in m), which breaks downstream geometry interpretation such as IfcConvert boolean cleanup. Subcontexts derive Precision from their parent, so only root contexts are scaled. Co-Authored-By: Claude Fable 5 --- .../ifcopenshell/util/unit.py | 7 +++++++ src/ifcopenshell-python/test/util/test_unit.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index dfadebe5f1..89e4466b6c 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -912,6 +912,13 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str = " new_value = convert_value(val) setattr(element, attr.name(), new_value) + # IfcGeometricRepresentationContext.Precision is typed as a plain IfcReal + # but is interpreted in the project length unit, so it must be scaled too. + # Subcontexts derive Precision from their parent and cannot be set. + for context in file_patched.by_type("IfcGeometricRepresentationContext", include_subtypes=False): + if context.Precision is not None: + context.Precision = convert_unit(context.Precision, old_length, new_length) + has_map_unit = False if ( ifc_file.schema == "IFC2X3" diff --git a/src/ifcopenshell-python/test/util/test_unit.py b/src/ifcopenshell-python/test/util/test_unit.py index 17f7fd9976..38d78ba024 100644 --- a/src/ifcopenshell-python/test/util/test_unit.py +++ b/src/ifcopenshell-python/test/util/test_unit.py @@ -19,6 +19,7 @@ from math import pi import numpy as np +import pytest import ifcopenshell.api.context import ifcopenshell.api.georeference @@ -258,6 +259,23 @@ class TestConvertFileLengthUnits(test.bootstrap.IFC2X3): assert max(i.id() for i in output) == len(output.wrapped_data.entity_names()) + 1 assert subject.get_full_unit_name(subject.get_project_unit(output, "LENGTHUNIT")) == "METRE" + def test_precision_conversion(self): + # Regression test for #6127: IfcGeometricRepresentationContext.Precision + # is typed IfcReal but interpreted in the project length unit, so it must + # be scaled along with the length measures. + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT", prefix="MILLI") + ifcopenshell.api.unit.assign_unit(self.file, units=[unit]) + context = ifcopenshell.api.context.add_context(self.file, context_type="Model") + context.Precision = 0.01 + # Subcontexts derive Precision from the parent and must be left alone. + ifcopenshell.api.context.add_context( + self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=context + ) + output = subject.convert_file_length_units(self.file, target_units="METER") + new_context = output.by_type("IfcGeometricRepresentationContext", include_subtypes=False)[0] + assert new_context.Precision == pytest.approx(0.00001) + def test_attribute_conversion(self): ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT", prefix="MILLI")