From 306ee3dfa4e2700800ea08f3212543fc9b0f80b9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 7 Sep 2023 12:18:32 +1000 Subject: [PATCH] You can now choose both input and output units for imperial formatting --- .../ifcopenshell/util/selector.py | 16 +++-- .../ifcopenshell/util/unit.py | 37 ++++++++---- .../test/util/test_selector.py | 3 +- .../test/util/test_unit.py | 59 +++++++++++++++++++ 4 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 src/ifcopenshell-python/test/util/test_unit.py diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index b0c2b2fb7e..46fcd6c9e8 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -120,7 +120,7 @@ format_grammar = lark.Lark( round: "round(" function "," NUMBER ")" format_length: metric_length | imperial_length metric_length: "metric_length(" function "," NUMBER "," NUMBER ")" - imperial_length: "imperial_length(" function "," NUMBER ["," ESCAPED_STRING] ")" + imperial_length: "imperial_length(" function "," NUMBER ["," ESCAPED_STRING "," ESCAPED_STRING] ")" lower: "lower(" function ")" upper: "upper(" function ")" title: "title(" function ")" @@ -195,17 +195,15 @@ class FormatTransformer(lark.Transformer): def imperial_length(self, args): if len(args) == 2: - imperial_unit = "foot" + input_unit = "foot" value, precision = args else: - value, precision, imperial_unit = args - if imperial_unit == "inch": - imperial_unit = "inch" - else: - imperial_unit = "foot" + value, precision, input_unit, output_unit = args + input_unit = "inch" if input_unit == "inch" else "foot" + output_unit = "inch" if output_unit == "inch" else "foot" return ifcopenshell.util.unit.format_length( - float(value), int(precision), unit_system="imperial", imperial_unit=imperial_unit + float(value), int(precision), unit_system="imperial", input_unit=input_unit, output_unit=output_unit ) @@ -821,7 +819,7 @@ class Selector: key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it. if isinstance(key, re.Pattern): - attribute = None # Should we support regex attributes? Probably not for now. + attribute = None # Should we support regex attributes? Probably not for now. else: attribute = getattr(value, key, None) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index d9c3c4259b..43eae4fd18 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -548,12 +548,18 @@ def calculate_unit_scale(ifc_file): def format_length( - value, precision, decimal_places=2, suppress_zero_inches=True, unit_system="imperial", imperial_unit="foot" + value, + precision, + decimal_places=2, + suppress_zero_inches=True, + unit_system="imperial", + input_unit="foot", + output_unit="foot", ): """Formats a length for readability and imperial formatting :param value: The value in meters if metric, or either decimal feet or - inches if imperial depending on imperial_unit. + inches if imperial depending on input_unit. :type value: float :param precision: How precise the format should be. I.e. round to nearest. For imperial, it is 1/Nth. E.g. 12 means to the nearest 1/12th of an @@ -566,15 +572,18 @@ def format_length( :type suppress_zero_inches: bool :param unit_system: Choose whether your value is "metric" or "imperial" :type unit_system: str - :param imperial_unit: If imperial, specify whether your value is "foot" or + :param input_unit: If imperial, specify whether your value is "foot" or "inch". - :type imperial_unit: str + :type input_unit: str + :param output_unit: If imperial, specify whether your value is "foot" to + format as both feet and inches, or "inch" if only inches should be + shown. """ if unit_system == "imperial": - if imperial_unit == "foot": + if input_unit == "foot": feet = int(value) inches = (value - feet) * 12 - elif imperial_unit == "inch": + elif input_unit == "inch": inches = value % 12 feet = int(round((value - inches) / 12)) @@ -587,13 +596,21 @@ def format_length( # If fraction is a whole number, format it accordingly if frac.denominator == 1: if suppress_zero_inches and frac.numerator == 0: - return f"{feet}'" - return f"{feet}' - {frac.numerator}\"" + if output_unit == "foot": + return f"{feet}'" + return f'{feet * 12}"' + if output_unit == "foot": + return f"{feet}' - {frac.numerator}\"" + return f'{(feet * 12) + frac.numerator}"' if frac.numerator > frac.denominator: remainder = frac.numerator % frac.denominator whole = int((frac.numerator - remainder) / frac.denominator) - return f"{feet}' - {whole} {remainder}/{frac.denominator}\"" - return f"{feet}' - {frac.numerator}/{frac.denominator}\"" + if output_unit == "foot": + return f"{feet}' - {whole} {remainder}/{frac.denominator}\"" + return f'{(feet * 12) + whole} {remainder}/{frac.denominator}"' + if output_unit == "foot": + return f"{feet}' - {frac.numerator}/{frac.denominator}\"" + return f'{feet * 12} {frac.numerator}/{frac.denominator}"' elif unit_system == "metric": rounded_val = round(value / precision) * precision return f"{rounded_val:.{decimal_places}f}" diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index ebb2528f15..4fd4972f75 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -45,7 +45,8 @@ class TestFormat(): assert subject.format('imperial_length(3.123, 1)') == "3' - 1\"" assert subject.format('imperial_length(3.123, 2)') == "3' - 1 1/2\"" assert subject.format('imperial_length(\"3.123\", 2)') == "3' - 1 1/2\"" - assert subject.format('imperial_length(\"123.123\", 2, \"inch\")') == "10' - 3\"" + assert subject.format('imperial_length(\"123.123\", 2, \"inch\", \"foot\")') == "10' - 3\"" + assert subject.format('imperial_length(\"123.123\", 2, \"inch\", \"inch\")') == "123\"" class TestGetElementValue(test.bootstrap.IFC4): diff --git a/src/ifcopenshell-python/test/util/test_unit.py b/src/ifcopenshell-python/test/util/test_unit.py new file mode 100644 index 0000000000..cdf028a222 --- /dev/null +++ b/src/ifcopenshell-python/test/util/test_unit.py @@ -0,0 +1,59 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2023 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import pytest +import test.bootstrap +import ifcopenshell.api +import ifcopenshell.util.unit as subject + + +class TestFormatLength(test.bootstrap.IFC4): + def test_run(self): + assert subject.format_length(1, 1, decimal_places=0, unit_system="metric") == "1" + assert subject.format_length(1, 1, decimal_places=2, unit_system="metric") == "1.00" + assert subject.format_length(3, 5, decimal_places=2, unit_system="metric") == "5.00" + assert subject.format_length(3.123, 0.01, decimal_places=2, unit_system="metric") == "3.12" + + assert subject.format_length(3, 1, unit_system="imperial", input_unit="foot") == "3'" + assert subject.format_length(3.5, 1, unit_system="imperial", input_unit="foot") == "3' - 6\"" + assert subject.format_length(3.123, 1, unit_system="imperial", input_unit="foot") == "3' - 1\"" + assert subject.format_length(3.123, 2, unit_system="imperial", input_unit="foot") == "3' - 1 1/2\"" + assert subject.format_length(3.123, 4, unit_system="imperial", input_unit="foot") == "3' - 1 1/2\"" + assert subject.format_length(3.123, 32, unit_system="imperial", input_unit="foot") == "3' - 1 15/32\"" + assert subject.format_length(24, 1, unit_system="imperial", input_unit="inch") == "2'" + assert subject.format_length(25.23, 1, unit_system="imperial", input_unit="inch") == "2' - 1\"" + assert subject.format_length(25.23, 4, unit_system="imperial", input_unit="inch") == "2' - 1 1/4\"" + + assert subject.format_length(3, 1, unit_system="imperial", input_unit="foot", output_unit="inch") == '36"' + assert subject.format_length(3.5, 1, unit_system="imperial", input_unit="foot", output_unit="inch") == '42"' + assert subject.format_length(3.123, 1, unit_system="imperial", input_unit="foot", output_unit="inch") == '37"' + assert ( + subject.format_length(3.123, 2, unit_system="imperial", input_unit="foot", output_unit="inch") == '37 1/2"' + ) + assert ( + subject.format_length(3.123, 4, unit_system="imperial", input_unit="foot", output_unit="inch") == '37 1/2"' + ) + assert ( + subject.format_length(3.123, 32, unit_system="imperial", input_unit="foot", output_unit="inch") + == '37 15/32"' + ) + assert subject.format_length(24, 1, unit_system="imperial", input_unit="inch", output_unit="inch") == '24"' + assert subject.format_length(25.23, 1, unit_system="imperial", input_unit="inch", output_unit="inch") == '25"' + assert ( + subject.format_length(25.23, 4, unit_system="imperial", input_unit="inch", output_unit="inch") == '25 1/4"' + )