mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
Add geometry.copy_representation API
Deep-copies the named representation from a source element to a target element. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -52,6 +52,7 @@ from .disconnect_element import disconnect_element
|
|||||||
from .disconnect_path import disconnect_path
|
from .disconnect_path import disconnect_path
|
||||||
from .edit_object_placement import edit_object_placement
|
from .edit_object_placement import edit_object_placement
|
||||||
from .map_representation import map_representation
|
from .map_representation import map_representation
|
||||||
|
from .copy_representation import copy_representation
|
||||||
from .regenerate_wall_representation import regenerate_wall_representation
|
from .regenerate_wall_representation import regenerate_wall_representation
|
||||||
from .remove_boolean import remove_boolean
|
from .remove_boolean import remove_boolean
|
||||||
from .remove_representation import remove_representation
|
from .remove_representation import remove_representation
|
||||||
@@ -65,6 +66,7 @@ __all__ = [
|
|||||||
"add_boolean",
|
"add_boolean",
|
||||||
"clip_solid",
|
"clip_solid",
|
||||||
"clip_solid_bounded",
|
"clip_solid_bounded",
|
||||||
|
"copy_representation",
|
||||||
"add_door_representation",
|
"add_door_representation",
|
||||||
"add_footprint_representation",
|
"add_footprint_representation",
|
||||||
"add_mesh_representation",
|
"add_mesh_representation",
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# IfcOpenShell - IFC toolkit and geometry engine
|
||||||
|
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import ifcopenshell.api.geometry
|
||||||
|
import ifcopenshell.util.element
|
||||||
|
import ifcopenshell.util.representation
|
||||||
|
|
||||||
|
|
||||||
|
def copy_representation(
|
||||||
|
file: ifcopenshell.file,
|
||||||
|
source: ifcopenshell.entity_instance,
|
||||||
|
target: ifcopenshell.entity_instance,
|
||||||
|
context_identifier: str = "Body",
|
||||||
|
) -> Optional[ifcopenshell.entity_instance]:
|
||||||
|
"""Copy a geometric representation from one element to another.
|
||||||
|
|
||||||
|
Finds the named representation on ``source``, deep-copies its entity
|
||||||
|
graph (geometry items, profiles, placements, etc.), and assigns the copy
|
||||||
|
to ``target``. Representation contexts are shared rather than copied.
|
||||||
|
If ``target`` already has a matching representation it is removed and
|
||||||
|
replaced.
|
||||||
|
|
||||||
|
If no matching representation is found on ``source``, returns ``None``
|
||||||
|
and leaves ``target`` unchanged.
|
||||||
|
|
||||||
|
:param source: The element to copy the representation from.
|
||||||
|
:param target: The element to assign the copied representation to.
|
||||||
|
:param context_identifier: The RepresentationIdentifier to look up on
|
||||||
|
``source`` (e.g. ``"Body"``, ``"Axis"``, ``"Box"``).
|
||||||
|
Defaults to ``"Body"``.
|
||||||
|
:return: The newly created IfcShapeRepresentation, or None if no
|
||||||
|
matching representation was found on ``source``.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
wall_a = model.by_id(1)
|
||||||
|
wall_b = model.by_id(2)
|
||||||
|
|
||||||
|
# Give wall_b the same body geometry as wall_a.
|
||||||
|
ifcopenshell.api.geometry.copy_representation(model,
|
||||||
|
source=wall_a, target=wall_b)
|
||||||
|
"""
|
||||||
|
source_rep = ifcopenshell.util.representation.get_representation(
|
||||||
|
source, "Model", context_identifier
|
||||||
|
)
|
||||||
|
if source_rep is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
new_rep = ifcopenshell.util.element.copy_deep(
|
||||||
|
file, source_rep, exclude=["IfcGeometricRepresentationContext"]
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_rep = ifcopenshell.util.representation.get_representation(
|
||||||
|
target, "Model", context_identifier
|
||||||
|
)
|
||||||
|
if existing_rep:
|
||||||
|
ifcopenshell.api.geometry.unassign_representation(file, product=target, representation=existing_rep)
|
||||||
|
ifcopenshell.api.geometry.remove_representation(file, representation=existing_rep)
|
||||||
|
|
||||||
|
ifcopenshell.api.geometry.assign_representation(file, product=target, representation=new_rep)
|
||||||
|
return new_rep
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
# IfcOpenShell - IFC toolkit and geometry engine
|
||||||
|
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import ifcopenshell.api.geometry
|
||||||
|
import ifcopenshell.api.root
|
||||||
|
import ifcopenshell.util.representation
|
||||||
|
import ifcopenshell.util.shape_builder
|
||||||
|
import test.bootstrap
|
||||||
|
|
||||||
|
|
||||||
|
class TestCopyRepresentation(test.bootstrap.IFC4):
|
||||||
|
def _body_context(self):
|
||||||
|
body = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW")
|
||||||
|
if body is None:
|
||||||
|
model = self.file.createIfcGeometricRepresentationContext(
|
||||||
|
ContextType="Model", CoordinateSpaceDimension=3, Precision=1e-5,
|
||||||
|
WorldCoordinateSystem=self.file.createIfcAxis2Placement3D(
|
||||||
|
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
body = self.file.createIfcGeometricRepresentationSubContext(
|
||||||
|
ContextIdentifier="Body", ContextType="Model",
|
||||||
|
TargetView="MODEL_VIEW", ParentContext=model,
|
||||||
|
)
|
||||||
|
return body
|
||||||
|
|
||||||
|
def _add_body_rep(self, element):
|
||||||
|
body = self._body_context()
|
||||||
|
rep = ifcopenshell.api.geometry.add_wall_representation(
|
||||||
|
self.file, context=body, length=5.0, height=3.0, thickness=0.2
|
||||||
|
)
|
||||||
|
ifcopenshell.api.geometry.assign_representation(self.file, product=element, representation=rep)
|
||||||
|
return rep
|
||||||
|
|
||||||
|
def test_copy_to_empty_target(self):
|
||||||
|
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
self._add_body_rep(wall_a)
|
||||||
|
|
||||||
|
result = ifcopenshell.api.geometry.copy_representation(
|
||||||
|
self.file, source=wall_a, target=wall_b
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result.is_a("IfcShapeRepresentation")
|
||||||
|
target_rep = ifcopenshell.util.representation.get_representation(wall_b, "Model", "Body")
|
||||||
|
assert target_rep is not None
|
||||||
|
assert target_rep == result
|
||||||
|
|
||||||
|
def test_source_rep_entities_are_distinct(self):
|
||||||
|
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
source_rep = self._add_body_rep(wall_a)
|
||||||
|
|
||||||
|
new_rep = ifcopenshell.api.geometry.copy_representation(
|
||||||
|
self.file, source=wall_a, target=wall_b
|
||||||
|
)
|
||||||
|
|
||||||
|
assert new_rep.id() != source_rep.id()
|
||||||
|
assert new_rep.Items[0].id() != source_rep.Items[0].id()
|
||||||
|
|
||||||
|
def test_context_is_shared_not_copied(self):
|
||||||
|
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
source_rep = self._add_body_rep(wall_a)
|
||||||
|
|
||||||
|
new_rep = ifcopenshell.api.geometry.copy_representation(
|
||||||
|
self.file, source=wall_a, target=wall_b
|
||||||
|
)
|
||||||
|
|
||||||
|
assert new_rep.ContextOfItems.id() == source_rep.ContextOfItems.id()
|
||||||
|
|
||||||
|
def test_replaces_existing_target_rep(self):
|
||||||
|
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
self._add_body_rep(wall_a)
|
||||||
|
old_rep = self._add_body_rep(wall_b)
|
||||||
|
old_rep_id = old_rep.id()
|
||||||
|
|
||||||
|
ifcopenshell.api.geometry.copy_representation(
|
||||||
|
self.file, source=wall_a, target=wall_b
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.file.by_id(old_rep_id)
|
||||||
|
assert False, "old representation still exists"
|
||||||
|
except RuntimeError:
|
||||||
|
pass # entity was removed, as expected
|
||||||
|
|
||||||
|
def test_source_unchanged_after_copy(self):
|
||||||
|
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
source_rep = self._add_body_rep(wall_a)
|
||||||
|
source_rep_id = source_rep.id()
|
||||||
|
|
||||||
|
ifcopenshell.api.geometry.copy_representation(
|
||||||
|
self.file, source=wall_a, target=wall_b
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.file.by_id(source_rep_id) is not None # source must still exist
|
||||||
|
assert ifcopenshell.util.representation.get_representation(wall_a, "Model", "Body") is not None
|
||||||
|
|
||||||
|
def test_returns_none_when_no_matching_rep(self):
|
||||||
|
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
|
||||||
|
result = ifcopenshell.api.geometry.copy_representation(
|
||||||
|
self.file, source=wall_a, target=wall_b
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_custom_context_identifier(self):
|
||||||
|
wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||||
|
self._add_body_rep(wall_a)
|
||||||
|
|
||||||
|
# "Axis" doesn't exist on wall_a, so should return None
|
||||||
|
result = ifcopenshell.api.geometry.copy_representation(
|
||||||
|
self.file, source=wall_a, target=wall_b, context_identifier="Axis"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestCopyRepresentationIFC2X3(test.bootstrap.IFC2X3, TestCopyRepresentation):
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user