MergeProject - support merging projects with different units

This commit is contained in:
Andrej730
2024-04-24 11:28:49 +05:00
parent 1c729a74d1
commit 606a0b46f2
2 changed files with 85 additions and 0 deletions
@@ -18,6 +18,7 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.unit
from typing import Union from typing import Union
from logging import Logger from logging import Logger
@@ -30,6 +31,9 @@ class Patcher:
further processing will be done. This means that you may end up with further processing will be done. This means that you may end up with
duplicate spatial hierarchies (i.e. 2 sites, 2 buildings, etc). duplicate spatial hierarchies (i.e. 2 sites, 2 buildings, etc).
Will automatically convert length units in the second model to the main
model's unit before merging.
:param filepath: The filepath of the second IFC model to merge into the :param filepath: The filepath of the second IFC model to merge into the
first. The first model is already specified as the input to first. The first model is already specified as the input to
IfcPatch. IfcPatch.
@@ -48,6 +52,10 @@ class Patcher:
def patch(self): def patch(self):
source = ifcopenshell.open(self.filepath) source = ifcopenshell.open(self.filepath)
# make sure models units will match
if (main_unit := self.get_unit_name(self.file)) != self.get_unit_name(source):
source = ifcopenshell.util.unit.convert_file_length_units(source, main_unit)
self.existing_contexts: list[ifcopenshell.entity_instance] = self.file.by_type( self.existing_contexts: list[ifcopenshell.entity_instance] = self.file.by_type(
"IfcGeometricRepresentationContext" "IfcGeometricRepresentationContext"
) )
@@ -69,6 +77,17 @@ class Patcher:
self.reuse_existing_contexts() self.reuse_existing_contexts()
def get_unit_name(self, ifc_file: ifcopenshell.file) -> str:
length_unit = ifcopenshell.util.unit.get_project_unit(ifc_file, "LENGTHUNIT")
names = {
"METRE": "METERS",
"FOOT": "FEET",
"INCH": "INCHES",
"MILE": "MILES",
}
prefix = getattr(length_unit, "Prefix", None) or ""
return prefix + names[length_unit.Name.upper()]
def reuse_existing_contexts(self): def reuse_existing_contexts(self):
to_delete = set() to_delete = set()
+66
View File
@@ -0,0 +1,66 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcpatch
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.placement
import test.bootstrap
import tempfile
import numpy as np
from pathlib import Path
from typing import Optional
class TestMergeProject(test.bootstrap.IFC4):
def setup_project(self, ifc_file: Optional[ifcopenshell.file] = None):
prefix = None if ifc_file else "MILLI"
if ifc_file is None:
ifc_file = ifcopenshell.file(schema=self.file.schema)
project = ifcopenshell.api.run("root.create_entity", ifc_file, ifc_class="IfcProject")
unit = ifcopenshell.api.run("unit.add_si_unit", ifc_file, unit_type="LENGTHUNIT", prefix=prefix)
ifcopenshell.api.run("unit.assign_unit", ifc_file, units=[unit])
matrix = np.eye(4)
matrix[:, 3] = (1, 2, 3, 1)
wall = ifcopenshell.api.run("root.create_entity", ifc_file, ifc_class="IfcWall")
ifcopenshell.api.run("geometry.edit_object_placement", ifc_file, product=wall, matrix=matrix, is_si=True)
return ifc_file
def test_run(self):
self.file = self.setup_project(self.file)
second_file = self.setup_project()
temp_path = Path(tempfile.gettempdir()) / "second.ifc"
second_file.write(temp_path)
output = ifcpatch.execute({"file": self.file, "recipe": "MergeProject", "arguments": [str(temp_path)]})
assert len(output.by_type("IfcWall")) == 2
wall1, wall2 = output.by_type("IfcWall")
# test that units are converted
placement1 = ifcopenshell.util.placement.get_local_placement(wall1.ObjectPlacement)
placement2 = ifcopenshell.util.placement.get_local_placement(wall2.ObjectPlacement)
to_tuple = lambda arr: tuple(map(tuple, arr))
matrix = np.eye(4)
matrix[:, 3] = (1, 2, 3, 1)
assert to_tuple(placement1) == to_tuple(placement2) == to_tuple(matrix)
class TestMergeProjectIFC2X3(test.bootstrap.IFC2X3, TestMergeProject):
pass