From 8aecee583677ab999a549f40436fd2711ab13fa1 Mon Sep 17 00:00:00 2001 From: krande Date: Sat, 16 Dec 2023 13:37:38 +0100 Subject: [PATCH 1/8] initial proposal for updating units conversion --- .../ifcopenshell/util/unit.py | 97 +++++++++++++++++-- .../ifcpatch/recipes/ConvertLengthUnit.py | 42 +------- 2 files changed, 90 insertions(+), 49 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 28bc705f00..300f3a0501 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . +import ifcopenshell from math import pi from fractions import Fraction @@ -214,12 +215,10 @@ si_conversions = { "fahrenheit": 1.8, } - si_offsets = { "fahrenheit": -459.67, } - imperial_types = { "thou": "LENGTHUNIT", "inch": "LENGTHUNIT", @@ -260,7 +259,6 @@ imperial_types = { "fahrenheit": "THERMODYNAMICTEMPERATUREUNIT", } - prefix_symbols = { "EXA": "E", "PETA": "P", @@ -558,13 +556,13 @@ def calculate_unit_scale(ifc_file): def format_length( - value, - precision, - decimal_places=2, - suppress_zero_inches=True, - unit_system="imperial", - input_unit="foot", - output_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 @@ -624,3 +622,82 @@ def format_length( elif unit_system == "metric": rounded_val = round(value / precision) * precision return f"{rounded_val:.{decimal_places}f}" + + +def get_base_type_name( + content_type: ifcopenshell.ifcopenshell_wrapper.named_type | ifcopenshell.ifcopenshell_wrapper.type_declaration) -> ifcopenshell.ifcopenshell_wrapper.type_declaration | None: + cur_decl = content_type + while hasattr(cur_decl, "declared_type") is True: + cur_decl = cur_decl.declared_type() + if hasattr(cur_decl, "name") is False: + continue + if cur_decl.name() == "IfcLengthMeasure": + return cur_decl + + if isinstance(cur_decl, ifcopenshell.ifcopenshell_wrapper.aggregation_type): + res = cur_decl.type_of_element() + cur_decl = res.declared_type() + if hasattr(cur_decl, "name") and cur_decl.name() == "IfcLengthMeasure": + return cur_decl + while hasattr(cur_decl, "declared_type") is True: + cur_decl = cur_decl.declared_type() + if hasattr(cur_decl, "name") is False: + continue + if cur_decl.name() == "IfcLengthMeasure": + return cur_decl + + return None + + +def convert_file_units(ifc_file: ifcopenshell.file, target_units: str) -> ifcopenshell.file: + """Converts all units in an IFC file to the specified target units. Returns a new file.""" + prefix = "MILLI" if target_units == "MILLIMETERS" else None + + file_patched = ifcopenshell.api.run("project.create_file", version=ifc_file.schema) + if ifc_file.schema == "IFC2X3": + user = file_patched.add(ifc_file.by_type("IfcProject")[0].OwnerHistory.OwningUser) + application = file_patched.add(ifc_file.by_type("IfcProject")[0].OwnerHistory.OwningApplication) + old_get_user = ifcopenshell.api.owner.settings.get_user + old_get_application = ifcopenshell.api.owner.settings.get_application + ifcopenshell.api.owner.settings.get_user = lambda ifc: user + ifcopenshell.api.owner.settings.get_application = lambda ifc: application + + # Copy all elements from the original file to the patched file + for el in ifc_file: + file_patched.add(el) + + unit_assignment = ifcopenshell.util.unit.get_unit_assignment(file_patched) + + old_length = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == "LENGTHUNIT"][0] + new_length = ifcopenshell.api.run("unit.add_si_unit", file_patched, unit_type="LENGTHUNIT", prefix=prefix) + + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema) + # Traverse all elements and their nested attributes in the file and convert them + for element in file_patched: + entity = schema.declaration_by_name(element.is_a()) + attrs = entity.all_attributes() + for i, (attr, val, is_derived) in enumerate(zip(attrs, list(element), entity.derived())): + if is_derived: + continue + # Get all methods and attributes of the element + attr_type = attr.type_of_attribute() + base_type = get_base_type_name(attr_type) + if base_type is None: + continue + if val is None: + continue + if isinstance(val, tuple): + new_el = [ifcopenshell.util.unit.convert_unit(v, old_length, new_length) for v in val] + setattr(element, attr.name(), tuple(new_el)) + else: + new_el = ifcopenshell.util.unit.convert_unit(val, old_length, new_length) + # set the new value + setattr(element, attr.name(), new_el) + + file_patched.remove(old_length) + + if ifc_file.schema == "IFC2X3": + ifcopenshell.api.owner.settings.get_user = old_get_user + ifcopenshell.api.owner.settings.get_application = old_get_application + + return file_patched diff --git a/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py b/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py index b18f0f670e..4aa8ee45a4 100644 --- a/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py +++ b/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py @@ -21,6 +21,7 @@ import ifcopenshell.api import ifcopenshell.api.owner.settings import ifcopenshell.util.pset import ifcopenshell.util.element +import ifcopenshell.util.unit class Patcher: @@ -47,44 +48,7 @@ class Patcher: self.file = file self.logger = logger self.unit = unit + self.file_patched: ifcopenshell.file = None def patch(self): - unit = {"is_metric": "METERS" in self.unit, "raw": self.unit} - self.file_patched = ifcopenshell.api.run("project.create_file", version=self.file.schema) - if self.file.schema == "IFC2X3": - user = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningUser) - application = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningApplication) - old_get_user = ifcopenshell.api.owner.settings.get_user - old_get_application = ifcopenshell.api.owner.settings.get_application - ifcopenshell.api.owner.settings.get_user = lambda ifc: user - ifcopenshell.api.owner.settings.get_application = lambda ifc: application - project = ifcopenshell.api.run("root.create_entity", self.file_patched, ifc_class="IfcProject") - unit_assignment = ifcopenshell.api.run("unit.assign_unit", self.file_patched, **{"length": unit}) - - # Is there a better way? - for element in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): - element.Precision = 1e-8 - - # If we don't add openings first, they don't get converted - for element in self.file.by_type("IfcOpeningElement"): - self.file_patched.add(element) - - for element in self.file: - self.file_patched.add(element) - - new_length = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == "LENGTHUNIT"][0] - old_length = [ - u - for u in self.file_patched.by_type("IfcProject")[1].UnitsInContext.Units - if getattr(u, "UnitType", None) == "LENGTHUNIT" - ][0] - - for inverse in self.file_patched.get_inverse(old_length): - ifcopenshell.util.element.replace_attribute(inverse, old_length, new_length) - - self.file_patched.remove(old_length) - self.file_patched.remove(project) - - if self.file.schema == "IFC2X3": - ifcopenshell.api.owner.settings.get_user = old_get_user - ifcopenshell.api.owner.settings.get_application = old_get_application + self.file_patched = ifcopenshell.util.unit.convert_file_units(self.file, self.unit) From b11b3b7fa8e804e74e98a8874a673b606d5c01fa Mon Sep 17 00:00:00 2001 From: krande Date: Sat, 16 Dec 2023 13:47:18 +0100 Subject: [PATCH 2/8] update unit_assignment --- src/ifcopenshell-python/ifcopenshell/util/unit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 300f3a0501..1d770a06df 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -695,6 +695,7 @@ def convert_file_units(ifc_file: ifcopenshell.file, target_units: str) -> ifcope setattr(element, attr.name(), new_el) file_patched.remove(old_length) + unit_assignment.Units = tuple([new_length, *unit_assignment.Units]) if ifc_file.schema == "IFC2X3": ifcopenshell.api.owner.settings.get_user = old_get_user From c9552fac29034dae1bec6faa9dcebe446d6b5a5c Mon Sep 17 00:00:00 2001 From: krande Date: Sat, 16 Dec 2023 14:01:36 +0100 Subject: [PATCH 3/8] update name of function to make its purpose more clear --- src/ifcopenshell-python/ifcopenshell/util/unit.py | 2 +- src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 1d770a06df..0d49c2f654 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -649,7 +649,7 @@ def get_base_type_name( return None -def convert_file_units(ifc_file: ifcopenshell.file, target_units: str) -> ifcopenshell.file: +def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str) -> ifcopenshell.file: """Converts all units in an IFC file to the specified target units. Returns a new file.""" prefix = "MILLI" if target_units == "MILLIMETERS" else None diff --git a/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py b/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py index 4aa8ee45a4..48da1e1b37 100644 --- a/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py +++ b/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py @@ -51,4 +51,4 @@ class Patcher: self.file_patched: ifcopenshell.file = None def patch(self): - self.file_patched = ifcopenshell.util.unit.convert_file_units(self.file, self.unit) + self.file_patched = ifcopenshell.util.unit.convert_file_length_units(self.file, self.unit) From f5500da0af1ad79a3350bd304bf7b03365b065ea Mon Sep 17 00:00:00 2001 From: krande Date: Sat, 16 Dec 2023 14:18:13 +0100 Subject: [PATCH 4/8] remove ifc2x3 conditional in conversion utility --- src/ifcopenshell-python/ifcopenshell/util/unit.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 0d49c2f654..33ee3d9af3 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -654,14 +654,6 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str) -> prefix = "MILLI" if target_units == "MILLIMETERS" else None file_patched = ifcopenshell.api.run("project.create_file", version=ifc_file.schema) - if ifc_file.schema == "IFC2X3": - user = file_patched.add(ifc_file.by_type("IfcProject")[0].OwnerHistory.OwningUser) - application = file_patched.add(ifc_file.by_type("IfcProject")[0].OwnerHistory.OwningApplication) - old_get_user = ifcopenshell.api.owner.settings.get_user - old_get_application = ifcopenshell.api.owner.settings.get_application - ifcopenshell.api.owner.settings.get_user = lambda ifc: user - ifcopenshell.api.owner.settings.get_application = lambda ifc: application - # Copy all elements from the original file to the patched file for el in ifc_file: file_patched.add(el) @@ -697,8 +689,4 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str) -> file_patched.remove(old_length) unit_assignment.Units = tuple([new_length, *unit_assignment.Units]) - if ifc_file.schema == "IFC2X3": - ifcopenshell.api.owner.settings.get_user = old_get_user - ifcopenshell.api.owner.settings.get_application = old_get_application - return file_patched From 2ce7e1653760b4aafce49b2ff72995a270d35f6f Mon Sep 17 00:00:00 2001 From: krande Date: Sun, 17 Dec 2023 15:33:35 +0100 Subject: [PATCH 5/8] add fix for ci tests --- .github/workflows/ci.yml | 45 +++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b8026111b..891dfcca2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,9 +34,29 @@ jobs: runs-on: ubuntu-20.04 needs: activate steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: 3.11 + + - name: Locate Python Paths + run: | + echo "PYTHON_EXECUTABLE=$(which python)" >> $GITHUB_ENV + echo "PYTHON_INCLUDE_DIR=$(python -c 'import sysconfig; print(sysconfig.get_path(\"include\"))')" >> $GITHUB_ENV + echo "PYTHON_LIBRARY=$(python -c 'import sysconfig; print(sysconfig.get_config_var(\"LIBDIR\"))')/libpython3.11.so" >> $GITHUB_ENV + shell: bash + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil + pip install src/bcf --no-deps + pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip + - name: Install C++ dependencies run: | sudo apt update @@ -49,7 +69,6 @@ jobs: libboost-regex-dev \ libboost-system-dev \ libboost-thread-dev \ - python3-all-dev python3-pip \ swig libpcre3-dev libxml2-dev \ libtbb-dev nlohmann-json3-dev \ libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \ @@ -71,9 +90,9 @@ jobs: -DCMAKE_SYSTEM_PREFIX_PATH=/usr \ -DOCC_INCLUDE_DIR=/usr/include/opencascade \ -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \ - -DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \ - -DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \ + -DPYTHON_EXECUTABLE:FILEPATH=${{ env.PYTHON_EXECUTABLE }} \ + -DPYTHON_INCLUDE_DIR:PATH=${{ env.PYTHON_INCLUDE_DIR }} \ + -DPYTHON_LIBRARY:FILEPATH=${{ env.PYTHON_LIBRARY }} \ -DCOLLADA_SUPPORT=Off \ "-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \ -DGLTF_SUPPORT=On \ @@ -102,23 +121,11 @@ jobs: grep 0$ statuses grep 0$ statuses | wc -l - - name: Install Python dependencies - run: | - sudo /usr/bin/python -m pip install -U pip - sudo /usr/bin/python -m pip install xmlschema xsdata numpy lxml - sudo /usr/bin/python -m pip install src/bcf --no-deps - sudo /usr/bin/python -m pip install pytest - sudo /usr/bin/python -m pip install isodate - sudo /usr/bin/python -m pip install lark - sudo /usr/bin/python -m pip install networkx - sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip - sudo /usr/bin/python -m pip install tabulate - sudo /usr/bin/python -m pip install python-dateutil - - name: Test + - name: Test ifcopenshell-python run: | cd test - sudo /usr/bin/python tests.py + python tests.py cd ../src/ifcopenshell-python mv ifcopenshell ifcopenshell-local # Force testing on installed module make test-safe From eccc21277d37c3dde39fcb2b31bfe493d3d5720d Mon Sep 17 00:00:00 2001 From: krande Date: Sun, 17 Dec 2023 15:42:38 +0100 Subject: [PATCH 6/8] update ci --- .github/workflows/ci.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 891dfcca2b..f291d370f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,17 +39,10 @@ jobs: submodules: recursive - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: 3.11 - - name: Locate Python Paths - run: | - echo "PYTHON_EXECUTABLE=$(which python)" >> $GITHUB_ENV - echo "PYTHON_INCLUDE_DIR=$(python -c 'import sysconfig; print(sysconfig.get_path(\"include\"))')" >> $GITHUB_ENV - echo "PYTHON_LIBRARY=$(python -c 'import sysconfig; print(sysconfig.get_config_var(\"LIBDIR\"))')/libpython3.11.so" >> $GITHUB_ENV - shell: bash - - name: Install dependencies run: | python -m pip install --upgrade pip @@ -90,9 +83,7 @@ jobs: -DCMAKE_SYSTEM_PREFIX_PATH=/usr \ -DOCC_INCLUDE_DIR=/usr/include/opencascade \ -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - -DPYTHON_EXECUTABLE:FILEPATH=${{ env.PYTHON_EXECUTABLE }} \ - -DPYTHON_INCLUDE_DIR:PATH=${{ env.PYTHON_INCLUDE_DIR }} \ - -DPYTHON_LIBRARY:FILEPATH=${{ env.PYTHON_LIBRARY }} \ + -DPython3_ROOT_DIR=$Python3_ROOT_DIR \ -DCOLLADA_SUPPORT=Off \ "-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \ -DGLTF_SUPPORT=On \ From 9335c8a0c8c9cbc2fc9056cec79dd0bdd10c7350 Mon Sep 17 00:00:00 2001 From: krande Date: Sun, 17 Dec 2023 18:09:28 +0100 Subject: [PATCH 7/8] add test and update ci --- .github/workflows/ci.yml | 7 +- .../ifcopenshell/util/unit.py | 73 +-- .../fixtures/units/beam-standard-case.ifc | 426 ++++++++++++++++++ .../test/util/test_unit_conversion.py | 49 ++ 4 files changed, 523 insertions(+), 32 deletions(-) create mode 100644 src/ifcopenshell-python/test/fixtures/units/beam-standard-case.ifc create mode 100644 src/ifcopenshell-python/test/util/test_unit_conversion.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f291d370f8..c07dbe5115 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,6 +74,9 @@ jobs: - name: Build ifcopenshell run: | + echo $Python3_ROOT_DIR + echo ${{ env.pythonLocation }} + mkdir build && cd build cmake \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ @@ -83,7 +86,9 @@ jobs: -DCMAKE_SYSTEM_PREFIX_PATH=/usr \ -DOCC_INCLUDE_DIR=/usr/include/opencascade \ -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - -DPython3_ROOT_DIR=$Python3_ROOT_DIR \ + -DPYTHON_EXECUTABLE:FILEPATH=${{ env.pythonLocation }}/bin/python \ + -DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \ + -DPYTHON_LIBRARY:FILEPATH=${{ env.pythonLocation }}/lib/libpython3.11.so \ -DCOLLADA_SUPPORT=Off \ "-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \ -DGLTF_SUPPORT=On \ diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 33ee3d9af3..cc20b88ac2 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -16,9 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -import ifcopenshell -from math import pi from fractions import Fraction +from math import pi +from typing import Tuple, Iterable, Any + +import ifcopenshell +import ifcopenshell.api prefixes = { "EXA": 1e18, @@ -624,67 +627,75 @@ def format_length( return f"{rounded_val:.{decimal_places}f}" -def get_base_type_name( - content_type: ifcopenshell.ifcopenshell_wrapper.named_type | ifcopenshell.ifcopenshell_wrapper.type_declaration) -> ifcopenshell.ifcopenshell_wrapper.type_declaration | None: +def is_attr_type( + content_type: ifcopenshell.ifcopenshell_wrapper.named_type | ifcopenshell.ifcopenshell_wrapper.type_declaration, + ifc_unit_type_name: str) -> ifcopenshell.ifcopenshell_wrapper.type_declaration | None: cur_decl = content_type while hasattr(cur_decl, "declared_type") is True: cur_decl = cur_decl.declared_type() if hasattr(cur_decl, "name") is False: continue - if cur_decl.name() == "IfcLengthMeasure": + if cur_decl.name() == ifc_unit_type_name: return cur_decl if isinstance(cur_decl, ifcopenshell.ifcopenshell_wrapper.aggregation_type): res = cur_decl.type_of_element() cur_decl = res.declared_type() - if hasattr(cur_decl, "name") and cur_decl.name() == "IfcLengthMeasure": + if hasattr(cur_decl, "name") and cur_decl.name() == ifc_unit_type_name: return cur_decl while hasattr(cur_decl, "declared_type") is True: cur_decl = cur_decl.declared_type() if hasattr(cur_decl, "name") is False: continue - if cur_decl.name() == "IfcLengthMeasure": + if cur_decl.name() == ifc_unit_type_name: return cur_decl return None +def iter_element_and_attributes_per_type(ifc_file: ifcopenshell.file, attr_type_name: str) -> Iterable[ + Tuple[ifcopenshell.entity_instance, ifcopenshell.ifcopenshell_wrapper.attribute, Any, str]]: + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema) + + for element in ifc_file: + entity = schema.declaration_by_name(element.is_a()) + attrs = entity.all_attributes() + for i, (attr, val, is_derived) in enumerate(zip(attrs, list(element), entity.derived())): + if is_derived: + continue + + # Get all methods and attributes of the element + attr_type = attr.type_of_attribute() + base_type = is_attr_type(attr_type, attr_type_name) + if base_type is None: + continue + + if val is None: + continue + + yield element, attr, val + + def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str) -> ifcopenshell.file: """Converts all units in an IFC file to the specified target units. Returns a new file.""" prefix = "MILLI" if target_units == "MILLIMETERS" else None - file_patched = ifcopenshell.api.run("project.create_file", version=ifc_file.schema) # Copy all elements from the original file to the patched file - for el in ifc_file: - file_patched.add(el) + file_patched = ifcopenshell.file.from_string(ifc_file.wrapped_data.to_string()) unit_assignment = ifcopenshell.util.unit.get_unit_assignment(file_patched) old_length = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == "LENGTHUNIT"][0] new_length = ifcopenshell.api.run("unit.add_si_unit", file_patched, unit_type="LENGTHUNIT", prefix=prefix) - schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema) # Traverse all elements and their nested attributes in the file and convert them - for element in file_patched: - entity = schema.declaration_by_name(element.is_a()) - attrs = entity.all_attributes() - for i, (attr, val, is_derived) in enumerate(zip(attrs, list(element), entity.derived())): - if is_derived: - continue - # Get all methods and attributes of the element - attr_type = attr.type_of_attribute() - base_type = get_base_type_name(attr_type) - if base_type is None: - continue - if val is None: - continue - if isinstance(val, tuple): - new_el = [ifcopenshell.util.unit.convert_unit(v, old_length, new_length) for v in val] - setattr(element, attr.name(), tuple(new_el)) - else: - new_el = ifcopenshell.util.unit.convert_unit(val, old_length, new_length) - # set the new value - setattr(element, attr.name(), new_el) + for element, attr, val in iter_element_and_attributes_per_type(file_patched, "IfcLengthMeasure"): + if isinstance(val, tuple): + new_value = [ifcopenshell.util.unit.convert_unit(v, old_length, new_length) for v in val] + setattr(element, attr.name(), tuple(new_value)) + else: + new_value = ifcopenshell.util.unit.convert_unit(val, old_length, new_length) + setattr(element, attr.name(), new_value) file_patched.remove(old_length) unit_assignment.Units = tuple([new_length, *unit_assignment.Units]) diff --git a/src/ifcopenshell-python/test/fixtures/units/beam-standard-case.ifc b/src/ifcopenshell-python/test/fixtures/units/beam-standard-case.ifc new file mode 100644 index 0000000000..0f506b45a4 --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/units/beam-standard-case.ifc @@ -0,0 +1,426 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION((''),'2;1'); +FILE_NAME('','2019-03-20T15:56:44',(''),(''),'BuildingSmart IfcKit by Constructivity','IfcDoc 12.0.0.0',''); +FILE_SCHEMA(('IFC4x1')); +ENDSEC; + +DATA; + +#1= IFCBEAMTYPE('0juf4qyggSstrxA20Qwnsj',$,'IPE220','Beam type',$,$,$,$,$,.BEAM.); + +#2= IFCRELDECLARES('3P3zL0KYv4C9D9h3OX$dey',$,$,$,#5,(#1,#6)); + +#3= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Q49sj',$,$,$,(#1),#7); +#7= IFCMATERIALPROFILESET($,$,(#8),$); +#8= IFCMATERIALPROFILE('IPE220',$,#9,#10,$,$); +#9= IFCMATERIAL('S275J2',$,'Steel'); +#10= IFCISHAPEPROFILEDEF(.AREA.,'IPE220',$,110.,220.,5.9,9.2,12.,$,$); + +#4= IFCRELDEFINESBYTYPE('2aq$Crcs_xJvd69lbm2bMM',$,'beam typing',$,(#11,#12,#13,#14,#15,#16,#17,#18,#19),#1); + +#5= IFCPROJECT('32DJhIf6esIeAOIlD4Xw2m',#20,'Test model for beam cardinal points',$,$,$,$,(#21),#22); +#20= IFCOWNERHISTORY(#24,#25,$,.NOTDEFINED.,$,$,$,1320688800); +#21= IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-05,#26,$); +#22= IFCUNITASSIGNMENT((#63,#64,#65,#66,#67,#68,#69,#70)); +#24= IFCPERSONANDORGANIZATION(#71,#72,$); +#25= IFCAPPLICATION(#72,'Unknown','SDS/2 Version 6.300 on NT','Unknown'); +#26= IFCAXIS2PLACEMENT3D(#73,#74,#75); +#27= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#76)); +#28= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#78)); +#29= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#79)); +#30= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#81)); +#31= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#82)); +#32= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#84)); +#33= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#85)); +#34= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#87)); +#35= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#88)); +#36= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#90)); +#37= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#91)); +#38= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#93)); +#39= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#94)); +#40= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#96)); +#41= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#97)); +#42= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#99)); +#43= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#100)); +#44= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#102)); +#45= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#103)); +#46= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#105)); +#47= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#106)); +#48= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#108)); +#49= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#109)); +#50= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#111)); +#51= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#112)); +#52= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#114)); +#53= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#115)); +#54= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#117)); +#55= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#118)); +#56= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#120)); +#57= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#121)); +#58= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#123)); +#59= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#124)); +#60= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#126)); +#61= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#127)); +#62= IFCSHAPEREPRESENTATION(#21,'Axis','Curve3D',(#129)); +#63= IFCSIUNIT($,.LENGTHUNIT.,.MILLI.,.METRE.); +#64= IFCSIUNIT($,.PLANEANGLEUNIT.,$,.RADIAN.); +#65= IFCSIUNIT($,.MASSUNIT.,.KILO.,.GRAM.); +#66= IFCSIUNIT($,.TIMEUNIT.,$,.SECOND.); +#67= IFCSIUNIT($,.AREAUNIT.,$,.SQUARE_METRE.); +#68= IFCSIUNIT($,.PRESSUREUNIT.,$,.PASCAL.); +#69= IFCSIUNIT($,.FORCEUNIT.,$,.NEWTON.); +#70= IFCSIUNIT($,.THERMODYNAMICTEMPERATUREUNIT.,$,.DEGREE_CELSIUS.); +#71= IFCPERSON($,'Liebich',$,$,$,$,$,$); +#72= IFCORGANIZATION($,'buildingSMART',$,$,$); +#73= IFCCARTESIANPOINT((0.,0.,0.)); +#74= IFCDIRECTION((0.,0.,1.)); +#75= IFCDIRECTION((1.,0.,0.)); +#76= IFCEXTRUDEDAREASOLID(#10,#130,#131,2000.); +#77= IFCPRODUCTDEFINITIONSHAPE($,$,(#28,#27)); +#78= IFCPOLYLINE((#132,#133)); +#79= IFCEXTRUDEDAREASOLID(#10,#134,#135,2000.); +#80= IFCPRODUCTDEFINITIONSHAPE($,$,(#30,#29)); +#81= IFCPOLYLINE((#136,#137)); +#82= IFCEXTRUDEDAREASOLID(#10,#138,#139,2000.); +#83= IFCPRODUCTDEFINITIONSHAPE($,$,(#32,#31)); +#84= IFCPOLYLINE((#140,#141)); +#85= IFCEXTRUDEDAREASOLID(#10,#142,#143,2000.); +#86= IFCPRODUCTDEFINITIONSHAPE($,$,(#34,#33)); +#87= IFCPOLYLINE((#144,#145)); +#88= IFCEXTRUDEDAREASOLID(#10,#146,#147,2000.); +#89= IFCPRODUCTDEFINITIONSHAPE($,$,(#36,#35)); +#90= IFCPOLYLINE((#148,#149)); +#91= IFCEXTRUDEDAREASOLID(#10,#150,#151,2000.); +#92= IFCPRODUCTDEFINITIONSHAPE($,$,(#38,#37)); +#93= IFCPOLYLINE((#152,#153)); +#94= IFCEXTRUDEDAREASOLID(#10,#154,#155,2000.); +#95= IFCPRODUCTDEFINITIONSHAPE($,$,(#40,#39)); +#96= IFCPOLYLINE((#156,#157)); +#97= IFCEXTRUDEDAREASOLID(#10,#158,#159,2000.); +#98= IFCPRODUCTDEFINITIONSHAPE($,$,(#42,#41)); +#99= IFCPOLYLINE((#160,#161)); +#100= IFCEXTRUDEDAREASOLID(#10,#162,#163,2000.); +#101= IFCPRODUCTDEFINITIONSHAPE($,$,(#44,#43)); +#102= IFCPOLYLINE((#164,#165)); +#103= IFCEXTRUDEDAREASOLID(#166,#167,#168,3000.); +#104= IFCPRODUCTDEFINITIONSHAPE($,$,(#46,#45)); +#105= IFCPOLYLINE((#170,#171)); +#106= IFCEXTRUDEDAREASOLID(#166,#172,#173,3000.); +#107= IFCPRODUCTDEFINITIONSHAPE($,$,(#48,#47)); +#108= IFCPOLYLINE((#175,#176)); +#109= IFCEXTRUDEDAREASOLID(#166,#177,#178,3000.); +#110= IFCPRODUCTDEFINITIONSHAPE($,$,(#50,#49)); +#111= IFCPOLYLINE((#180,#181)); +#112= IFCEXTRUDEDAREASOLID(#166,#182,#183,3000.); +#113= IFCPRODUCTDEFINITIONSHAPE($,$,(#52,#51)); +#114= IFCPOLYLINE((#185,#186)); +#115= IFCEXTRUDEDAREASOLID(#166,#187,#188,3000.); +#116= IFCPRODUCTDEFINITIONSHAPE($,$,(#54,#53)); +#117= IFCPOLYLINE((#190,#191)); +#118= IFCEXTRUDEDAREASOLID(#166,#192,#193,3000.); +#119= IFCPRODUCTDEFINITIONSHAPE($,$,(#56,#55)); +#120= IFCPOLYLINE((#195,#196)); +#121= IFCEXTRUDEDAREASOLID(#166,#197,#198,3000.); +#122= IFCPRODUCTDEFINITIONSHAPE($,$,(#58,#57)); +#123= IFCPOLYLINE((#200,#201)); +#124= IFCEXTRUDEDAREASOLID(#166,#202,#203,3000.); +#125= IFCPRODUCTDEFINITIONSHAPE($,$,(#60,#59)); +#126= IFCPOLYLINE((#205,#206)); +#127= IFCEXTRUDEDAREASOLID(#166,#207,#208,3000.); +#128= IFCPRODUCTDEFINITIONSHAPE($,$,(#62,#61)); +#129= IFCPOLYLINE((#210,#211)); +#130= IFCAXIS2PLACEMENT3D(#212,$,$); +#131= IFCDIRECTION((0.,0.,1.)); +#132= IFCCARTESIANPOINT((0.,0.,0.)); +#133= IFCCARTESIANPOINT((0.,0.,2000.)); +#134= IFCAXIS2PLACEMENT3D(#213,$,$); +#135= IFCDIRECTION((0.,0.,1.)); +#136= IFCCARTESIANPOINT((0.,0.,0.)); +#137= IFCCARTESIANPOINT((0.,0.,2000.)); +#138= IFCAXIS2PLACEMENT3D(#214,$,$); +#139= IFCDIRECTION((0.,0.,1.)); +#140= IFCCARTESIANPOINT((0.,0.,0.)); +#141= IFCCARTESIANPOINT((0.,0.,2000.)); +#142= IFCAXIS2PLACEMENT3D(#215,$,$); +#143= IFCDIRECTION((0.,0.,1.)); +#144= IFCCARTESIANPOINT((0.,0.,0.)); +#145= IFCCARTESIANPOINT((0.,0.,2000.)); +#146= IFCAXIS2PLACEMENT3D(#216,$,$); +#147= IFCDIRECTION((0.,0.,1.)); +#148= IFCCARTESIANPOINT((0.,0.,0.)); +#149= IFCCARTESIANPOINT((0.,0.,2000.)); +#150= IFCAXIS2PLACEMENT3D(#217,$,$); +#151= IFCDIRECTION((0.,0.,1.)); +#152= IFCCARTESIANPOINT((0.,0.,0.)); +#153= IFCCARTESIANPOINT((0.,0.,2000.)); +#154= IFCAXIS2PLACEMENT3D(#218,$,$); +#155= IFCDIRECTION((0.,0.,1.)); +#156= IFCCARTESIANPOINT((0.,0.,0.)); +#157= IFCCARTESIANPOINT((0.,0.,2000.)); +#158= IFCAXIS2PLACEMENT3D(#219,$,$); +#159= IFCDIRECTION((0.,0.,1.)); +#160= IFCCARTESIANPOINT((0.,0.,0.)); +#161= IFCCARTESIANPOINT((0.,0.,2000.)); +#162= IFCAXIS2PLACEMENT3D(#220,$,$); +#163= IFCDIRECTION((0.,0.,1.)); +#164= IFCCARTESIANPOINT((0.,0.,0.)); +#165= IFCCARTESIANPOINT((0.,0.,2000.)); +#166= IFCTSHAPEPROFILEDEF(.AREA.,'1/2IPE300',$,150.,150.,7.1,10.7,15.,$,$,$,$); +#167= IFCAXIS2PLACEMENT3D(#221,$,$); +#168= IFCDIRECTION((0.,0.,1.)); +#170= IFCCARTESIANPOINT((0.,0.,0.)); +#171= IFCCARTESIANPOINT((0.,0.,3000.)); +#172= IFCAXIS2PLACEMENT3D(#222,$,$); +#173= IFCDIRECTION((0.,0.,1.)); +#175= IFCCARTESIANPOINT((0.,0.,0.)); +#176= IFCCARTESIANPOINT((0.,0.,3000.)); +#177= IFCAXIS2PLACEMENT3D(#223,$,$); +#178= IFCDIRECTION((0.,0.,1.)); +#180= IFCCARTESIANPOINT((0.,0.,0.)); +#181= IFCCARTESIANPOINT((0.,0.,3000.)); +#182= IFCAXIS2PLACEMENT3D(#224,$,$); +#183= IFCDIRECTION((0.,0.,1.)); +#185= IFCCARTESIANPOINT((0.,0.,0.)); +#186= IFCCARTESIANPOINT((0.,0.,3000.)); +#187= IFCAXIS2PLACEMENT3D(#225,$,$); +#188= IFCDIRECTION((0.,0.,1.)); +#190= IFCCARTESIANPOINT((0.,0.,0.)); +#191= IFCCARTESIANPOINT((0.,0.,3000.)); +#192= IFCAXIS2PLACEMENT3D(#226,$,$); +#193= IFCDIRECTION((0.,0.,1.)); +#195= IFCCARTESIANPOINT((0.,0.,0.)); +#196= IFCCARTESIANPOINT((0.,0.,3000.)); +#197= IFCAXIS2PLACEMENT3D(#227,$,$); +#198= IFCDIRECTION((0.,0.,1.)); +#200= IFCCARTESIANPOINT((0.,0.,0.)); +#201= IFCCARTESIANPOINT((0.,0.,3000.)); +#202= IFCAXIS2PLACEMENT3D(#228,$,$); +#203= IFCDIRECTION((0.,0.,1.)); +#205= IFCCARTESIANPOINT((0.,0.,0.)); +#206= IFCCARTESIANPOINT((0.,0.,3000.)); +#207= IFCAXIS2PLACEMENT3D(#229,$,$); +#208= IFCDIRECTION((0.,0.,1.)); +#210= IFCCARTESIANPOINT((0.,0.,0.)); +#211= IFCCARTESIANPOINT((0.,0.,3000.)); +#212= IFCCARTESIANPOINT((-55.,110.,0.)); +#213= IFCCARTESIANPOINT((0.,110.,0.)); +#214= IFCCARTESIANPOINT((55.,110.,0.)); +#215= IFCCARTESIANPOINT((-55.,0.,0.)); +#216= IFCCARTESIANPOINT((0.,0.,0.)); +#217= IFCCARTESIANPOINT((55.,0.,0.)); +#218= IFCCARTESIANPOINT((-55.,-110.,0.)); +#219= IFCCARTESIANPOINT((0.,-110.,0.)); +#220= IFCCARTESIANPOINT((55.,-110.,0.)); +#221= IFCCARTESIANPOINT((-75.,75.,0.)); +#222= IFCCARTESIANPOINT((0.,75.,0.)); +#223= IFCCARTESIANPOINT((75.,75.,0.)); +#224= IFCCARTESIANPOINT((-75.,0.,0.)); +#225= IFCCARTESIANPOINT((0.,0.,0.)); +#226= IFCCARTESIANPOINT((75.,0.,0.)); +#227= IFCCARTESIANPOINT((-75.,-75.,0.)); +#228= IFCCARTESIANPOINT((0.,-75.,0.)); +#229= IFCCARTESIANPOINT((75.,-75.,0.)); + +#6= IFCBEAMTYPE('0juf4qyggSstrxA20Qdisj',$,'1/2IPE300','Beam type',$,$,$,$,$,.BEAM.); + +#11= IFCBEAMSTANDARDCASE('0juf4qyggSI8rxA20Qwnsj',$,'A-1','IPE220','Beam',#232,#77,'A-1',$); +#232= IFCLOCALPLACEMENT(#235,#236); +#235= IFCLOCALPLACEMENT(#237,#26); +#236= IFCAXIS2PLACEMENT3D(#256,#257,#258); +#237= IFCLOCALPLACEMENT($,#26); +#239= IFCLOCALPLACEMENT(#235,#260); +#240= IFCLOCALPLACEMENT(#235,#261); +#241= IFCLOCALPLACEMENT(#235,#262); +#242= IFCLOCALPLACEMENT(#235,#263); +#243= IFCLOCALPLACEMENT(#235,#264); +#244= IFCLOCALPLACEMENT(#235,#265); +#245= IFCLOCALPLACEMENT(#235,#266); +#246= IFCLOCALPLACEMENT(#235,#267); +#247= IFCLOCALPLACEMENT(#235,#268); +#248= IFCLOCALPLACEMENT(#235,#269); +#249= IFCLOCALPLACEMENT(#235,#270); +#250= IFCLOCALPLACEMENT(#235,#271); +#251= IFCLOCALPLACEMENT(#235,#272); +#252= IFCLOCALPLACEMENT(#235,#273); +#253= IFCLOCALPLACEMENT(#235,#274); +#254= IFCLOCALPLACEMENT(#235,#275); +#255= IFCLOCALPLACEMENT(#235,#276); +#256= IFCCARTESIANPOINT((0.,0.,0.)); +#257= IFCDIRECTION((1.,0.,0.)); +#258= IFCDIRECTION((0.,1.,0.)); +#260= IFCAXIS2PLACEMENT3D(#277,#278,#279); +#261= IFCAXIS2PLACEMENT3D(#280,#281,#282); +#262= IFCAXIS2PLACEMENT3D(#283,#284,#285); +#263= IFCAXIS2PLACEMENT3D(#286,#287,#288); +#264= IFCAXIS2PLACEMENT3D(#289,#290,#291); +#265= IFCAXIS2PLACEMENT3D(#292,#293,#294); +#266= IFCAXIS2PLACEMENT3D(#295,#296,#297); +#267= IFCAXIS2PLACEMENT3D(#298,#299,#300); +#268= IFCAXIS2PLACEMENT3D(#301,#302,#303); +#269= IFCAXIS2PLACEMENT3D(#304,#305,#306); +#270= IFCAXIS2PLACEMENT3D(#307,#308,#309); +#271= IFCAXIS2PLACEMENT3D(#310,#311,#312); +#272= IFCAXIS2PLACEMENT3D(#313,#314,#315); +#273= IFCAXIS2PLACEMENT3D(#316,#317,#318); +#274= IFCAXIS2PLACEMENT3D(#319,#320,#321); +#275= IFCAXIS2PLACEMENT3D(#322,#323,#324); +#276= IFCAXIS2PLACEMENT3D(#325,#326,#327); +#277= IFCCARTESIANPOINT((0.,1500.,0.)); +#278= IFCDIRECTION((1.,0.,0.)); +#279= IFCDIRECTION((0.,1.,0.)); +#280= IFCCARTESIANPOINT((0.,3000.,0.)); +#281= IFCDIRECTION((1.,0.,0.)); +#282= IFCDIRECTION((0.,1.,0.)); +#283= IFCCARTESIANPOINT((0.,4500.,0.)); +#284= IFCDIRECTION((1.,0.,0.)); +#285= IFCDIRECTION((0.,1.,0.)); +#286= IFCCARTESIANPOINT((0.,6000.,0.)); +#287= IFCDIRECTION((1.,0.,0.)); +#288= IFCDIRECTION((0.,1.,0.)); +#289= IFCCARTESIANPOINT((0.,7500.,0.)); +#290= IFCDIRECTION((1.,0.,0.)); +#291= IFCDIRECTION((0.,1.,0.)); +#292= IFCCARTESIANPOINT((0.,9000.,0.)); +#293= IFCDIRECTION((1.,0.,0.)); +#294= IFCDIRECTION((0.,1.,0.)); +#295= IFCCARTESIANPOINT((0.,10500.,0.)); +#296= IFCDIRECTION((1.,0.,0.)); +#297= IFCDIRECTION((0.,1.,0.)); +#298= IFCCARTESIANPOINT((0.,12000.,0.)); +#299= IFCDIRECTION((1.,0.,0.)); +#300= IFCDIRECTION((0.,1.,0.)); +#301= IFCCARTESIANPOINT((0.,0.,1500.)); +#302= IFCDIRECTION((0.98,0.081,0.182)); +#303= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#304= IFCCARTESIANPOINT((0.,1500.,1500.)); +#305= IFCDIRECTION((0.98,0.081,0.182)); +#306= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#307= IFCCARTESIANPOINT((0.,3000.,1500.)); +#308= IFCDIRECTION((0.98,0.081,0.182)); +#309= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#310= IFCCARTESIANPOINT((0.,4500.,1500.)); +#311= IFCDIRECTION((0.98,0.081,0.182)); +#312= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#313= IFCCARTESIANPOINT((0.,6000.,1500.)); +#314= IFCDIRECTION((0.98,0.081,0.182)); +#315= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#316= IFCCARTESIANPOINT((0.,7500.,1500.)); +#317= IFCDIRECTION((0.98,0.081,0.182)); +#318= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#319= IFCCARTESIANPOINT((0.,9000.,1500.)); +#320= IFCDIRECTION((0.98,0.081,0.182)); +#321= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#322= IFCCARTESIANPOINT((0.,10500.,1500.)); +#323= IFCDIRECTION((0.98,0.081,0.182)); +#324= IFCDIRECTION((-0.0001,0.9138,-0.40616)); +#325= IFCCARTESIANPOINT((0.,12000.,1500.)); +#326= IFCDIRECTION((0.98,0.081,0.182)); +#327= IFCDIRECTION((-0.0001,0.9138,-0.40616)); + +#12= IFCBEAMSTANDARDCASE('0juf4qyggSI8rxA20sznsj',$,'A-2','IPE220','Beam',#239,#80,'A-2',$); + +#13= IFCBEAMSTANDARDCASE('0juf4qyggSI8s4A20sznsj',$,'A-3','IPE220','Beam',#240,#83,'A-3',$); + +#14= IFCBEAMSTANDARDCASE('0juf4qyggSI8s4A20sznw6',$,'A-4','IPE220','Beam',#241,#86,'A-4',$); + +#15= IFCBEAMSTANDARDCASE('0juf4qyggSI8rxA20Qwnab',$,'A-5','IPE220','Beam',#242,#89,'A-5',$); + +#16= IFCBEAMSTANDARDCASE('0juf4qyggSI8rxA20Qwng1',$,'A-6','IPE220','Beam',#243,#92,'A-6',$); + +#17= IFCBEAMSTANDARDCASE('0juf4qyggSI8rxA20Qwn3s',$,'A-7','IPE220','Beam',#244,#95,'A-7',$); + +#18= IFCBEAMSTANDARDCASE('0juf4qyggSI8rxA20Qwnlq',$,'A-8','IPE220','Beam',#245,#98,'A-8',$); + +#19= IFCBEAMSTANDARDCASE('0juf4qyggSI8s4A20sznrt',$,'A-9','IPE220','Beam',#246,#101,'A-9',$); + +#23= IFCRELAGGREGATES('0ZdtoJM$VsHBqD_feRfxAg',$,$,$,#5,(#259)); + +#169= IFCBEAMSTANDARDCASE('3QbcAsYoB7Hvx$4VHzijYi',$,'B-1','1/2IPE300','Beam',#247,#104,'B-1',$); + +#174= IFCBEAMSTANDARDCASE('3Qb5gsYoB7Hvx$4VHzijYi',$,'B-2','1/2IPE300','Beam',#248,#107,'B-2',$); + +#179= IFCBEAMSTANDARDCASE('3QbcAsYsg7Hvx$4VHzijYi',$,'B-3','1/2IPE300','Beam',#249,#110,'B-3',$); + +#184= IFCBEAMSTANDARDCASE('3QbcAsYsg7Hvx$4VHzijGT',$,'B-4','1/2IPE300','Beam',#250,#113,'B-4',$); + +#189= IFCBEAMSTANDARDCASE('3QbcAsYsg7Hvx$4VHzijdF',$,'B-5','1/2IPE300','Beam',#251,#116,'B-5',$); + +#194= IFCBEAMSTANDARDCASE('3QbcAsYsg7Hvx$4VHzij3V',$,'B-6','1/2IPE300','Beam',#252,#119,'B-6',$); + +#199= IFCBEAMSTANDARDCASE('3QbcAsYsg7Hvx$4VHzij71',$,'B-7','1/2IPE300','Beam',#253,#122,'B-7',$); + +#204= IFCBEAMSTANDARDCASE('3QbcAsYsg7Hvx$4VHzijvb',$,'B-8','1/2IPE300','Beam',#254,#125,'B-8',$); + +#209= IFCBEAMSTANDARDCASE('3QbcAsYsg7Hvx$4VHzijp1',$,'B-9','1/2IPE300','Beam',#255,#128,'B-9',$); + +#230= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Q2fsj',$,$,$,(#6),#345); +#345= IFCMATERIALPROFILESET($,$,(#346),$); +#346= IFCMATERIALPROFILE('1/2IPE300',$,#347,#166,$,$); +#347= IFCMATERIAL('S275J2',$,'Steel'); + +#231= IFCRELDEFINESBYTYPE('2aq$Crcs_xJvN69lbm2bMM',$,'beam typing',$,(#169,#174,#179,#184,#189,#194,#199,#204,#209),#6); + +#233= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QfZsj',$,$,$,(#11),#348); +#348= IFCMATERIALPROFILESETUSAGE(#7,1,$); + +#234= IFCRELCONTAINEDINSPATIALSTRUCTURE('2aq$Crcs_xJvtg9lbm2bMM',$,'Physical model',$,(#11,#12,#13,#14,#15,#16,#17,#18,#19,#169,#174,#179,#184,#189,#194,#199,#204,#209),#238); + +#238= IFCBUILDING('0Xwup04AK2G8Mt0WNZVy_Z',$,'Building',$,$,#235,$,$,.ELEMENT.,$,$,$); + +#259= IFCSITE('10cTefjFQoJexBQrSqFcWZ',$,'Site',$,$,#237,$,$,.ELEMENT.,$,$,$,$,$); + +#328= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QgAsj',$,$,$,(#12),#350); +#350= IFCMATERIALPROFILESETUSAGE(#7,2,$); + +#329= IFCRELASSOCIATESMATERIAL('3x0gFSPAr5puQ5WI22xYOm',$,$,$,(#13),#351); +#351= IFCMATERIALPROFILESETUSAGE(#7,3,$); + +#330= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QOHsj',$,$,$,(#14),#352); +#352= IFCMATERIALPROFILESETUSAGE(#7,4,$); + +#331= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QRLsj',$,$,$,(#15),#353); +#353= IFCMATERIALPROFILESETUSAGE(#7,5,$); + +#332= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QMmsj',$,$,$,(#16),#354); +#354= IFCMATERIALPROFILESETUSAGE(#7,6,$); + +#333= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Qh4sj',$,$,$,(#17),#355); +#355= IFCMATERIALPROFILESETUSAGE(#7,7,$); + +#334= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QwKsj',$,$,$,(#18),#356); +#356= IFCMATERIALPROFILESETUSAGE(#7,8,$); + +#335= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QJksj',$,$,$,(#19),#357); +#357= IFCMATERIALPROFILESETUSAGE(#7,9,$); + +#336= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QbFsj',$,$,$,(#169),#358); +#358= IFCMATERIALPROFILESETUSAGE(#345,1,$); + +#337= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Qqlsj',$,$,$,(#174),#359); +#359= IFCMATERIALPROFILESETUSAGE(#345,2,$); + +#338= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Q7isj',$,$,$,(#179),#360); +#360= IFCMATERIALPROFILESETUSAGE(#345,3,$); + +#339= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Q4nsj',$,$,$,(#184),#361); +#361= IFCMATERIALPROFILESETUSAGE(#345,4,$); + +#340= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Q41sj',$,$,$,(#189),#362); +#362= IFCMATERIALPROFILESETUSAGE(#345,5,$); + +#341= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QURsj',$,$,$,(#194),#363); +#363= IFCMATERIALPROFILESETUSAGE(#345,6,$); + +#342= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Qw9sj',$,$,$,(#199),#364); +#364= IFCMATERIALPROFILESETUSAGE(#345,7,$); + +#343= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20QOcsj',$,$,$,(#204),#365); +#365= IFCMATERIALPROFILESETUSAGE(#345,8,$); + +#344= IFCRELASSOCIATESMATERIAL('0juf4qyggSstrxA20Qw6sj',$,$,$,(#209),#366); +#366= IFCMATERIALPROFILESETUSAGE(#345,9,$); + +#349= IFCRELAGGREGATES('1FUTCayKJbIQqDNnZzXu07',$,$,$,#259,(#238)); +ENDSEC; + +END-ISO-10303-21; diff --git a/src/ifcopenshell-python/test/util/test_unit_conversion.py b/src/ifcopenshell-python/test/util/test_unit_conversion.py new file mode 100644 index 0000000000..af84677647 --- /dev/null +++ b/src/ifcopenshell-python/test/util/test_unit_conversion.py @@ -0,0 +1,49 @@ +# 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 pathlib + +import pytest + +import ifcopenshell.util.unit + +UNITS_FIXTURE_DIR = pathlib.Path(__file__).parent.parent / "fixtures" / "units" + + +@pytest.mark.parametrize("ifc_file", UNITS_FIXTURE_DIR.glob("*.ifc")) +def test_file_units_length_convert(ifc_file): + f = ifcopenshell.open(ifc_file) + project_unit = ifcopenshell.util.unit.get_project_unit(f, "LENGTHUNIT") + if project_unit.Prefix == "MILLI": + target_units = "METERS" + scale = 0.001 + else: + target_units = "MILLIMETERS" + scale = 1000 + + new_f = ifcopenshell.util.unit.convert_file_length_units(f, target_units) + + for element, attr, val in ifcopenshell.util.unit.iter_element_and_attributes_per_type(new_f, "IfcLengthMeasure"): + elem_id = element.id() + original_element = f.by_id(elem_id) + original_val = getattr(original_element, attr.name()) + if isinstance(original_val, tuple): + # assert element is equal to original element times scale + assert val == tuple([v * scale for v in original_val]) + else: + # assert element is equal to original element times scale + assert val == original_val * scale From 593fca7c3db6a334d5418484733c649a2d22d12d Mon Sep 17 00:00:00 2001 From: krande Date: Sun, 17 Dec 2023 18:42:15 +0100 Subject: [PATCH 8/8] remove ci-py-only.yml as ci.yml already does this --- .github/workflows/ci-py-only.yml | 98 -------------------------------- 1 file changed, 98 deletions(-) delete mode 100644 .github/workflows/ci-py-only.yml diff --git a/.github/workflows/ci-py-only.yml b/.github/workflows/ci-py-only.yml deleted file mode 100644 index 08d29461b0..0000000000 --- a/.github/workflows/ci-py-only.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: ci_py_only - -on: - push: - paths: - - 'src/**' - - 'test/**' - - 'conda/**' - - 'cmake/**' - - '.github/workflows/ci-py-only.yml' - pull_request: - -jobs: - activate: - runs-on: ubuntu-latest - if: | - github.repository == 'IfcOpenShell/IfcOpenShell' && - !contains(github.event.head_commit.message, 'skip ci') - steps: - - run: echo ok go - - build: - runs-on: ubuntu-20.04 - needs: activate - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Install C++ dependencies - run: | - sudo apt update - sudo apt-get install --no-install-recommends \ - git cmake gcc g++ \ - libboost-date-time-dev \ - libboost-filesystem-dev \ - libboost-iostreams-dev \ - libboost-program-options-dev \ - libboost-regex-dev \ - libboost-system-dev \ - libboost-thread-dev \ - python3-all-dev python3-pip \ - swig libpcre3-dev libxml2-dev \ - libtbb-dev nlohmann-json3-dev \ - libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \ - libhdf5-dev libcgal-dev - - - name: ccache - uses: hendrikmuhs/ccache-action@v1 - with: - key: ${GITHUB_WORKFLOW} - - - name: Build ifcopenshell - run: | - sudo /usr/sbin/update-ccache-symlinks - mkdir build && cd build - PATH="/usr/lib/ccache:$PATH" cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH=/usr \ - -DCMAKE_SYSTEM_PREFIX_PATH=/usr \ - -DOCC_INCLUDE_DIR=/usr/include/opencascade \ - -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \ - -DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \ - -DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \ - -DCOLLADA_SUPPORT=Off \ - "-DSCHEMA_VERSIONS=2x3;4;4x3_add1" \ - -DBUILD_CONVERT=Off \ - -DGLTF_SUPPORT=On \ - -DJSON_INCLUDE_DIR=/usr/include \ - -DCGAL_INCLUDE_DIR=/usr/include \ - -DGMP_INCLUDE_DIR=/usr/include \ - -DMPFR_INCLUDE_DIR=/usr/include \ - -DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - -DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ - -DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \ - ../cmake - sudo make -j $(nproc) - sudo make install - - - name: Install Python dependencies - run: | - sudo /usr/bin/python -m pip install -U pip - sudo /usr/bin/python -m pip install xmlschema xsdata numpy lxml - sudo /usr/bin/python -m pip install src/bcf --no-deps - sudo /usr/bin/python -m pip install pytest - sudo /usr/bin/python -m pip install isodate - sudo /usr/bin/python -m pip install lark - sudo /usr/bin/python -m pip install networkx - sudo /usr/bin/python -m pip install tabulate - sudo /usr/bin/python -m pip install python-dateutil - - - name: Test - run: | - cd test - sudo /usr/bin/python tests.py - cd ../src/ifcopenshell-python - mv ifcopenshell ifcopenshell-local # Force testing on installed module - make test-safe