2022-01-19 12:18:33 +11:00
|
|
|
# IfcOpenShell - IFC toolkit and geometry engine
|
|
|
|
|
# Copyright (C) 2021 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/>.
|
|
|
|
|
|
2021-01-27 19:00:35 +11:00
|
|
|
import ifcopenshell
|
2022-10-05 19:44:32 +11:00
|
|
|
import ifcopenshell.util.system
|
2021-08-10 11:48:38 +10:00
|
|
|
import ifcopenshell.util.element
|
2021-01-27 19:00:35 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Usecase:
|
2022-12-16 15:29:50 +11:00
|
|
|
def __init__(self, file, product=None):
|
|
|
|
|
"""Copies a product
|
|
|
|
|
|
|
|
|
|
The following relationships are also duplicated:
|
|
|
|
|
|
|
|
|
|
* The copy will have the same object placement coordinates as the
|
|
|
|
|
original.
|
|
|
|
|
* The copy will have duplicated property sets, properties, and quantities
|
|
|
|
|
* The copy will have all nested distribution ports copied too
|
|
|
|
|
* The copy will be part of the same aggregate
|
|
|
|
|
* The copy will be contained in the same spatial structure
|
|
|
|
|
* The copy, if it is an occurrence, will have the same type
|
|
|
|
|
* Voids are duplicated too
|
|
|
|
|
* The copy will have the same material as the original. Parametric
|
|
|
|
|
material set usages will be copied.
|
2023-02-08 12:29:40 +11:00
|
|
|
* The copy will be part of the same groups as the original.
|
2022-12-16 15:29:50 +11:00
|
|
|
|
|
|
|
|
Be warned that:
|
|
|
|
|
|
|
|
|
|
* Representations are _not_ copied. Copying representations is an
|
|
|
|
|
expensive operation so for now the user is responsible for handling
|
|
|
|
|
representations.
|
|
|
|
|
* Filled voids are not copied, as there is no guarantee that the filling
|
|
|
|
|
will also be copied.
|
|
|
|
|
* Path connectivity is not copied, as there is no guarantee that the
|
|
|
|
|
connections are still valid.
|
|
|
|
|
|
|
|
|
|
:param product: The IfcProduct to copy.
|
|
|
|
|
:type param: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
:return: The copied product
|
|
|
|
|
:rtype: ifcopenshell.entity_instance.entity_instance
|
|
|
|
|
|
2023-01-10 10:16:28 +11:00
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
2022-12-16 15:29:50 +11:00
|
|
|
|
|
|
|
|
# We have a wall
|
|
|
|
|
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
|
|
|
|
|
|
|
|
|
# And now we have two
|
|
|
|
|
wall_copy = ifcopenshell.api.run("root.copy_class", model, product=wall)
|
|
|
|
|
"""
|
2021-01-27 19:00:35 +11:00
|
|
|
self.file = file
|
2022-12-16 15:29:50 +11:00
|
|
|
self.settings = {"product": product}
|
2021-01-27 19:00:35 +11:00
|
|
|
|
|
|
|
|
def execute(self):
|
2021-08-10 11:48:38 +10:00
|
|
|
result = ifcopenshell.util.element.copy(self.file, self.settings["product"])
|
2021-10-14 16:13:48 +11:00
|
|
|
self.copy_direct_attributes(result)
|
2021-08-10 11:48:38 +10:00
|
|
|
self.copy_indirect_attributes(self.settings["product"], result)
|
2021-01-27 19:00:35 +11:00
|
|
|
return result
|
|
|
|
|
|
2021-10-14 16:13:48 +11:00
|
|
|
def copy_direct_attributes(self, to_element):
|
|
|
|
|
self.remove_representations(to_element)
|
|
|
|
|
self.copy_object_placements(to_element)
|
2023-04-13 14:08:02 +10:00
|
|
|
self.copy_psets(to_element)
|
2021-10-14 16:13:48 +11:00
|
|
|
|
2021-08-10 11:48:38 +10:00
|
|
|
def copy_indirect_attributes(self, from_element, to_element):
|
|
|
|
|
for inverse in self.file.get_inverse(from_element):
|
|
|
|
|
if inverse.is_a("IfcRelDefinesByProperties"):
|
2021-09-21 14:19:41 +10:00
|
|
|
# Properties must not be shared between objects for convenience of authoring
|
2021-08-10 11:48:38 +10:00
|
|
|
inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
|
|
|
|
inverse.RelatedObjects = [to_element]
|
|
|
|
|
pset = ifcopenshell.util.element.copy_deep(self.file, inverse.RelatingPropertyDefinition)
|
|
|
|
|
inverse.RelatingPropertyDefinition = pset
|
2023-07-14 15:47:36 +05:00
|
|
|
elif (
|
|
|
|
|
inverse.is_a("IfcRelNests")
|
|
|
|
|
and inverse.RelatingObject == from_element
|
|
|
|
|
or inverse.is_a("IfcRelConnectsPortToElement")
|
|
|
|
|
and inverse.RelatedElement == from_element
|
|
|
|
|
):
|
|
|
|
|
# IfcRelConnectsPortToElement was used in IFC2X3
|
|
|
|
|
if inverse.is_a("IfcRelNests"):
|
|
|
|
|
ports = [e for e in inverse.RelatedObjects if e.is_a("IfcDistributionPort")]
|
|
|
|
|
else: # IfcRelConnectsPortToElement
|
|
|
|
|
ports = [inverse.RelatingPort]
|
|
|
|
|
if not ports:
|
|
|
|
|
continue
|
|
|
|
|
new_ports = [ifcopenshell.api.run("root.copy_class", self.file, product=p) for p in ports]
|
|
|
|
|
inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
|
|
|
|
|
|
|
|
|
if inverse.is_a("IfcRelNests"):
|
2022-05-03 18:05:27 +10:00
|
|
|
inverse.RelatingObject = to_element
|
|
|
|
|
inverse.RelatedObjects = new_ports
|
2023-07-14 15:47:36 +05:00
|
|
|
else:
|
|
|
|
|
inverse.RelatedElement = to_element
|
|
|
|
|
inverse.RelatingPort = new_ports[0]
|
|
|
|
|
|
|
|
|
|
for port in new_ports:
|
|
|
|
|
ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=port)
|
2023-08-15 10:35:36 +05:00
|
|
|
ifcopenshell.api.run("system.disconnect_port", self.file, port=port)
|
2023-07-14 15:47:36 +05:00
|
|
|
matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
|
|
|
|
|
ifcopenshell.api.run(
|
|
|
|
|
"geometry.edit_object_placement",
|
|
|
|
|
self.file,
|
|
|
|
|
product=port,
|
|
|
|
|
matrix=matrix,
|
|
|
|
|
is_si=False,
|
|
|
|
|
should_transform_children=False,
|
|
|
|
|
)
|
2021-09-21 14:19:41 +10:00
|
|
|
elif inverse.is_a("IfcRelAggregates") and inverse.RelatingObject == from_element:
|
|
|
|
|
continue
|
2021-10-14 15:52:38 +11:00
|
|
|
elif inverse.is_a("IfcRelContainedInSpatialStructure") and inverse.RelatingStructure == from_element:
|
|
|
|
|
continue
|
2021-10-26 20:51:27 +11:00
|
|
|
elif inverse.is_a("IfcRelDefinesByType") and inverse.RelatingType == from_element:
|
|
|
|
|
continue
|
2022-10-05 19:44:32 +11:00
|
|
|
elif inverse.is_a("IfcRelVoidsElement") and inverse.RelatingBuildingElement == from_element:
|
|
|
|
|
opening = inverse.RelatedOpeningElement
|
2022-10-09 22:22:12 +11:00
|
|
|
# We don't copy filled openings, since there is no guarantee the filling is also copied
|
2023-10-16 15:05:40 +05:00
|
|
|
if not opening.is_a("IfcOpeningElement") or not opening.HasFillings:
|
2022-10-09 22:22:12 +11:00
|
|
|
new_opening = ifcopenshell.api.run("root.copy_class", self.file, product=opening)
|
|
|
|
|
new_opening.VoidsElements[0].RelatingBuildingElement = to_element
|
|
|
|
|
if new_opening.ObjectPlacement and new_opening.ObjectPlacement.is_a("IfcLocalPlacement"):
|
|
|
|
|
if to_element.ObjectPlacement:
|
|
|
|
|
new_opening.ObjectPlacement.PlacementRelTo = to_element.ObjectPlacement
|
|
|
|
|
# For now, we do copy opening representations
|
|
|
|
|
if opening.Representation:
|
|
|
|
|
new_opening.Representation = ifcopenshell.util.element.copy_deep(
|
|
|
|
|
self.file, opening.Representation, exclude=["IfcGeometricRepresentationContext"]
|
|
|
|
|
)
|
2021-09-21 14:19:41 +10:00
|
|
|
elif inverse.is_a("IfcRelFillsElement"):
|
2021-08-30 18:59:50 +10:00
|
|
|
continue
|
2022-09-20 12:01:40 +10:00
|
|
|
elif inverse.is_a("IfcRelConnectsPathElements"):
|
|
|
|
|
continue
|
2021-10-22 12:09:57 +11:00
|
|
|
elif inverse.is_a("IfcRelAssociatesMaterial") and "Usage" in inverse.RelatingMaterial.is_a():
|
|
|
|
|
inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
|
|
|
|
inverse.RelatingMaterial = ifcopenshell.util.element.copy(self.file, inverse.RelatingMaterial)
|
|
|
|
|
inverse.RelatedObjects = [to_element]
|
2023-03-20 20:40:11 +11:00
|
|
|
elif inverse.is_a("IfcRelAssociatesMaterial") and "Set" in inverse.RelatingMaterial.is_a():
|
2022-01-14 13:15:47 +11:00
|
|
|
inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
2023-03-20 20:40:11 +11:00
|
|
|
inverse.RelatingMaterial = ifcopenshell.util.element.copy_deep(
|
|
|
|
|
self.file, inverse.RelatingMaterial, exclude=["IfcMaterial"]
|
|
|
|
|
)
|
2022-01-14 13:15:47 +11:00
|
|
|
inverse.RelatedObjects = [to_element]
|
2021-01-27 19:00:35 +11:00
|
|
|
else:
|
2021-08-10 11:48:38 +10:00
|
|
|
for i, value in enumerate(inverse):
|
|
|
|
|
if value == from_element:
|
|
|
|
|
new_inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
|
|
|
|
new_inverse[i] = to_element
|
|
|
|
|
elif isinstance(value, (tuple, list)) and from_element in value:
|
|
|
|
|
new_value = list(value)
|
|
|
|
|
new_value.append(to_element)
|
|
|
|
|
inverse[i] = new_value
|
|
|
|
|
|
|
|
|
|
def remove_representations(self, element):
|
|
|
|
|
if element.is_a("IfcProduct"):
|
|
|
|
|
element.Representation = None
|
|
|
|
|
elif element.is_a("IfcTypeProduct"):
|
|
|
|
|
element.RepresentationMaps = None
|
2021-10-14 16:13:48 +11:00
|
|
|
|
|
|
|
|
def copy_object_placements(self, element):
|
|
|
|
|
if not element.is_a("IfcProduct") or not element.ObjectPlacement:
|
|
|
|
|
return
|
|
|
|
|
element.ObjectPlacement = ifcopenshell.util.element.copy(self.file, element.ObjectPlacement)
|
2021-10-14 18:47:48 +11:00
|
|
|
element.ObjectPlacement.RelativePlacement = ifcopenshell.util.element.copy_deep(
|
|
|
|
|
self.file, element.ObjectPlacement.RelativePlacement
|
|
|
|
|
)
|
2023-04-13 14:08:02 +10:00
|
|
|
|
|
|
|
|
def copy_psets(self, element):
|
|
|
|
|
if not element.is_a("IfcTypeObject") or not element.HasPropertySets:
|
|
|
|
|
return
|
|
|
|
|
element.HasPropertySets = [
|
|
|
|
|
ifcopenshell.util.element.copy_deep(self.file, pset) for pset in element.HasPropertySets
|
|
|
|
|
]
|