First Commit

This commit is contained in:
2026-05-31 10:17:09 +07:00
commit 17a9c69379
4547 changed files with 1170384 additions and 0 deletions
@@ -0,0 +1,42 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 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/>.
"""Create, copy, or remove physical elements such as walls, doors, slabs, etc
This is one of the most used API modules and should be used any time you want
to create, remove, copy, or change a physical or spatial element. See
:func:`create_entity` to get started.
This module should also be used to create types. To then associate types with
elements, see :mod:`ifcopenshell.api.type`.
"""
from .. import wrap_usecases
from .copy_class import copy_class
from .create_entity import create_entity
from .reassign_class import reassign_class
from .remove_product import remove_product
wrap_usecases(__path__, __name__)
__all__ = [
"copy_class",
"create_entity",
"reassign_class",
"remove_product",
]
@@ -0,0 +1,193 @@
# 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/>.
import ifcopenshell
import ifcopenshell.api.geometry
import ifcopenshell.api.material
import ifcopenshell.api.root
import ifcopenshell.api.system
import ifcopenshell.util.element
import ifcopenshell.util.placement
def copy_class(file: ifcopenshell.file, product: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
"""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.
* The copy will be part of the same groups as the original.
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.
:return: The copied product
Example:
.. code:: python
# We have a wall
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
# And now we have two
wall_copy = ifcopenshell.api.root.copy_class(model, product=wall)
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(product)
class Usecase:
file: ifcopenshell.file
def execute(self, product: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
result = ifcopenshell.util.element.copy(self.file, product)
self.copy_direct_attributes(result)
self.copy_indirect_attributes(product, result)
return result
def copy_direct_attributes(self, to_element: ifcopenshell.entity_instance) -> None:
self.remove_representations(to_element)
self.copy_object_placements(to_element)
self.copy_psets(to_element)
def copy_indirect_attributes(
self, from_element: ifcopenshell.entity_instance, to_element: ifcopenshell.entity_instance
) -> None:
for inverse in self.file.get_inverse(from_element):
if inverse.is_a("IfcRelDefinesByProperties"):
# Properties must not be shared between objects for convenience of authoring
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
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.root.copy_class(self.file, product=p) for p in ports]
inverse = ifcopenshell.util.element.copy(self.file, inverse)
if inverse.is_a("IfcRelNests"):
inverse.RelatingObject = to_element
inverse.RelatedObjects = new_ports
else:
inverse.RelatedElement = to_element
inverse.RelatingPort = new_ports[0]
for port in new_ports:
ifcopenshell.api.system.unassign_port(self.file, element=from_element, port=port)
ifcopenshell.api.system.disconnect_port(self.file, port=port)
matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
ifcopenshell.api.geometry.edit_object_placement(
self.file,
product=port,
matrix=matrix,
is_si=False,
should_transform_children=False,
)
elif inverse.is_a("IfcRelAggregates") and inverse.RelatingObject == from_element:
continue
elif inverse.is_a("IfcRelContainedInSpatialStructure") and inverse.RelatingStructure == from_element:
continue
elif inverse.is_a("IfcRelDefinesByType") and inverse.RelatingType == from_element:
continue
elif inverse.is_a("IfcRelVoidsElement") and inverse.RelatingBuildingElement == from_element:
opening = inverse.RelatedOpeningElement
# We don't copy filled openings, since there is no guarantee the filling is also copied
if not opening.is_a("IfcOpeningElement") or not opening.HasFillings:
new_opening = ifcopenshell.api.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"]
)
elif inverse.is_a("IfcRelFillsElement"):
continue
elif inverse.is_a("IfcRelConnectsPathElements"):
continue
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]
elif inverse.is_a("IfcRelAssociatesMaterial") and "Set" in inverse.RelatingMaterial.is_a():
inverse = ifcopenshell.util.element.copy(self.file, inverse)
inverse.RelatingMaterial = ifcopenshell.api.material.copy_material(self.file, inverse.RelatingMaterial)
inverse.RelatedObjects = [to_element]
else:
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: ifcopenshell.entity_instance) -> None:
if element.is_a("IfcProduct"):
element.Representation = None
elif element.is_a("IfcTypeProduct"):
element.RepresentationMaps = None
def copy_object_placements(self, element: ifcopenshell.entity_instance) -> None:
if not element.is_a("IfcProduct") or not element.ObjectPlacement:
return
element.ObjectPlacement = ifcopenshell.util.element.copy(self.file, element.ObjectPlacement)
element.ObjectPlacement.RelativePlacement = ifcopenshell.util.element.copy_deep(
self.file, element.ObjectPlacement.RelativePlacement
)
def copy_psets(self, element: ifcopenshell.entity_instance) -> None:
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
]
@@ -0,0 +1,140 @@
# 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/>.
from typing import Optional
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.guid
def create_entity(
file: ifcopenshell.file,
ifc_class: str = "IfcBuildingElementProxy",
predefined_type: Optional[str] = None,
name: Optional[str] = None,
) -> ifcopenshell.entity_instance:
"""Create a new rooted product
This is a critical function used to create almost any rooted product or
product type. If you want to create walls, spaces, buildings, wall
types, and so on, use this function.
Just specify the class you want to create, as well as the predefined
type and name. It will handle the storage of the predefined type and
check whether the predefined type is built-in or custom. It will also
generate a valid GlobalId and store ownership history. It will also
handle some edge cases for default validity where users might forget to
populate some mandatory attributes. For example, doors must define an
operation type but many people forget.
:param ifc_class: Any rooted IFC class.
:param predefined_type: Any built-in or user-defined predefined type that
is applicable to that IFC class. For user-defined predefined types
just enter in any value and the API will handle it automatically.
:param name: The name of the new element.
:return: The newly created element based on the specified IFC class.
Example:
.. code:: python
# We have a project.
ifcopenshell.api.root.create_entity(model, ifc_class="IfcProject")
# We have a building.
ifcopenshell.api.root.create_entity(model, ifc_class="IfcBuilding")
# We have a wall.
ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
# We have a wall type.
ifcopenshell.api.root.create_entity(model, ifc_class="IfcWallType")
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(ifc_class, predefined_type, name)
class Usecase:
file: ifcopenshell.file
def execute(
self, ifc_class: str, predefined_type: Optional[str] = None, name: Optional[str] = None
) -> ifcopenshell.entity_instance:
element = self.file.create_entity(
ifc_class,
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(self.file),
}
)
element.Name = name or None
if predefined_type:
if hasattr(element, "PredefinedType"):
try:
element.PredefinedType = predefined_type
except:
element.PredefinedType = "USERDEFINED"
if hasattr(element, "ObjectType"):
element.ObjectType = predefined_type
elif hasattr(element, "ElementType"):
element.ElementType = predefined_type
elif hasattr(element, "ProcessType"):
element.ProcessType = predefined_type
elif hasattr(element, "ObjectType"):
element.ObjectType = predefined_type
if self.file.schema == "IFC2X3":
self.handle_2x3_defaults(element)
else:
self.handle_4_defaults(element)
return element
def handle_2x3_defaults(self, element: ifcopenshell.entity_instance) -> None:
if element.is_a("IfcElementType"):
if hasattr(element, "PredefinedType") and not element.PredefinedType:
element.PredefinedType = "NOTDEFINED"
if element.is_a("IfcSpatialStructureElement"):
element.CompositionType = "ELEMENT"
elif element.is_a("IfcRoof"):
element.ShapeType = "NOTDEFINED"
elif element.is_a("IfcFurnitureType"):
element.AssemblyPlace = "NOTDEFINED"
elif element.is_a("IfcDoorStyle") or element.is_a("IfcWindowStyle"):
element.OperationType = "NOTDEFINED"
element.ConstructionType = "NOTDEFINED"
element.ParameterTakesPrecedence = False
element.Sizeable = False
def handle_4_defaults(self, element: ifcopenshell.entity_instance) -> None:
if element.is_a("IfcElementType"):
if hasattr(element, "PredefinedType") and not element.PredefinedType:
element.PredefinedType = "NOTDEFINED"
if element.file.schema == "IFC4" and (element.is_a("IfcDoorStyle") or element.is_a("IfcWindowStyle")):
element.OperationType = "NOTDEFINED"
element.ConstructionType = "NOTDEFINED"
element.ParameterTakesPrecedence = False
element.Sizeable = False
elif element.is_a("IfcDoorType"):
element.OperationType = "NOTDEFINED"
elif element.is_a("IfcWindowType"):
element.PartitioningType = "NOTDEFINED"
elif element.is_a("IfcFurnitureType"):
element.AssemblyPlace = "NOTDEFINED"
@@ -0,0 +1,236 @@
# 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/>.
from typing import Literal, Optional, Union
import ifcopenshell
import ifcopenshell.api.aggregate
import ifcopenshell.api.geometry
import ifcopenshell.api.pset
import ifcopenshell.api.spatial
import ifcopenshell.api.type
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.schema
import ifcopenshell.util.type
def reassign_class(
file: ifcopenshell.file,
product: ifcopenshell.entity_instance,
ifc_class: str = "IfcBuildingElementProxy",
predefined_type: Optional[str] = None,
occurrence_class: Optional[str] = None,
) -> ifcopenshell.entity_instance:
"""Changes the class of a product
If you ever created a wall then realised it's meant to be something
else, this function lets you change the IFC class whilst retaining all
other geometry and relationships.
This is especially useful when dealing with poorly classified data from
proprietary software with limited IFC capabilities.
If you are reassigning a type, the occurrence classes are also
reassigned to maintain validity.
Vice versa, if you are reassigning an occurrence, the type is also
reassigned in IFC4 and up. In IFC2X3, this may not occur if the type
cannot be unambiguously derived, so you are required to manually check
this.
Reassigning type class to occurrence (and vice versa) is supported.
:param product: The IfcProduct that you want to change the class of.
:param ifc_class: The new IFC class you want to change it to.
:param predefined_type: In case you want to change the predefined type
too. User defined types are also allowed, just type what you want.
:param occurrence_class: IFC class to assign to occurrences in case
if provided ``ifc_class`` is IfcTypeProduct.
If omitted, class will be deduced automatically from the type.
Only really needed in IFC2X3, since in IFC4+ there is no ambiguity on
what class to assign to occurrences.
:return: The newly modified product.
Example:
.. code:: python
# We have a wall.
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
# Oh, did I say wall? I meant slab.
slab = ifcopenshell.api.root.reassign_class(model, product=wall, ifc_class="IfcSlab")
# Warning: this will crash since wall doesn't exist any more.
print(wall) # Kaboom.
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(product, ifc_class, predefined_type, occurrence_class)
class Usecase:
file: ifcopenshell.file
def execute(
self,
product: ifcopenshell.entity_instance,
ifc_class: str,
predefined_type: Union[str, None],
occurrence_class: Union[str, None],
) -> ifcopenshell.entity_instance:
self.occurrence_class = occurrence_class
was_type_product_before = product.is_a("IfcTypeProduct")
self.schema = schema = ifcopenshell.schema_by_name(self.file.schema)
is_type_product_after: bool
is_type_product_after = schema.declaration_by_name(ifc_class)._is("IfcTypeProduct")
if was_type_product_before == is_type_product_after:
return self.simple_reassignment(product, ifc_class, predefined_type)
switch_type = "occurrence_to_type" if is_type_product_after else "type_to_occurrence"
return self.switch_between_class_types(
product,
switch_type,
ifc_class,
predefined_type,
)
def switch_between_class_types(
self,
element: ifcopenshell.entity_instance,
switch_type: Literal["occurrence_to_type", "type_to_occurrence"],
ifc_class: str,
predefined_type: Union[str, None],
) -> ifcopenshell.entity_instance:
psets_to_reassign: list[ifcopenshell.entity_instance] = []
representations = list(ifcopenshell.util.representation.get_representations_iter(element))
for rep in representations:
if switch_type == "type_to_occurrence":
rep = ifcopenshell.util.representation.resolve_representation(rep)
ifcopenshell.api.geometry.unassign_representation(self.file, product=element, representation=rep)
if switch_type == "type_to_occurrence":
occurrences = ifcopenshell.util.element.get_types(element)
# Don't need to reassign as psets are linked to type directly, without rel.
psets_to_reassign = element.HasPropertySets or []
element = self.reassign_class(element, ifc_class, predefined_type)
ifcopenshell.api.type.unassign_type(self.file, occurrences)
else: # occurrence_to_type
element_type = ifcopenshell.util.element.get_type(element)
# Handle element type.
if element_type:
ifcopenshell.api.type.unassign_type(self.file, [element])
# Handle containers and aggregates.
if ifcopenshell.util.element.get_container(element):
ifcopenshell.api.spatial.unassign_container(self.file, [element])
elif ifcopenshell.util.element.get_aggregate(element):
ifcopenshell.api.aggregate.unassign_object(self.file, [element])
psets = ifcopenshell.util.element.get_psets(element)
for pset_name in psets:
pset = self.file.by_id(psets[pset_name]["id"])
psets_to_reassign.append(pset)
ifcopenshell.api.pset.unassign_pset(self.file, [element], pset)
element = self.reassign_class(element, ifc_class, predefined_type)
# Reassign psets.
for pset in psets_to_reassign:
ifcopenshell.api.pset.assign_pset(self.file, [element], pset)
# Reassign representations.
for rep in representations:
ifcopenshell.api.geometry.assign_representation(self.file, product=element, representation=rep)
# Keep IFC valid (PlacementForShapeRepresentation).
if switch_type == "type_to_occurrence" and representations:
ifcopenshell.api.geometry.edit_object_placement(self.file, product=element)
return element
def simple_reassignment(
self,
element: ifcopenshell.entity_instance,
ifc_class: str,
predefined_type: Union[str, None],
) -> ifcopenshell.entity_instance:
"""
Simple class reassignment doesn't involve changing class type.
E.g. IfcWindow (IfcProduct class) -> IfcWall (other IfcProduct class).
Changing class type (e.g. IfcWindowType -> IfcWindow) is more complex
as it requires to recreate some entities / assign them in different way
(e.g. representations, property sets and their rels).
"""
element = self.reassign_class(element, ifc_class, predefined_type)
if element.is_a("IfcTypeProduct"):
if self.occurrence_class:
occurrence_class = self.occurrence_class
else:
# NOTE: in theory we can skip reassignment in IFC2X3 in some cases
# e.g. if occurrence is IfcRoof and we're reassigning to IfcBuildingElementProxyType
# but currently type_to_entity_map doesn't completely match entity_to_type_map,
# see type.py for more details.
occurrence_class = next(
iter(ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema))
)
assert not self.schema.declaration_by_name(occurrence_class)._is(
"IfcTypeProduct"
), f"Unexpected occurrence_class: '{occurrence_class}' / '{self.occurrence_class}'."
for occurrence in ifcopenshell.util.element.get_types(element):
self.reassign_class(occurrence, occurrence_class, predefined_type)
else:
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
ifc_class_ = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class, self.file.schema)))
element_type = self.reassign_class(element_type, ifc_class_, predefined_type)
ifc_class = element.is_a()
for occurrence in ifcopenshell.util.element.get_types(element_type):
if occurrence == element:
continue
self.reassign_class(occurrence, ifc_class, predefined_type)
return element
def reassign_class(
self, element: ifcopenshell.entity_instance, ifc_class: str, predefined_type: Union[str, None]
) -> ifcopenshell.entity_instance:
element = ifcopenshell.util.schema.reassign_class(self.file, element, ifc_class)
if predefined_type and hasattr(element, "PredefinedType"):
try:
element.PredefinedType = predefined_type
except:
# PredefinedType wasn't in the respective enum, assume it's actually USERDEFINED
# and set .ElementType / .ObjectType to the provided predefined type
element.PredefinedType = "USERDEFINED"
if element.is_a("IfcTypeProduct"):
element.ElementType = predefined_type
else:
element.ObjectType = predefined_type
return element
@@ -0,0 +1,223 @@
# 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/>.
import ifcopenshell.api.boundary
import ifcopenshell.api.feature
import ifcopenshell.api.geometry
import ifcopenshell.api.grid
import ifcopenshell.api.material
import ifcopenshell.api.pset
import ifcopenshell.api.root
import ifcopenshell.api.type
import ifcopenshell.util.element
def remove_product(file: ifcopenshell.file, product: ifcopenshell.entity_instance) -> None:
"""Removes a product
This is effectively a smart delete function that not only removes a
product, but also all of its relationships. It is always recommended to
use this function to prevent orphaned data in your IFC model.
This is intended to be used for removing:
- IfcAnnotation
- IfcElement
- IfcElementType
- IfcSpatialElement
- IfcSpatialElementType
For example, geometric representations are removed. Placement
coordinates are also removed. Properties are removed. Material, type,
containment, aggregation, and nesting relationships are removed (but
naturally, the materials, types, containers, etc themselves remain).
:param product: The element to remove.
:return: None
Example:
.. code:: python
# We have a wall.
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
# No we don't.
ifcopenshell.api.root.remove_product(model, product=wall)
"""
representations: list[ifcopenshell.entity_instance] = []
if product.is_a("IfcProduct"):
if product.Representation:
representations = product.Representation.Representations or []
else:
representations = []
# remove object placements
object_placement = product.ObjectPlacement
if object_placement:
if file.get_total_inverses(object_placement) == 1:
product.ObjectPlacement = None # remove the inverse for remove_deep2 to work
ifcopenshell.util.element.remove_deep2(file, object_placement)
elif product.is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []]
# remove psets
psets = product.HasPropertySets or []
for pset in psets:
if file.get_total_inverses(pset) != 1:
continue
ifcopenshell.api.pset.remove_pset(file, product=product, pset=pset)
for representation in representations:
ifcopenshell.api.geometry.unassign_representation(file, product=product, representation=representation)
ifcopenshell.api.geometry.remove_representation(file, representation=representation)
for opening in getattr(product, "HasOpenings", []) or []:
ifcopenshell.api.feature.remove_feature(file, feature=opening.RelatedOpeningElement)
if product.is_a("IfcGrid"):
for axis in product.UAxes + product.VAxes + (product.WAxes or ()):
ifcopenshell.api.grid.remove_grid_axis(file, axis=axis)
def element_exists(element_id):
try:
file.by_id(element_id)
return True
except RuntimeError:
return False
# TODO: remove object placement and other relationships
for inverse_id in [i.id() for i in file.get_inverse(product)]:
try:
inverse = file.by_id(inverse_id)
except:
continue
if inverse.is_a("IfcRelDefinesByProperties"):
ifcopenshell.api.pset.remove_pset(file, product=product, pset=inverse.RelatingPropertyDefinition)
elif inverse.is_a("IfcRelAssociatesMaterial"):
ifcopenshell.api.material.unassign_material(file, products=[product])
elif inverse.is_a("IfcRelDefinesByType"):
if inverse.RelatingType == product:
ifcopenshell.api.type.unassign_type(file, related_objects=inverse.RelatedObjects)
else:
ifcopenshell.api.type.unassign_type(file, related_objects=[product])
elif inverse.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.boundary.remove_boundary(file, boundary=inverse)
elif inverse.is_a("IfcRelFillsElement"):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelVoidsElement"):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelServicesBuildings"):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelNests"):
if inverse.RelatingObject == product:
inverse_id = inverse.id()
for subelement in inverse.RelatedObjects:
if subelement.is_a("IfcDistributionPort"):
ifcopenshell.api.root.remove_product(file, product=subelement)
# IfcRelNests could have been already deleted after removing one of the products
if element_exists(inverse_id):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.RelatedObjects == (product,):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAggregates"):
if inverse.RelatingObject == product or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelContainedInSpatialStructure"):
if inverse.RelatingStructure == product or len(inverse.RelatedElements) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelConnectsElements"):
if inverse.is_a("IfcRelConnectsWithRealizingElements"):
if product not in (inverse.RelatingElement, inverse.RelatedElement) and any(
el for el in inverse.RealizingElements if el != product
):
continue
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelConnectsPortToElement"):
if inverse.RelatedElement == product:
ifcopenshell.api.root.remove_product(file, product=inverse.RelatingPort)
elif inverse.RelatingPort == product:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelConnectsPorts"):
if product not in (inverse.RelatingPort, inverse.RelatedPort):
# if it's not RelatingPort/RelatedPort then it's optional RealizingElement
# so we keep the relationship
continue
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToGroup"):
if len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToProduct"):
if inverse.RelatingProduct == product:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelFlowControlElements"):
if inverse.RelatingFlowElement == product:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.RelatedControlElements == (product,):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = product.OwnerHistory
file.remove(product)
if history:
ifcopenshell.util.element.remove_deep2(file, history)