diff --git a/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py b/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py index c373b507f3..abaae30b19 100644 --- a/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py +++ b/src/ifcpatch/ifcpatch/recipes/ResetSpatialElementLocations.py @@ -63,7 +63,7 @@ class Patcher: queue.extend(parts) def patch_placement_to_origin(self, element: ifcopenshell.entity_instance) -> None: - if not getattr(element, "ObjectPlacement", None): + if not getattr(element, "ObjectPlacement", None) or getattr(element, "Representation", None): return if self.only_xy: m = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) diff --git a/src/ifcpatch/ifcpatch/recipes/SetFalseOrigin.py b/src/ifcpatch/ifcpatch/recipes/SetFalseOrigin.py index d0e6f26c9e..e22f4787d1 100644 --- a/src/ifcpatch/ifcpatch/recipes/SetFalseOrigin.py +++ b/src/ifcpatch/ifcpatch/recipes/SetFalseOrigin.py @@ -19,7 +19,7 @@ import ifcopenshell import ifcopenshell.api.georeference import ifcopenshell.util.geolocation -from ifcpatch.recipes import OffsetObjectPlacements, SetWorldCoordinateSystem +from ifcpatch.recipes import OffsetObjectPlacements, SetWorldCoordinateSystem, ResetSpatialElementLocations import typing @@ -37,6 +37,7 @@ class Patcher: h: typing.Union[str, float] = "0", gn_angle: typing.Union[str, float] = "0", rotate_angle: typing.Union[str, float] = "0", + reset_spatial_elements: bool = True, ): """Sets local coordinates XYZ as a (false) origin that correlates to map coordinates ENH @@ -83,6 +84,7 @@ class Patcher: self.h = float(h) self.gn_angle = float(gn_angle) self.rotate_angle = float(rotate_angle) + self.reset_spatial_elements = reset_spatial_elements def patch(self): SetWorldCoordinateSystem.Patcher(self.file, self.logger, x=0, y=0, z=0, ax=0, ay=0, az=0).patch() @@ -111,6 +113,8 @@ class Patcher: should_rotate_first=False, ax=self.rotate_angle or None, ).patch() + if self.reset_spatial_elements: + ResetSpatialElementLocations.Patcher(self.file, self.logger).patch() else: ifcopenshell.api.georeference.remove_georeferencing(self.file) OffsetObjectPlacements.Patcher( @@ -122,6 +126,8 @@ class Patcher: should_rotate_first=False, ax=self.rotate_angle or None, ).patch() + if self.reset_spatial_elements: + ResetSpatialElementLocations.Patcher(self.file, self.logger).patch() OffsetObjectPlacements.Patcher( self.file, self.logger, diff --git a/src/ifcpatch/test/test_SetFalseOrigin.py b/src/ifcpatch/test/test_SetFalseOrigin.py new file mode 100644 index 0000000000..337f82a43e --- /dev/null +++ b/src/ifcpatch/test/test_SetFalseOrigin.py @@ -0,0 +1,70 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2025 Dion Moult +# +# 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 . + +import test.bootstrap +import ifcopenshell.api +import ifcopenshell.api.root +import ifcopenshell.api.unit +import ifcopenshell.util.unit +import ifcpatch +import numpy as np + + +class TestSetFalseOrigin(test.bootstrap.IFC4): + def test_run(self): + project = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + model3d = ifcopenshell.api.context.add_context(self.file, context_type="Model") + body = ifcopenshell.api.context.add_context( + self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d + ) + unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT") + ifcopenshell.api.unit.assign_unit(self.file, units=[unit]) + # ifcopenshell.api.georeference.add_georeferencing(self.file) + # ifcopenshell.api.georeference.edit_georeferencing(self.file) + site = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSite") + wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + ifcopenshell.api.aggregate.assign_object(self.file, products=[site], relating_object=project) + ifcopenshell.api.spatial.assign_container(self.file, products=[wall], relating_structure=site) + m = np.eye(4) + ifcopenshell.api.geometry.edit_object_placement(self.file, product=site, matrix=m) + ifcopenshell.api.geometry.edit_object_placement(self.file, product=wall, matrix=m) + ifcpatch.execute( + { + "file": self.file, + "recipe": "SetFalseOrigin", + "arguments": ["EPSG:1234", 5, 0, 0, 1000, 2000, 3000, 0, 0, True], + } + ) + if self.file.schema == "IFC2X3": + assert ( + ifcopenshell.util.element.get_pset(self.file.by_type("IfcProject")[0], "ePSet_ProjectedCRS", "Name") + == "EPSG:1234" + ) + else: + assert self.file.by_type("IfcProjectedCRS")[0].Name == "EPSG:1234" + assert ifcopenshell.util.geolocation.auto_xyz2enh(self.file, 0, 0, 0) == (1000, 2000, 3000) + m = ifcopenshell.util.placement.get_local_placement(site.ObjectPlacement) + assert np.allclose(m, np.eye(4)) + m = ifcopenshell.util.placement.get_local_placement(wall.ObjectPlacement) + m2 = np.eye(4) + m2[0][3] = -5 + assert np.allclose(m, m2) + + +class TestSetFalseOriginIFC2X3(test.bootstrap.IFC2X3, TestSetFalseOrigin): + pass