mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-27 18:57:17 +00:00
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:
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user