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
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6b8026111b..c07dbe5115 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,9 +34,22 @@ 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@v5
+ with:
+ python-version: 3.11
+
+ - 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 +62,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 \
@@ -62,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 \
@@ -71,9 +86,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.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 \
@@ -102,23 +117,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
diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py
index 28bc705f00..cc20b88ac2 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/unit.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py
@@ -16,8 +16,12 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
-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,
@@ -214,12 +218,10 @@ si_conversions = {
"fahrenheit": 1.8,
}
-
si_offsets = {
"fahrenheit": -459.67,
}
-
imperial_types = {
"thou": "LENGTHUNIT",
"inch": "LENGTHUNIT",
@@ -260,7 +262,6 @@ imperial_types = {
"fahrenheit": "THERMODYNAMICTEMPERATUREUNIT",
}
-
prefix_symbols = {
"EXA": "E",
"PETA": "P",
@@ -558,13 +559,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 +625,79 @@ def format_length(
elif unit_system == "metric":
rounded_val = round(value / precision) * precision
return f"{rounded_val:.{decimal_places}f}"
+
+
+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() == 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() == 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() == 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
+
+ # Copy all elements from the original file to the patched file
+ 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)
+
+ # 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"):
+ 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])
+
+ return file_patched
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
diff --git a/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py b/src/ifcpatch/ifcpatch/recipes/ConvertLengthUnit.py
index b18f0f670e..48da1e1b37 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_length_units(self.file, self.unit)