First Commit
This commit is contained in:
@@ -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",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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)
|
||||
Reference in New Issue
Block a user