Generate functions for all API usecases for better static code features. See #2693.

This commit is contained in:
Dion Moult
2024-05-06 14:35:39 +10:00
parent 10f894e2ea
commit d11ec67129
330 changed files with 13283 additions and 13751 deletions
@@ -15,3 +15,8 @@
#
# 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 .copy_class import copy_class
from .create_entity import create_entity
from .reassign_class import reassign_class
from .remove_product import remove_product
@@ -21,52 +21,55 @@ import ifcopenshell.util.system
import ifcopenshell.util.element
def copy_class(file, product=None) -> 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.
* 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.
:type param: ifcopenshell.entity_instance
:return: The copied product
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# 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)
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {"product": product}
return usecase.execute()
class Usecase:
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.
* 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.
:type param: ifcopenshell.entity_instance
:return: The copied product
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# 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)
"""
self.file = file
self.settings = {"product": product}
def execute(self):
result = ifcopenshell.util.element.copy(self.file, self.settings["product"])
self.copy_direct_attributes(result)
@@ -21,63 +21,65 @@ import ifcopenshell.api
from typing import Optional
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.
:type ifc_class: str,optional
: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.
:type predefined_type: str,optional
:param name: The name of the new element.
:type name: str,optional
:return: The newly created element based on the specified IFC class.
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# We have a project.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
# We have a building.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
# We have a wall.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# We have a wall type.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType")
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {
"ifc_class": ifc_class,
"predefined_type": predefined_type,
"name": name,
}
return usecase.execute()
class Usecase:
def __init__(
self,
file: ifcopenshell.file,
ifc_class: str = "IfcBuildingElementProxy",
predefined_type: Optional[str] = None,
name: Optional[str] = None,
):
"""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.
:type ifc_class: str,optional
: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.
:type predefined_type: str,optional
:param name: The name of the new element.
:type name: str,optional
:return: The newly created element based on the specified IFC class.
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# We have a project.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
# We have a building.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
# We have a wall.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# We have a wall type.
ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType")
"""
self.file = file
self.settings = {
"ifc_class": ifc_class,
"predefined_type": predefined_type,
"name": name,
}
def execute(self) -> ifcopenshell.entity_instance:
def execute(self):
element = self.file.create_entity(
self.settings["ifc_class"],
**{
@@ -22,61 +22,63 @@ import ifcopenshell.util.schema
import ifcopenshell.util.element
def reassign_class(
file,
product=None,
ifc_class="IfcBuildingElementProxy",
predefined_type=None,
) -> None:
"""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.
:param product: The IfcProduct that you want to change the class of.
:type product: ifcopenshell.entity_instance
:param ifc_class: The new IFC class you want to change it to.
:type ifc_class: str,optional
:param predefined_type: In case you want to change the predefined type
too. User defined types are also allowed, just type what you want.
:type predefined_type: str,optional
:return: The newly modified product.
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# We have a wall.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# Oh, did I say wall? I meant slab.
slab = ifcopenshell.api.run("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
usecase.settings = {
"product": product,
"ifc_class": ifc_class,
"predefined_type": predefined_type,
}
return usecase.execute()
class Usecase:
def __init__(
self,
file,
product=None,
ifc_class="IfcBuildingElementProxy",
predefined_type=None,
):
"""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.
:param product: The IfcProduct that you want to change the class of.
:type product: ifcopenshell.entity_instance
:param ifc_class: The new IFC class you want to change it to.
:type ifc_class: str,optional
:param predefined_type: In case you want to change the predefined type
too. User defined types are also allowed, just type what you want.
:type predefined_type: str,optional
:return: The newly modified product.
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# We have a wall.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# Oh, did I say wall? I meant slab.
slab = ifcopenshell.api.run("root.reassign_class", model, product=wall, ifc_class="IfcSlab")
# Warning: this will crash since wall doesn't exist any more.
print(wall) # Kaboom.
"""
self.file = file
self.settings = {
"product": product,
"ifc_class": ifc_class,
"predefined_type": predefined_type,
}
def execute(self):
element = self.reassign_class(self.settings["product"], self.settings["ifc_class"])
@@ -20,212 +20,207 @@ import ifcopenshell.api
import ifcopenshell.util.element
class Usecase:
def __init__(self, file: ifcopenshell.file, product: ifcopenshell.entity_instance):
"""Removes a product
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 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:
This is intended to be used for removing:
- IfcAnnotation
- IfcElement
- IfcElementType
- IfcSpatialElement
- IfcSpatialElementType
- 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).
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.
:type product: ifcopenshell.entity_instance
:return: None
:rtype: None
:param product: The element to remove.
:type product: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
Example:
.. code:: python
.. code:: python
# We have a wall.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# We have a wall.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
# No we don't.
ifcopenshell.api.run("root.remove_product", model, product=wall)
"""
self.file = file
self.settings = {"product": product}
# No we don't.
ifcopenshell.api.run("root.remove_product", model, product=wall)
"""
settings = {"product": product}
def execute(self) -> None:
representations = []
if self.settings["product"].is_a("IfcProduct"):
if self.settings["product"].Representation:
representations = self.settings["product"].Representation.Representations or []
else:
representations = []
representations = []
if settings["product"].is_a("IfcProduct"):
if settings["product"].Representation:
representations = settings["product"].Representation.Representations or []
else:
representations = []
# remove object placements
object_placement = self.settings["product"].ObjectPlacement
if object_placement:
if self.file.get_total_inverses(object_placement) == 1:
self.settings["product"].ObjectPlacement = None # remove the inverse for remove_deep2 to work
ifcopenshell.util.element.remove_deep2(self.file, object_placement)
# remove object placements
object_placement = settings["product"].ObjectPlacement
if object_placement:
if file.get_total_inverses(object_placement) == 1:
settings["product"].ObjectPlacement = None # remove the inverse for remove_deep2 to work
ifcopenshell.util.element.remove_deep2(file, object_placement)
elif self.settings["product"].is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in self.settings["product"].RepresentationMaps or []]
elif settings["product"].is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in settings["product"].RepresentationMaps or []]
# remove psets
psets = self.settings["product"].HasPropertySets or []
for pset in psets:
if self.file.get_total_inverses(pset) != 1:
continue
ifcopenshell.api.run(
"pset.remove_pset",
self.file,
product=self.settings["product"],
pset=pset,
)
for representation in representations:
ifcopenshell.api.run(
"geometry.unassign_representation",
self.file,
**{"product": self.settings["product"], "representation": representation}
)
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
for opening in getattr(self.settings["product"], "HasOpenings", []) or []:
ifcopenshell.api.run("void.remove_opening", self.file, opening=opening.RelatedOpeningElement)
if self.settings["product"].is_a("IfcGrid"):
for axis in (
self.settings["product"].UAxes + self.settings["product"].VAxes + (self.settings["product"].WAxes or ())
):
ifcopenshell.api.run("grid.remove_grid_axis", self.file, axis=axis)
def element_exists(element_id):
try:
self.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 self.file.get_inverse(self.settings["product"])]:
try:
inverse = self.file.by_id(inverse_id)
except:
# remove psets
psets = settings["product"].HasPropertySets or []
for pset in psets:
if file.get_total_inverses(pset) != 1:
continue
if inverse.is_a("IfcRelDefinesByProperties"):
ifcopenshell.api.run(
"pset.remove_pset",
self.file,
product=self.settings["product"],
pset=inverse.RelatingPropertyDefinition,
)
elif inverse.is_a("IfcRelAssociatesMaterial"):
ifcopenshell.api.run("material.unassign_material", self.file, products=[self.settings["product"]])
elif inverse.is_a("IfcRelDefinesByType"):
if inverse.RelatingType == self.settings["product"]:
ifcopenshell.api.run("type.unassign_type", self.file, related_objects=inverse.RelatedObjects)
else:
ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[self.settings["product"]])
elif inverse.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.run("boundary.remove_boundary", self.file, boundary=inverse)
elif inverse.is_a("IfcRelFillsElement"):
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelVoidsElement"):
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelServicesBuildings"):
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelNests"):
if inverse.RelatingObject == self.settings["product"]:
inverse_id = inverse.id()
for subelement in inverse.RelatedObjects:
if subelement.is_a("IfcDistributionPort"):
ifcopenshell.api.run("root.remove_product", self.file, product=subelement)
# IfcRelNests could have been already deleted after removing one of the products
if element_exists(inverse_id):
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.RelatedObjects == (self.settings["product"],):
ifcopenshell.api.run(
"pset.remove_pset",
file,
product=settings["product"],
pset=pset,
)
for representation in representations:
ifcopenshell.api.run(
"geometry.unassign_representation",
file,
**{"product": settings["product"], "representation": representation}
)
ifcopenshell.api.run("geometry.remove_representation", file, **{"representation": representation})
for opening in getattr(settings["product"], "HasOpenings", []) or []:
ifcopenshell.api.run("void.remove_opening", file, opening=opening.RelatedOpeningElement)
if settings["product"].is_a("IfcGrid"):
for axis in settings["product"].UAxes + settings["product"].VAxes + (settings["product"].WAxes or ()):
ifcopenshell.api.run("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(settings["product"])]:
try:
inverse = file.by_id(inverse_id)
except:
continue
if inverse.is_a("IfcRelDefinesByProperties"):
ifcopenshell.api.run(
"pset.remove_pset",
file,
product=settings["product"],
pset=inverse.RelatingPropertyDefinition,
)
elif inverse.is_a("IfcRelAssociatesMaterial"):
ifcopenshell.api.run("material.unassign_material", file, products=[settings["product"]])
elif inverse.is_a("IfcRelDefinesByType"):
if inverse.RelatingType == settings["product"]:
ifcopenshell.api.run("type.unassign_type", file, related_objects=inverse.RelatedObjects)
else:
ifcopenshell.api.run("type.unassign_type", file, related_objects=[settings["product"]])
elif inverse.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.run("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 == settings["product"]:
inverse_id = inverse.id()
for subelement in inverse.RelatedObjects:
if subelement.is_a("IfcDistributionPort"):
ifcopenshell.api.run("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
self.file.remove(inverse)
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelAggregates"):
if inverse.RelatingObject == self.settings["product"] or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelContainedInSpatialStructure"):
if inverse.RelatingStructure == self.settings["product"] or len(inverse.RelatedElements) == 1:
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelConnectsElements"):
if inverse.is_a("IfcRelConnectsWithRealizingElements"):
if self.settings["product"] not in (inverse.RelatingElement, inverse.RelatedElement) and any(
el for el in inverse.RealizingElements if el != self.settings["product"]
):
continue
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.RelatedObjects == (settings["product"],):
history = inverse.OwnerHistory
self.file.remove(inverse)
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelConnectsPorts"):
if self.settings["product"] not in (inverse.RelatingPort, inverse.RelatedPort):
# if it's not RelatingPort/RelatedPort then it's optional RealizingElement
# so we keep the relationship
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAggregates"):
if inverse.RelatingObject == settings["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 == settings["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 settings["product"] not in (inverse.RelatingElement, inverse.RelatedElement) and any(
el for el in inverse.RealizingElements if el != settings["product"]
):
continue
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelConnectsPorts"):
if settings["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
self.file.remove(inverse)
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelAssignsToGroup"):
if len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelAssignsToProduct"):
if inverse.RelatingProduct == self.settings["product"]:
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.is_a("IfcRelFlowControlElements"):
if inverse.RelatingFlowElement == self.settings["product"]:
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
elif inverse.RelatedControlElements == (self.settings["product"],):
history = inverse.OwnerHistory
self.file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
history = self.settings["product"].OwnerHistory
self.file.remove(self.settings["product"])
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToProduct"):
if inverse.RelatingProduct == settings["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 == settings["product"]:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.RelatedControlElements == (settings["product"],):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = settings["product"].OwnerHistory
file.remove(settings["product"])
if history:
ifcopenshell.util.element.remove_deep2(file, history)