diff --git a/src/ifcpatch/ifcpatch/recipes/MergeProject.py b/src/ifcpatch/ifcpatch/recipes/MergeProject.py index a65cbeeaac..412a45b256 100644 --- a/src/ifcpatch/ifcpatch/recipes/MergeProject.py +++ b/src/ifcpatch/ifcpatch/recipes/MergeProject.py @@ -16,9 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcPatch. If not, see . +import numpy as np import ifcopenshell -import ifcopenshell.util.element import ifcopenshell.util.unit +import ifcopenshell.util.element +import ifcopenshell.util.geolocation +from ifcpatch.recipes.SetFalseOrigin import Patcher as SetFalseOrigin from typing import Union from logging import Logger @@ -51,12 +54,25 @@ class Patcher: def patch(self): if isinstance(self.filepath, ifcopenshell.file): - source = self.filepath + other = self.filepath else: - source = ifcopenshell.open(self.filepath) + other = ifcopenshell.open(self.filepath) - 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) + if (main_unit := self.get_unit_name(self.file)) != self.get_unit_name(other): + other = ifcopenshell.util.unit.convert_file_length_units(other, main_unit) + + existing_enh = np.array( + ifcopenshell.util.geolocation.auto_xyz2enh(self.file, 0, 0, 0, should_return_in_map_units=False) + ) + other_enh = np.array( + ifcopenshell.util.geolocation.auto_xyz2enh(other, 0, 0, 0, should_return_in_map_units=False) + ) + + if not np.allclose(existing_enh, other_enh): + x, y, z = ifcopenshell.util.geolocation.auto_enh2xyz(other, *existing_enh, is_specified_in_map_units=False) + e, n, h = existing_enh + # For now don't handle rotation because my brain is going to explode + SetFalseOrigin("", other, self.logger, name="", x=x, y=y, z=z, e=e, n=n, h=h).patch() self.existing_contexts: list[ifcopenshell.entity_instance] = self.file.by_type( "IfcGeometricRepresentationContext" @@ -64,13 +80,13 @@ class Patcher: self.added_contexts: set[ifcopenshell.entity_instance] = set() original_project = self.file.by_type("IfcProject")[0] - merged_project = self.file.add(source.by_type("IfcProject")[0]) + merged_project = self.file.add(other.by_type("IfcProject")[0]) - for element in source.by_type("IfcGeometricRepresentationContext"): + for element in other.by_type("IfcGeometricRepresentationContext"): new = self.file.add(element) self.added_contexts.add(new) - for element in source: + for element in other: self.file.add(element) for inverse in self.file.get_inverse(merged_project): diff --git a/src/ifcpatch/test/test_MergeProject.py b/src/ifcpatch/test/test_MergeProject.py index 685f4326a5..6be9305e82 100644 --- a/src/ifcpatch/test/test_MergeProject.py +++ b/src/ifcpatch/test/test_MergeProject.py @@ -18,7 +18,7 @@ import ifcpatch import ifcopenshell -import ifcopenshell.api +import ifcopenshell.api.georeference import ifcopenshell.util.placement import test.bootstrap import tempfile @@ -81,5 +81,48 @@ class TestMergeProject(test.bootstrap.IFC4): assert len(output.by_type("IfcProjectedCRS")) == 1 assert len(output.by_type("IfcMapConversion")) == 1 + def test_shifting_the_source_project_to_match_the_original_project_origin(self): + self.file = self.setup_project(self.file) + second_file = self.setup_project() + ifcopenshell.api.georeference.add_georeferencing(self.file) + ifcopenshell.api.georeference.edit_georeferencing( + self.file, coordinate_operation={"Eastings": 10, "Northings": 20}, projected_crs={"Name": "EPSG:1234"} + ) + ifcopenshell.api.georeference.add_georeferencing(second_file) + ifcopenshell.api.georeference.edit_georeferencing( + second_file, coordinate_operation={"Eastings": 30000, "Northings": 40000}, projected_crs={"Name": "EPSG:0"} + ) + + # Original file is in meters + wall1 = self.file.by_type("IfcWall")[0] + m1 = ifcopenshell.util.placement.get_local_placement(wall1.ObjectPlacement) + assert np.allclose(m1[:, 3], (1, 2, 3, 1)) + global_m1 = ifcopenshell.util.geolocation.auto_local2global(self.file, m1, should_return_in_map_units=False) + assert np.allclose(global_m1[:, 3], (11, 22, 3, 1)) + + # Second file is in millimeters with a different false origin + wall1 = second_file.by_type("IfcWall")[0] + m1 = ifcopenshell.util.placement.get_local_placement(wall1.ObjectPlacement) + assert np.allclose(m1[:, 3], (1000, 2000, 3000, 1)) + global_m1 = ifcopenshell.util.geolocation.auto_local2global(second_file, m1, should_return_in_map_units=False) + assert np.allclose(global_m1[:, 3], (31000, 42000, 3000, 1)) + + output = ifcpatch.execute({"file": self.file, "recipe": "MergeProject", "arguments": [second_file]}) + + # In the future we may use proj to support reprojection from different CRSes. For now... nope! + if self.file.schema != "IFC2X3": + assert output.by_type("IfcProjectedCRS")[0].Name == "EPSG:1234" + + # The results should be in meters with the false origin of the original file + params = ifcopenshell.util.geolocation.get_helmert_transformation_parameters(output) + assert params.e == 10 + assert params.n == 20 + wall1, wall2 = output.by_type("IfcWall") + m1 = ifcopenshell.util.placement.get_local_placement(wall1.ObjectPlacement) + m2 = ifcopenshell.util.placement.get_local_placement(wall2.ObjectPlacement) + assert np.allclose(m1[:, 3], (1, 2, 3, 1)) + assert np.allclose(m2[:, 3], (21, 22, 3, 1)) + + class TestMergeProjectIFC2X3(test.bootstrap.IFC2X3, TestMergeProject): pass