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
@@ -22,3 +22,6 @@ One common use is spatial elements, such as how a site has multiple buildings,
and a building has multiple storeys. Another is for regular elements, such as
how a wall is made out of members and coverings.
"""
from .assign_object import assign_object
from .unassign_object import unassign_object
@@ -23,148 +23,144 @@ import ifcopenshell.util.placement
from typing import Union
class Usecase:
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
relating_object: ifcopenshell.entity_instance,
):
"""Assigns object as an aggregate to the products
def assign_object(
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
relating_object: ifcopenshell.entity_instance,
) -> Union[ifcopenshell.entity_instance, None]:
"""Assigns object as an aggregate to the products
All physical IFC model elements must be part of a hierarchical tree
called the "spatial decomposition", where large things are made up of
smaller things. This tree always begins at an "IfcProject" and is then
broken down using "decomposition" relationships, of which aggregation is
the first relationship you will use.
All physical IFC model elements must be part of a hierarchical tree
called the "spatial decomposition", where large things are made up of
smaller things. This tree always begins at an "IfcProject" and is then
broken down using "decomposition" relationships, of which aggregation is
the first relationship you will use.
Typically used when you want to describe how large spaces are made up of
smaller spaces. For example large spatial elements (e.g. sites,
buidings) can be made out of smaller spatial elements (e.g. storeys,
spaces).
Typically used when you want to describe how large spaces are made up of
smaller spaces. For example large spatial elements (e.g. sites,
buidings) can be made out of smaller spatial elements (e.g. storeys,
spaces).
The largest space (typically the IfcSite) can then be aggregated in a
project. It is requirement for all spatial structures to be directly or
indirectly aggregated back to the IfcProject to create a hierarchy of
spaces.
The largest space (typically the IfcSite) can then be aggregated in a
project. It is requirement for all spatial structures to be directly or
indirectly aggregated back to the IfcProject to create a hierarchy of
spaces.
The other common usecase is when larger physical products are made up of
smaller physical products. For example, a stair might be made out of a
flight, a landing, a railing and so on. Or a wall might be made out of
stud members, and coverings.
The other common usecase is when larger physical products are made up of
smaller physical products. For example, a stair might be made out of a
flight, a landing, a railing and so on. Or a wall might be made out of
stud members, and coverings.
As a product may only have a single location in the "spatial
decomposition" tree, assigning an aggregate relationship will remove any
previous aggregation, containment, or nesting relationships it may have.
As a product may only have a single location in the "spatial
decomposition" tree, assigning an aggregate relationship will remove any
previous aggregation, containment, or nesting relationships it may have.
IFC placements follow a convention where the placement is relative to
its parent in the spatial hierarchy. If your product has a placement,
its placement will be recalculated to follow this convention.
IFC placements follow a convention where the placement is relative to
its parent in the spatial hierarchy. If your product has a placement,
its placement will be recalculated to follow this convention.
:param products: The list of parts of the aggregate, typically of IfcElement or
IfcSpatialStructureElement subclass
:type product: list[ifcopenshell.entity_instance]
:param relating_object: The whole of the aggregate, typically an
IfcElement or IfcSpatialStructureElement subclass
:type relating_object: ifcopenshell.entity_instance
:return: The IfcRelAggregate relationship instance
or `None` if `products` was empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
:param products: The list of parts of the aggregate, typically of IfcElement or
IfcSpatialStructureElement subclass
:type product: list[ifcopenshell.entity_instance]
:param relating_object: The whole of the aggregate, typically an
IfcElement or IfcSpatialStructureElement subclass
:type relating_object: ifcopenshell.entity_instance
:return: The IfcRelAggregate relationship instance
or `None` if `products` was empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
Example:
.. code:: python
.. code:: python
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
subelement = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
subelement = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
# The project contains a site (note that project aggregation is a special case in IFC)
ifcopenshell.api.run("aggregate.assign_object", model, products=[element], relating_object=project)
# The project contains a site (note that project aggregation is a special case in IFC)
ifcopenshell.api.run("aggregate.assign_object", model, products=[element], relating_object=project)
# The site has a building
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement], relating_object=element)
"""
self.file = file
self.settings = {
"products": products,
"relating_object": relating_object,
}
# The site has a building
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement], relating_object=element)
"""
settings = {
"products": products,
"relating_object": relating_object,
}
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
if not self.settings["products"]:
return
if not settings["products"]:
return
products = set(self.settings["products"])
relating_object = self.settings["relating_object"]
is_decomposed_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelAggregates")), None)
products = set(settings["products"])
relating_object = settings["relating_object"]
is_decomposed_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelAggregates")), None)
previous_aggregates_rels: set[ifcopenshell.entity_instance] = set()
products_without_aggregates: list[ifcopenshell.entity_instance] = []
products_with_aggregates: list[ifcopenshell.entity_instance] = []
previous_aggregates_rels: set[ifcopenshell.entity_instance] = set()
products_without_aggregates: list[ifcopenshell.entity_instance] = []
products_with_aggregates: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for product in products:
product_rel = next(iter(product.Decomposes), None)
# check if there is anything to change
for product in products:
product_rel = next(iter(product.Decomposes), None)
if product_rel is None:
products_without_aggregates.append(product)
continue
if product_rel is None:
products_without_aggregates.append(product)
continue
# either is_decomposed_by is None or product is part of different rel
if product_rel != is_decomposed_by:
previous_aggregates_rels.add(product_rel)
products_with_aggregates.append(product)
# either is_decomposed_by is None or product is part of different rel
if product_rel != is_decomposed_by:
previous_aggregates_rels.add(product_rel)
products_with_aggregates.append(product)
# products with already assigned aggregates will be skipped
# products with already assigned aggregates will be skipped
products_to_change = products_without_aggregates + products_with_aggregates
# nothing to change
if not products_to_change:
return is_decomposed_by
products_to_change = products_without_aggregates + products_with_aggregates
# nothing to change
if not products_to_change:
return is_decomposed_by
# can be either only aggregated or only contained at the same time
# some product might not be able to have a container
possibly_contained_products = [p for p in products_without_aggregates if hasattr(p, "ContainedInStructure")]
ifcopenshell.api.run("spatial.unassign_container", self.file, products=possibly_contained_products)
# can be either only aggregated or only contained at the same time
# some product might not be able to have a container
possibly_contained_products = [p for p in products_without_aggregates if hasattr(p, "ContainedInStructure")]
ifcopenshell.api.run("spatial.unassign_container", file, products=possibly_contained_products)
# unassign elements from previous aggregates
for decomposes in previous_aggregates_rels:
related_objects = set(decomposes.RelatedObjects) - products
if related_objects:
decomposes.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": decomposes})
else:
history = decomposes.OwnerHistory
self.file.remove(decomposes)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
# assign elements to a new aggregate
if is_decomposed_by:
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_decomposed_by})
# unassign elements from previous aggregates
for decomposes in previous_aggregates_rels:
related_objects = set(decomposes.RelatedObjects) - products
if related_objects:
decomposes.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": decomposes})
else:
is_decomposed_by = self.file.create_entity(
"IfcRelAggregates",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": list(products),
"RelatingObject": relating_object,
}
history = decomposes.OwnerHistory
file.remove(decomposes)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
# assign elements to a new aggregate
if is_decomposed_by:
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products)
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": is_decomposed_by})
else:
is_decomposed_by = file.create_entity(
"IfcRelAggregates",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
"RelatedObjects": list(products),
"RelatingObject": relating_object,
}
)
# localize placement relative to a new aggregate for affected products
for product in products_to_change:
placement = getattr(product, "ObjectPlacement", None)
if placement and placement.is_a("IfcLocalPlacement"):
ifcopenshell.api.run(
"geometry.edit_object_placement",
file,
product=product,
matrix=ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement),
is_si=False,
)
# localize placement relative to a new aggregate for affected products
for product in products_to_change:
placement = getattr(product, "ObjectPlacement", None)
if placement and placement.is_a("IfcLocalPlacement"):
ifcopenshell.api.run(
"geometry.edit_object_placement",
self.file,
product=product,
matrix=ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement),
is_si=False,
)
return is_decomposed_by
return is_decomposed_by
@@ -21,60 +21,57 @@ import ifcopenshell.api
import ifcopenshell.util.element
class Usecase:
def __init__(self, file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]):
"""Unassigns products from their aggregate
def unassign_object(file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]) -> None:
"""Unassigns products from their aggregate
A product (i.e. a smaller part of a whole) may be aggregated into zero
or one larger space or element. This function will remove that
aggregation relationship.
A product (i.e. a smaller part of a whole) may be aggregated into zero
or one larger space or element. This function will remove that
aggregation relationship.
As all physical IFC model elements must be part of a hierarchical tree
called the "spatial decomposition", using this function will remove the
product from that tree. This is a dangerous operation and may result in
the product no longer being visible in IFC applications.
As all physical IFC model elements must be part of a hierarchical tree
called the "spatial decomposition", using this function will remove the
product from that tree. This is a dangerous operation and may result in
the product no longer being visible in IFC applications.
If the product is not part of an aggregation relationship, nothing will
happen.
If the product is not part of an aggregation relationship, nothing will
happen.
:param products: The list of parts of the aggregate, typically of IfcElements or
IfcSpatialStructureElement subclass
:type product: list[ifcopenshell.entity_instance]
:return: None
:rtype: None
:param products: The list of parts of the aggregate, typically of IfcElements or
IfcSpatialStructureElement subclass
:type product: list[ifcopenshell.entity_instance]
:return: None
:rtype: None
Example:
Example:
.. code:: python
.. code:: python
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
subelement1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
subelement2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement1], relating_object=element)
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement2], relating_object=element)
# nothing is returned
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement1])
# nothing is returned, relationship is removed
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement2])
"""
self.file = file
self.settings = {"products": products}
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
subelement1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
subelement2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement1], relating_object=element)
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement2], relating_object=element)
# nothing is returned
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement1])
# nothing is returned, relationship is removed
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement2])
"""
settings = {"products": products}
def execute(self) -> None:
products = set(self.settings["products"])
rels = set(
rel
for product in products
if (rel := next((rel for rel in product.Decomposes if rel.is_a("IfcRelAggregates")), None))
)
products = set(settings["products"])
rels = set(
rel
for product in products
if (rel := next((rel for rel in product.Decomposes if rel.is_a("IfcRelAggregates")), None))
)
for rel in rels:
related_objects = set(rel.RelatedObjects) - products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
for rel in rels:
related_objects = set(rel.RelatedObjects) - products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
else:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)