Merging projects can now handle projects with different false origins

Warning: I haven't yet looked at rotations. Yikes. No CRS reprojection either.
This commit is contained in:
Dion Moult
2024-06-29 17:05:06 +10:00
parent 0d401c9272
commit 9a871f1459
2 changed files with 68 additions and 9 deletions
+24 -8
View File
@@ -16,9 +16,12 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>. # along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.unit 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 typing import Union
from logging import Logger from logging import Logger
@@ -51,12 +54,25 @@ class Patcher:
def patch(self): def patch(self):
if isinstance(self.filepath, ifcopenshell.file): if isinstance(self.filepath, ifcopenshell.file):
source = self.filepath other = self.filepath
else: 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): if (main_unit := self.get_unit_name(self.file)) != self.get_unit_name(other):
source = ifcopenshell.util.unit.convert_file_length_units(source, main_unit) 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( self.existing_contexts: list[ifcopenshell.entity_instance] = self.file.by_type(
"IfcGeometricRepresentationContext" "IfcGeometricRepresentationContext"
@@ -64,13 +80,13 @@ class Patcher:
self.added_contexts: set[ifcopenshell.entity_instance] = set() self.added_contexts: set[ifcopenshell.entity_instance] = set()
original_project = self.file.by_type("IfcProject")[0] 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) new = self.file.add(element)
self.added_contexts.add(new) self.added_contexts.add(new)
for element in source: for element in other:
self.file.add(element) self.file.add(element)
for inverse in self.file.get_inverse(merged_project): for inverse in self.file.get_inverse(merged_project):
+44 -1
View File
@@ -18,7 +18,7 @@
import ifcpatch import ifcpatch
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api.georeference
import ifcopenshell.util.placement import ifcopenshell.util.placement
import test.bootstrap import test.bootstrap
import tempfile import tempfile
@@ -81,5 +81,48 @@ class TestMergeProject(test.bootstrap.IFC4):
assert len(output.by_type("IfcProjectedCRS")) == 1 assert len(output.by_type("IfcProjectedCRS")) == 1
assert len(output.by_type("IfcMapConversion")) == 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): class TestMergeProjectIFC2X3(test.bootstrap.IFC2X3, TestMergeProject):
pass pass