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 .append_asset import append_asset
from .assign_declaration import assign_declaration
from .create_file import create_file
from .unassign_declaration import unassign_declaration
@@ -21,100 +21,103 @@ import ifcopenshell.api
import ifcopenshell.api.owner.settings
def append_asset(file, library=None, element=None, reuse_identities=None) -> None:
"""Appends an asset from a library into the active project
A BIM library asset may be a type product (e.g. wall type), product
(e.g. pump), material, profile, or cost schedule.
This copies the asset from the specified library file into the active
project. It handles all details like ensuring that product materials,
styles, properties, quantities, and so on are preserved.
If an asset contains geometry, the geometric contexts are also
intelligentely transplanted such that existing equivalent contexts are
reused.
Do not mix units.
:param library: The file object containing the asset.
:type library: ifcopenshell.file
:param element: An element in the library file of the asset. It may be
an IfcTypeProduct, IfcProduct, IfcMaterial, IfcCostSchedule, or
IfcProfileDef.
:type element: ifcopenshell.entity_instance
:param reuse_identities: Optional dictionary of mapped entities' identities to the
already created elements. It will be used to avoid creating
duplicated inverse elements during multiple `project.append_asset` calls. If you want
to add just 1 asset or if added assets won't have any shared elements, then it can be left empty.
:type reuse_identities: dict[int, ifcopenshell.entity_instance]
:return: The appended element
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# Programmatically generate a library. You could do this visually too.
library = ifcopenshell.api.run("project.create_file")
root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library")
context = ifcopenshell.api.run("root.create_entity", library,
ifc_class="IfcProjectLibrary", name="Demo Library")
ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root)
# Assign units for our example library
unit = ifcopenshell.api.run("unit.add_si_unit", library,
unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
ifcopenshell.api.run("unit.assign_unit", library, units=[unit])
# Let's create a single asset of a 200mm thick concrete wall
wall_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType", name="WAL01")
concrete = ifcopenshell.api.run("material.add_material", usecase.file, name="CON", category="concrete")
rel = ifcopenshell.api.run("material.assign_material", library,
products=[wall_type], type="IfcMaterialLayerSet")
layer = ifcopenshell.api.run("material.add_layer", library,
layer_set=rel.RelatingMaterial, material=concrete)
layer.Name = "Structure"
layer.LayerThickness = 200
# Mark our wall type as a reusable asset in our library.
ifcopenshell.api.run("project.assign_declaration", library,
definitions=[wall_type], relating_context=context)
# Let's imagine we're starting a new project
model = ifcopenshell.api.run("project.create_file")
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject", name="Test")
# Now we can easily append our wall type from our libary
wall_type = ifcopenshell.api.run("project.append_asset", model, library=library, element=wall_type)
Example of adding multiple assets and avoiding duplicated inverses:
.. code:: python
# since occurrences of IfcWindow of the same type
# might have shared inverses (e.g. IfcStyledItem)
# we provide a dictionary that will be populated with newly created items
# and reused to avoid duplicated elements
reuse_identities = dict()
for element in ifcopenshell.util.selector.filter_elements(model, "IfcWindow"):
ifcopenshell.api.run(
"project.append_asset",
model, library=library,
element=wall_type
reuse_identities=reuse_identities
)
"""
usecase = Usecase()
usecase.file: ifcopenshell.file = file
usecase.settings = {
"library": library,
"element": element,
"reuse_identities": {} if reuse_identities is None else reuse_identities,
}
return usecase.execute()
class Usecase:
def __init__(self, file, library=None, element=None, reuse_identities=None):
"""Appends an asset from a library into the active project
A BIM library asset may be a type product (e.g. wall type), product
(e.g. pump), material, profile, or cost schedule.
This copies the asset from the specified library file into the active
project. It handles all details like ensuring that product materials,
styles, properties, quantities, and so on are preserved.
If an asset contains geometry, the geometric contexts are also
intelligentely transplanted such that existing equivalent contexts are
reused.
Do not mix units.
:param library: The file object containing the asset.
:type library: ifcopenshell.file
:param element: An element in the library file of the asset. It may be
an IfcTypeProduct, IfcProduct, IfcMaterial, IfcCostSchedule, or
IfcProfileDef.
:type element: ifcopenshell.entity_instance
:param reuse_identities: Optional dictionary of mapped entities' identities to the
already created elements. It will be used to avoid creating
duplicated inverse elements during multiple `project.append_asset` calls. If you want
to add just 1 asset or if added assets won't have any shared elements, then it can be left empty.
:type reuse_identities: dict[int, ifcopenshell.entity_instance]
:return: The appended element
:rtype: ifcopenshell.entity_instance
Example:
.. code:: python
# Programmatically generate a library. You could do this visually too.
library = ifcopenshell.api.run("project.create_file")
root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library")
context = ifcopenshell.api.run("root.create_entity", library,
ifc_class="IfcProjectLibrary", name="Demo Library")
ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root)
# Assign units for our example library
unit = ifcopenshell.api.run("unit.add_si_unit", library,
unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
ifcopenshell.api.run("unit.assign_unit", library, units=[unit])
# Let's create a single asset of a 200mm thick concrete wall
wall_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType", name="WAL01")
concrete = ifcopenshell.api.run("material.add_material", self.file, name="CON", category="concrete")
rel = ifcopenshell.api.run("material.assign_material", library,
products=[wall_type], type="IfcMaterialLayerSet")
layer = ifcopenshell.api.run("material.add_layer", library,
layer_set=rel.RelatingMaterial, material=concrete)
layer.Name = "Structure"
layer.LayerThickness = 200
# Mark our wall type as a reusable asset in our library.
ifcopenshell.api.run("project.assign_declaration", library,
definitions=[wall_type], relating_context=context)
# Let's imagine we're starting a new project
model = ifcopenshell.api.run("project.create_file")
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject", name="Test")
# Now we can easily append our wall type from our libary
wall_type = ifcopenshell.api.run("project.append_asset", model, library=library, element=wall_type)
Example of adding multiple assets and avoiding duplicated inverses:
.. code:: python
# since occurrences of IfcWindow of the same type
# might have shared inverses (e.g. IfcStyledItem)
# we provide a dictionary that will be populated with newly created items
# and reused to avoid duplicated elements
reuse_identities = dict()
for element in ifcopenshell.util.selector.filter_elements(model, "IfcWindow"):
ifcopenshell.api.run(
"project.append_asset",
model, library=library,
element=wall_type
reuse_identities=reuse_identities
)
"""
self.file: ifcopenshell.file = file
self.settings = {
"library": library,
"element": element,
"reuse_identities": {} if reuse_identities is None else reuse_identities,
}
def execute(self):
# mapping of old element ids to new elements
self.added_elements: dict[int, ifcopenshell.entity_instance] = {}
@@ -22,129 +22,125 @@ import ifcopenshell.util.element
from typing import Union
class Usecase:
def __init__(
self,
file: ifcopenshell.entity_instance,
definitions: list[ifcopenshell.entity_instance],
relating_context: ifcopenshell.entity_instance,
):
"""Declares the list of elements to the project
def assign_declaration(
file: ifcopenshell.entity_instance,
definitions: list[ifcopenshell.entity_instance],
relating_context: ifcopenshell.entity_instance,
) -> Union[ifcopenshell.entity_instance, None]:
"""Declares the list of elements to the project
All data in a model must be directly or indirectly related to the
project. Most data is indirectly related, existing instead within the
spatial decomposition tree. Other data, such as types, may be declared
at the top level.
All data in a model must be directly or indirectly related to the
project. Most data is indirectly related, existing instead within the
spatial decomposition tree. Other data, such as types, may be declared
at the top level.
Most of the time, the API handles declaration automatically for you.
There is one scenario where you might want to explicitly declare objects
to the project, and that's when you want to organise objects into
project libraries for future use (such as an assets library). Assigning
a declaration lets you say that an object belongs to a library.
Most of the time, the API handles declaration automatically for you.
There is one scenario where you might want to explicitly declare objects
to the project, and that's when you want to organise objects into
project libraries for future use (such as an assets library). Assigning
a declaration lets you say that an object belongs to a library.
:param definitions: The list of objects you want to declare. Typically a list of assets.
:type definitions: list[ifcopenshell.entity_instance]
:param relating_context: The IfcProject, or more commonly the
IfcProjectLibrary that you want the object to be part of.
:type relating_context: ifcopenshell.entity_instance
:return: The new IfcRelDeclares relationship or None if all definitions
were already declared / do not support declaration.
:rtype: Union[ifcopenshell.entity_instance, None]
:param definitions: The list of objects you want to declare. Typically a list of assets.
:type definitions: list[ifcopenshell.entity_instance]
:param relating_context: The IfcProject, or more commonly the
IfcProjectLibrary that you want the object to be part of.
:type relating_context: ifcopenshell.entity_instance
:return: The new IfcRelDeclares relationship or None if all definitions
were already declared / do not support declaration.
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
Example:
.. code:: python
.. code:: python
# Programmatically generate a library. You could do this visually too.
library = ifcopenshell.api.run("project.create_file")
root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library")
context = ifcopenshell.api.run("root.create_entity", library,
ifc_class="IfcProjectLibrary", name="Demo Library")
# Programmatically generate a library. You could do this visually too.
library = ifcopenshell.api.run("project.create_file")
root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library")
context = ifcopenshell.api.run("root.create_entity", library,
ifc_class="IfcProjectLibrary", name="Demo Library")
# It's necessary to say our library is part of our project.
ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root)
# It's necessary to say our library is part of our project.
ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root)
# Assign units for our example library
unit = ifcopenshell.api.run("unit.add_si_unit", library,
unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
ifcopenshell.api.run("unit.assign_unit", library, units=[unit])
# Assign units for our example library
unit = ifcopenshell.api.run("unit.add_si_unit", library,
unit_type="LENGTHUNIT", name="METRE", prefix="MILLI")
ifcopenshell.api.run("unit.assign_unit", library, units=[unit])
# Let's create a single asset of a 200mm thick concrete wall
wall_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType", name="WAL01")
concrete = ifcopenshell.api.run("material.add_material", self.file, name="CON", category="concrete")
rel = ifcopenshell.api.run("material.assign_material", library,
products=[wall_type], type="IfcMaterialLayerSet")
layer = ifcopenshell.api.run("material.add_layer", library,
layer_set=rel.RelatingMaterial, material=concrete)
layer.Name = "Structure"
layer.LayerThickness = 200
# Let's create a single asset of a 200mm thick concrete wall
wall_type = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType", name="WAL01")
concrete = ifcopenshell.api.run("material.add_material", file, name="CON", category="concrete")
rel = ifcopenshell.api.run("material.assign_material", library,
products=[wall_type], type="IfcMaterialLayerSet")
layer = ifcopenshell.api.run("material.add_layer", library,
layer_set=rel.RelatingMaterial, material=concrete)
layer.Name = "Structure"
layer.LayerThickness = 200
# Mark our wall type as a reusable asset in our library.
ifcopenshell.api.run("project.assign_declaration", library,
definitions=[wall_type], relating_context=context)
# Mark our wall type as a reusable asset in our library.
ifcopenshell.api.run("project.assign_declaration", library,
definitions=[wall_type], relating_context=context)
# All done, just for fun let's save our asset library to disk for later use.
library.write("/path/to/my-library.ifc")
"""
self.file = file
self.settings = {
"definitions": definitions,
"relating_context": relating_context,
}
# All done, just for fun let's save our asset library to disk for later use.
library.write("/path/to/my-library.ifc")
"""
settings = {
"definitions": definitions,
"relating_context": relating_context,
}
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
relating_context = self.settings["relating_context"]
all_declares = relating_context.Declares
definitions = set(self.settings["definitions"])
relating_context = settings["relating_context"]
all_declares = relating_context.Declares
definitions = set(settings["definitions"])
previous_declares_rels: set[ifcopenshell.entity_instance] = set()
objects_without_contexts: list[ifcopenshell.entity_instance] = []
objects_with_contexts: list[ifcopenshell.entity_instance] = []
previous_declares_rels: set[ifcopenshell.entity_instance] = set()
objects_without_contexts: list[ifcopenshell.entity_instance] = []
objects_with_contexts: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for definition in definitions:
has_context = getattr(definition, "HasContext", None)
if has_context is None:
continue
# check if there is anything to change
for definition in definitions:
has_context = getattr(definition, "HasContext", None)
if has_context is None:
continue
object_rel = next(iter(has_context), None)
if object_rel is None:
objects_without_contexts.append(definition)
continue
object_rel = next(iter(has_context), None)
if object_rel is None:
objects_without_contexts.append(definition)
continue
# either rel doesn't exist or product is part of different rel
if object_rel not in all_declares:
previous_declares_rels.add(object_rel)
objects_with_contexts.append(definition)
# either rel doesn't exist or product is part of different rel
if object_rel not in all_declares:
previous_declares_rels.add(object_rel)
objects_with_contexts.append(definition)
objects_to_change = objects_without_contexts + objects_with_contexts
# nothing to change
if not objects_to_change:
return None
objects_to_change = objects_without_contexts + objects_with_contexts
# nothing to change
if not objects_to_change:
return None
for has_context in previous_declares_rels:
related_definitions = set(has_context.RelatedDefinitions) - objects_with_contexts
if related_definitions:
has_context.RelatedDefinitions = related_definitions
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": has_context})
else:
history = has_context.OwnerHistory
self.file.remove(has_context)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
declares = next(iter(all_declares), None)
if declares:
declares.RelatedDefinitions = list(set(declares.RelatedDefinitions) | set(objects_to_change))
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": declares})
for has_context in previous_declares_rels:
related_definitions = set(has_context.RelatedDefinitions) - objects_with_contexts
if related_definitions:
has_context.RelatedDefinitions = related_definitions
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": has_context})
else:
declares = self.file.create_entity(
"IfcRelDeclares",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedDefinitions": list(objects_to_change),
"RelatingContext": relating_context,
}
)
return declares
history = has_context.OwnerHistory
file.remove(has_context)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
declares = next(iter(all_declares), None)
if declares:
declares.RelatedDefinitions = list(set(declares.RelatedDefinitions) | set(objects_to_change))
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": declares})
else:
declares = file.create_entity(
"IfcRelDeclares",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
"RelatedDefinitions": list(objects_to_change),
"RelatingContext": relating_context,
}
)
return declares
@@ -20,52 +20,46 @@ import datetime
import ifcopenshell
class Usecase:
def __init__(self, version: str = "IFC4"):
"""Create a blank IFC model file object
def create_file(version: str = "IFC4") -> ifcopenshell.file:
"""Create a blank IFC model file object
Create a new IFC file object based on the nominated schema version. The
schema version you choose determines what type of IFC data you can store
in this model. The file is blank and contains no entities.
Create a new IFC file object based on the nominated schema version. The
schema version you choose determines what type of IFC data you can store
in this model. The file is blank and contains no entities.
It also sets up header data for STEP file serialisation, such as the
current timestamp, IfcOpenShell as the preprocessor, and defaults to a
DesignTransferView MVD.
It also sets up header data for STEP file serialisation, such as the
current timestamp, IfcOpenShell as the preprocessor, and defaults to a
DesignTransferView MVD.
:param version: The schema version of the IFC file. Choose from
"IFC2X3", "IFC4", or "IFC4X3". If you have loaded in a custom
schema, you may specify that schema identifier here too.
:type version: str, optional
:return: The created IFC file object.
:rtype: ifcopenshell.file
:param version: The schema version of the IFC file. Choose from
"IFC2X3", "IFC4", or "IFC4X3". If you have loaded in a custom
schema, you may specify that schema identifier here too.
:type version: str, optional
:return: The created IFC file object.
:rtype: ifcopenshell.file
Example:
Example:
.. code:: python
.. code:: python
# Start a new model.
model = ifcopenshell.api.run("project.create_file")
# Start a new model.
model = ifcopenshell.api.run("project.create_file")
# It's currently a blank model, so typically the first thing we do
# is create a project in it.
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject", name="Test")
# It's currently a blank model, so typically the first thing we do
# is create a project in it.
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject", name="Test")
# ... and off we go!
"""
self.settings = {"version": version}
# ... and off we go!
"""
settings = {"version": version}
def execute(self) -> ifcopenshell.file:
self.file = ifcopenshell.file(schema=self.settings["version"])
self.file.wrapped_data.header.file_name.name = "/dev/null" # Hehehe
self.file.wrapped_data.header.file_name.time_stamp = (
datetime.datetime.utcnow()
.replace(tzinfo=datetime.timezone.utc)
.astimezone()
.replace(microsecond=0)
.isoformat()
)
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.authorization = "Nobody"
self.file.wrapped_data.header.file_description.description = ("ViewDefinition[DesignTransferView]",)
return self.file
file = ifcopenshell.file(schema=settings["version"])
file.wrapped_data.header.file_name.name = "/dev/null" # Hehehe
file.wrapped_data.header.file_name.time_stamp = (
datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
)
file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
file.wrapped_data.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version)
file.wrapped_data.header.file_name.authorization = "Nobody"
file.wrapped_data.header.file_description.description = ("ViewDefinition[DesignTransferView]",)
return file
@@ -21,59 +21,55 @@ import ifcopenshell.api
import ifcopenshell.util.element
class Usecase:
def __init__(
self,
file: ifcopenshell.file,
definitions: list[ifcopenshell.entity_instance],
relating_context: ifcopenshell.entity_instance,
):
"""Unassigns a list of objects from a project or project library
def unassign_declaration(
file: ifcopenshell.file,
definitions: list[ifcopenshell.entity_instance],
relating_context: ifcopenshell.entity_instance,
) -> None:
"""Unassigns a list of objects from a project or project library
Typically used to remove an asset from a project library.
Typically used to remove an asset from a project library.
:param definitions: The list of objects you want to undeclare.
Typically a list of assets.
:type definitions: list[ifcopenshell.entity_instance]
:param relating_context: The IfcProject, or more commonly the
IfcProjectLibrary that you want the object to no longer be part of.
:type relating_context: ifcopenshell.entity_instance
:return: None
:rtype: None
:param definitions: The list of objects you want to undeclare.
Typically a list of assets.
:type definitions: list[ifcopenshell.entity_instance]
:param relating_context: The IfcProject, or more commonly the
IfcProjectLibrary that you want the object to no longer be part of.
:type relating_context: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
Example:
.. code:: python
.. code:: python
# Programmatically generate a library. You could do this visually too.
library = ifcopenshell.api.run("project.create_file")
root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library")
context = ifcopenshell.api.run("root.create_entity", library,
ifc_class="IfcProjectLibrary", name="Demo Library")
# Programmatically generate a library. You could do this visually too.
library = ifcopenshell.api.run("project.create_file")
root = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject", name="Demo Library")
context = ifcopenshell.api.run("root.create_entity", library,
ifc_class="IfcProjectLibrary", name="Demo Library")
# It's necessary to say our library is part of our project.
ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root)
# It's necessary to say our library is part of our project.
ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root)
# Remove the library from our project
ifcopenshell.api.run("project.unassign_declaration", library, definitions=[context], relating_context=root)
"""
self.file = file
self.settings = {
"definitions": definitions,
"relating_context": relating_context,
}
# Remove the library from our project
ifcopenshell.api.run("project.unassign_declaration", library, definitions=[context], relating_context=root)
"""
settings = {
"definitions": definitions,
"relating_context": relating_context,
}
def execute(self):
definitions = set(self.settings["definitions"])
rels = {rel for obj in definitions if (rel := next(iter(obj.HasContext), None))}
definitions = set(settings["definitions"])
rels = {rel for obj in definitions if (rel := next(iter(obj.HasContext), None))}
for rel in rels:
related_definitions = set(rel.RelatedDefinitions) - definitions
if related_definitions:
rel.RelatedDefinitions = list(related_definitions)
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_definitions = set(rel.RelatedDefinitions) - definitions
if related_definitions:
rel.RelatedDefinitions = list(related_definitions)
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)