From 2eea7728d26c23a6ac4f66ac5555e4644f5fe1c5 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Thu, 9 Jul 2026 13:24:28 +0300 Subject: [PATCH] fix(selector): round() should not crash on non-numeric values (#6776) FormatTransformer.round() called Decimal() directly on the input value, which raises decimal.InvalidOperation when the value is a non-numeric string (a text property, or a value carrying a unit suffix like "12.5 m"). In a spreadsheet export this crashed the entire operation as soon as one element carried such a value. Now round() catches InvalidOperation and returns the value unchanged, the same graceful-fallback convention used by add(). Numeric rounding is unaffected. Co-Authored-By: Claude Sonnet 4.6 --- src/ifcopenshell-python/ifcopenshell/util/selector.py | 10 ++++++++-- src/ifcopenshell-python/test/util/test_selector.py | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 79b2e5ebac..6d3209ac3d 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -18,7 +18,7 @@ import re from collections.abc import Iterable -from decimal import Decimal +from decimal import Decimal, InvalidOperation from types import EllipsisType from typing import Any, Optional, Union @@ -316,7 +316,13 @@ class FormatTransformer(lark.Transformer): return value in ("true", "1", "yes") def round(self, args): - value = Decimal(0.0 if args[0] == "None" else args[0] or 0.0) + try: + value = Decimal(0.0 if args[0] == "None" else args[0] or 0.0) + except InvalidOperation: + # The value is not numeric (e.g. a text property, or a value with + # a unit suffix like "12.5 m"). Rounding is meaningless here, so + # return it unchanged instead of crashing the whole expression. + return args[0] nearest = Decimal(args[1]) result = round(value / nearest) * nearest if nearest % 1 == 0: diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index 3319e0df7a..485a1b886a 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -55,6 +55,9 @@ class TestFormat(test.bootstrap.IFC4): assert subject.format("round(123, 5)") == "125" assert subject.format('round("123", 5)') == "125" assert subject.format("round(-123, 5)") == "-125" + # Non-numeric values must pass through unchanged instead of crashing (#6776). + assert subject.format('round("Level 1", 0.01)') == "Level 1" + assert subject.format('round("12.5 m", 0.01)') == "12.5 m" assert subject.format("int(123.123)") == "123" assert subject.format("int(123)") == "123" assert subject.format("number(123)") == "123"