From ff662fdfcffb54242e8a589b411bfd2099a87c0a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 8 Jul 2024 11:38:35 +1000 Subject: [PATCH] Merging projects now accomodates models with different project norths --- .../test/bim/feature/project.feature | 1 - src/ifcpatch/ifcpatch/recipes/MergeProject.py | 37 +++++++++--- src/ifcpatch/test/test_MergeProject.py | 56 +++++++++++++++++-- 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/blenderbim/test/bim/feature/project.feature b/src/blenderbim/test/bim/feature/project.feature index 85f90b1aa5..5473a07cb5 100644 --- a/src/blenderbim/test/bim/feature/project.feature +++ b/src/blenderbim/test/bim/feature/project.feature @@ -780,7 +780,6 @@ Scenario: Link IFC - automatic false origin mode - two different false origins a And the object "Col:IfcProject/geolocation.ifc:Chunk" has a vertex at "-17.696,-7.866,0" And the object "Col:IfcProject/geolocation.ifc:Chunk" has a vertex at "-13.268,0.464,0" - Scenario: Toggle link visibility - wireframe mode Given an empty IFC project And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')" diff --git a/src/ifcpatch/ifcpatch/recipes/MergeProject.py b/src/ifcpatch/ifcpatch/recipes/MergeProject.py index 412a45b256..68f315945b 100644 --- a/src/ifcpatch/ifcpatch/recipes/MergeProject.py +++ b/src/ifcpatch/ifcpatch/recipes/MergeProject.py @@ -61,18 +61,41 @@ class Patcher: 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( + existing_origin = np.array( ifcopenshell.util.geolocation.auto_xyz2enh(self.file, 0, 0, 0, should_return_in_map_units=False) ) - other_enh = np.array( + other_origin = 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() + existing_angle = ifcopenshell.util.geolocation.get_grid_north(self.file) + other_angle = ifcopenshell.util.geolocation.get_grid_north(other) + + model_rotation = existing_angle - other_angle + if model_rotation > 180: + model_rotation = (360 - model_rotation) * -1 + elif model_rotation < -180: + model_rotation = (model_rotation * -1) - 360 + + if not np.allclose(existing_origin, other_origin) or not np.isclose(existing_angle, other_angle): + x, y, z = ifcopenshell.util.geolocation.auto_enh2xyz( + other, *existing_origin, is_specified_in_map_units=False + ) + e, n, h = existing_origin + SetFalseOrigin( + "", + other, + self.logger, + name="", + x=x, + y=y, + z=z, + e=e, + n=n, + h=h, + gn_angle=existing_angle, + rotate_angle=model_rotation, + ).patch() self.existing_contexts: list[ifcopenshell.entity_instance] = self.file.by_type( "IfcGeometricRepresentationContext" diff --git a/src/ifcpatch/test/test_MergeProject.py b/src/ifcpatch/test/test_MergeProject.py index 6be9305e82..dabbf1ce1b 100644 --- a/src/ifcpatch/test/test_MergeProject.py +++ b/src/ifcpatch/test/test_MergeProject.py @@ -20,6 +20,7 @@ import ifcpatch import ifcopenshell import ifcopenshell.api.georeference import ifcopenshell.util.placement +import ifcopenshell.util.shape_builder import test.bootstrap import tempfile import numpy as np @@ -81,31 +82,60 @@ 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): + def test_shifting_the_other_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) + xaa1, xao1 = ifcopenshell.util.geolocation.angle2xaxis(10) ifcopenshell.api.georeference.edit_georeferencing( - self.file, coordinate_operation={"Eastings": 10, "Northings": 20}, projected_crs={"Name": "EPSG:1234"} + self.file, + coordinate_operation={"Eastings": 10, "Northings": 20, "XAxisAbscissa": xaa1, "XAxisOrdinate": xao1}, + projected_crs={"Name": "EPSG:1234"}, ) ifcopenshell.api.georeference.add_georeferencing(second_file) + xaa, xao = ifcopenshell.util.geolocation.angle2xaxis(30) ifcopenshell.api.georeference.edit_georeferencing( - second_file, coordinate_operation={"Eastings": 30000, "Northings": 40000}, projected_crs={"Name": "EPSG:0"} + second_file, + coordinate_operation={"Eastings": 30000, "Northings": 40000, "XAxisAbscissa": xaa, "XAxisOrdinate": xao}, + projected_crs={"Name": "EPSG:0"}, ) + builder1 = ifcopenshell.util.shape_builder.ShapeBuilder(self.file) + builder2 = ifcopenshell.util.shape_builder.ShapeBuilder(second_file) + o1 = builder1.create_axis2_placement_3d() + o2 = builder2.create_axis2_placement_3d() + body1 = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW") + body2 = ifcopenshell.util.representation.get_context(second_file, "Model", "Body", "MODEL_VIEW") + # 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)) + assert np.allclose(global_m1[:, 3], (11.332, 21.796, 3, 1)) + + block = self.file.createIfcCsgSolid(self.file.createIfcBlock(o1, 2, 2, 2)) + rep = builder1.get_representation(context=body1, items=[block]) + ifcopenshell.api.geometry.assign_representation(self.file, product=wall1, representation=rep) + shape = ifcopenshell.geom.create_shape(ifcopenshell.geom.settings(), wall1) + verts = ifcopenshell.util.shape.get_shape_vertices(shape, shape.geometry) + assert np.any(np.all(np.isclose(np.array((1., 2., 3.)), verts), axis=1)) + assert np.any(np.all(np.isclose(np.array((3., 4., 5.)), verts), axis=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)) + assert np.allclose(global_m1[:, 3], (31866, 41232, 3000, 1)) + + block = second_file.createIfcCsgSolid(second_file.createIfcBlock(o2, 2000, 2000, 2000)) + rep = builder2.get_representation(context=body2, items=[block]) + ifcopenshell.api.geometry.assign_representation(second_file, product=wall1, representation=rep) + shape = ifcopenshell.geom.create_shape(ifcopenshell.geom.settings(), wall1) + verts = ifcopenshell.util.shape.get_shape_vertices(shape, shape.geometry) + assert np.any(np.all(np.isclose(np.array((1., 2., 3.)), verts), axis=1)) + assert np.any(np.all(np.isclose(np.array((3., 4., 5.)), verts), axis=1)) output = ifcpatch.execute({"file": self.file, "recipe": "MergeProject", "arguments": [second_file]}) @@ -117,11 +147,25 @@ class TestMergeProject(test.bootstrap.IFC4): params = ifcopenshell.util.geolocation.get_helmert_transformation_parameters(output) assert params.e == 10 assert params.n == 20 + assert params.xaa == xaa1 + assert params.xao == xao1 + 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)) + # assert np.allclose(m2[:, 3], (8.321, 29.321, 3, 1), atol=1e-3) + assert np.allclose(m2[:, 3], (17.847, 24.707, 3., 1.), atol=1e-3) + + shape = ifcopenshell.geom.create_shape(ifcopenshell.geom.settings(), wall1) + verts = ifcopenshell.util.shape.get_shape_vertices(shape, shape.geometry) + assert np.any(np.all(np.isclose(np.array((1., 2., 3.)), verts), axis=1)) + assert np.any(np.all(np.isclose(np.array((3., 4., 5.)), verts), axis=1)) + + shape = ifcopenshell.geom.create_shape(ifcopenshell.geom.settings(), wall2) + verts = ifcopenshell.util.shape.get_shape_vertices(shape, shape.geometry) + assert np.any(np.all(np.isclose(np.array((17.847, 24.707, 3.)), verts, atol=1e-3), axis=1)) + assert np.any(np.all(np.isclose(np.array((20.410, 25.902, 5.)), verts, atol=1e-3), axis=1)) class TestMergeProjectIFC2X3(test.bootstrap.IFC2X3, TestMergeProject):