This commit is contained in:
Andrej730
2025-02-18 15:18:23 +05:00
parent 83a351b1c7
commit 17642ca4e9
117 changed files with 683 additions and 1005 deletions
@@ -25,6 +25,7 @@ import mathutils
import numpy as np import numpy as np
import multiprocessing import multiprocessing
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.boundary
import ifcopenshell.geom import ifcopenshell.geom
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.util.shape import ifcopenshell.util.shape
@@ -416,7 +417,7 @@ class UpdateBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
tool.Boundary.move_origin_to_space_origin(context.active_object) tool.Boundary.move_origin_to_space_origin(context.active_object)
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object) settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings) ifcopenshell.api.boundary.assign_connection_geometry(tool.Ifc.get(), **settings)
return {"FINISHED"} return {"FINISHED"}
@@ -29,6 +29,7 @@ import ifcopenshell.util.representation
import ifcopenshell.util.shape_builder import ifcopenshell.util.shape_builder
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.boundary
import ifcopenshell.api.grid import ifcopenshell.api.grid
import bonsai.core.geometry import bonsai.core.geometry
import bonsai.core.geometry as core import bonsai.core.geometry as core
@@ -479,7 +480,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
elif product.is_a("IfcRelSpaceBoundary"): elif product.is_a("IfcRelSpaceBoundary"):
# TODO refactor # TODO refactor
settings = tool.Boundary.get_assign_connection_geometry_settings(obj) settings = tool.Boundary.get_assign_connection_geometry_settings(obj)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings) ifcopenshell.api.boundary.assign_connection_geometry(tool.Ifc.get(), **settings)
return return
if tool.Ifc.is_moved(obj) or tool.Geometry.is_scaled(obj): if tool.Ifc.is_moved(obj) or tool.Geometry.is_scaled(obj):
@@ -18,6 +18,7 @@
import bpy import bpy
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.profile
import ifcopenshell.util.element import ifcopenshell.util.element
import bonsai.bim.helper import bonsai.bim.helper
import bonsai.tool as tool import bonsai.tool as tool
@@ -174,12 +175,12 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
props.object_to_profile = None props.object_to_profile = None
if not indices: if not indices:
points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)] points = [(0, 0), (0.1, 0), (0.1, 0.1), (0, 0.1), (0, 0)]
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points) profile = ifcopenshell.api.profile.add_arbitrary_profile(tool.Ifc.get(), profile=points)
else: else:
if "inner_curves" not in indices: if "inner_curves" not in indices:
points = [(obj.data.vertices[i].co.x, obj.data.vertices[i].co.y) for i in indices["profile"]] points = [(obj.data.vertices[i].co.x, obj.data.vertices[i].co.y) for i in indices["profile"]]
points.append(points[0]) points.append(points[0])
profile = ifcopenshell.api.run("profile.add_arbitrary_profile", tool.Ifc.get(), profile=points) profile = ifcopenshell.api.profile.add_arbitrary_profile(tool.Ifc.get(), profile=points)
else: else:
outer_points = [(obj.data.vertices[i].co.x, obj.data.vertices[i].co.y) for i in indices["profile"]] outer_points = [(obj.data.vertices[i].co.x, obj.data.vertices[i].co.y) for i in indices["profile"]]
outer_points.append(outer_points[0]) outer_points.append(outer_points[0])
@@ -189,8 +190,7 @@ class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator):
] ]
for curve in inner_points: for curve in inner_points:
curve.append(curve[0]) curve.append(curve[0])
profile = ifcopenshell.api.run( profile = ifcopenshell.api.profile.add_arbitrary_profile_with_voids(
"profile.add_arbitrary_profile_with_voids",
tool.Ifc.get(), tool.Ifc.get(),
outer_profile=outer_points, outer_profile=outer_points,
inner_profiles=inner_points, inner_profiles=inner_points,
@@ -39,11 +39,8 @@ def edit_attributes(file: ifcopenshell.file, product: ifcopenshell.entity_instan
:param product: The product you want to edit. This may be any rooted IFC :param product: The product you want to edit. This may be any rooted IFC
entity. entity.
:type product: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -17,17 +17,20 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit import ifcopenshell.util.unit
import numpy as np
import numpy.typing as npt
from typing import Optional from typing import Optional
from ifcopenshell.util.shape_builder import SequenceOfVectors, V, ifc_safe_vector_type
def assign_connection_geometry( def assign_connection_geometry(
file: ifcopenshell.file, file: ifcopenshell.file,
rel_space_boundary: ifcopenshell.entity_instance, rel_space_boundary: ifcopenshell.entity_instance,
outer_boundary: list[tuple[float, float]], outer_boundary: SequenceOfVectors,
location: tuple[float, float, float], location: tuple[float, float, float],
axis: tuple[float, float, float], axis: tuple[float, float, float],
ref_direction: tuple[float, float, float], ref_direction: tuple[float, float, float],
inner_boundaries: Optional[list[list[tuple[float, float]]]] = None, inner_boundaries: Optional[SequenceOfVectors] = None,
unit_scale: Optional[float] = None, unit_scale: Optional[float] = None,
) -> None: ) -> None:
"""Create and assign a connection geometry to a space boundary relationship """Create and assign a connection geometry to a space boundary relationship
@@ -40,35 +43,28 @@ def assign_connection_geometry(
:param rel_space_boundary: The space boundary relationship to assign the :param rel_space_boundary: The space boundary relationship to assign the
connection geometry to. connection geometry to.
:type rel_space_boundary: ifcopenshell.entity_instance
:param outer_boundary: A list of 2D points representing an open :param outer_boundary: A list of 2D points representing an open
polyline. The last point will connect to the first point. Each polyline. The last point will connect to the first point. Each
point is represented by an interable of 2 floats. The coordinates of point is represented by an interable of 2 floats. The coordinates of
the points are relative to the positional matrix arguments. the points are relative to the positional matrix arguments.
:type outer_boundary: list[tuple[float, float]]
:param inner_boundaries: A list of zero or more inner boundaries to use :param inner_boundaries: A list of zero or more inner boundaries to use
for the plane. Each boundary is represented by an open polyline, as for the plane. Each boundary is represented by an open polyline, as
defined by the outer_boundary argument. defined by the outer_boundary argument.
:type inner_boundaries: list[list[tuple[float, float]]], optional
:param location: The local origin of the connection geometry, defined as :param location: The local origin of the connection geometry, defined as
an XYZ coordinate relative to the placement of the space that is an XYZ coordinate relative to the placement of the space that is
being bounded. being bounded.
:type location: tuple[float, float, float]
:param axis: The local X axis of the connection geometry, defined as an :param axis: The local X axis of the connection geometry, defined as an
XYZ vector relative to the placement of the space that is being XYZ vector relative to the placement of the space that is being
bounded. bounded.
:type axis: tuple[float, float, float]
:param ref_direction: The local Z axis of the connection geometry, :param ref_direction: The local Z axis of the connection geometry,
defined as an XYZ vector relative to the placement of the space that defined as an XYZ vector relative to the placement of the space that
is being bounded. The Y vector is automatically derived using the is being bounded. The Y vector is automatically derived using the
right hand rule. right hand rule.
:type ref_direction: tuple[float, float, float]
:param unit_scale: The unit scale as calculated by :param unit_scale: The unit scale as calculated by
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
will be automatically calculated for you. will be automatically calculated for you.
:type unit_scale: float, optional :type unit_scale: float, optional
:return: None :return: None
:rtype: None
Example: Example:
@@ -83,19 +79,26 @@ def assign_connection_geometry(
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.rel_space_boundary = rel_space_boundary usecase.rel_space_boundary = rel_space_boundary
usecase.outer_boundary = outer_boundary usecase.outer_boundary = V(outer_boundary)
usecase.inner_boundaries = inner_boundaries or () usecase.inner_boundaries = V(inner_boundaries or [])
usecase.location = location usecase.location = V(location)
usecase.axis = axis usecase.axis = V(axis)
usecase.ref_direction = ref_direction usecase.ref_direction = V(ref_direction)
usecase.unit_scale = unit_scale usecase.unit_scale = unit_scale if unit_scale is not None else ifcopenshell.util.unit.calculate_unit_scale(file)
return usecase.execute() return usecase.execute()
class Usecase: class Usecase:
file: ifcopenshell.file
rel_space_boundary: ifcopenshell.entity_instance
outer_boundary: npt.NDArray
inner_boundaries: npt.NDArray
location: npt.NDArray
axis: npt.NDArray
ref_direction: npt.NDArray
unit_scale: float
def execute(self): def execute(self):
if self.unit_scale is None:
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
outer_boundary = self.create_polyline(self.outer_boundary) outer_boundary = self.create_polyline(self.outer_boundary)
inner_boundaries = tuple(self.create_polyline(boundary) for boundary in self.inner_boundaries) inner_boundaries = tuple(self.create_polyline(boundary) for boundary in self.inner_boundaries)
plane = self.create_plane(self.location, self.axis, self.ref_direction) plane = self.create_plane(self.location, self.axis, self.ref_direction)
@@ -103,18 +106,23 @@ class Usecase:
connection_geometry = self.file.createIfcConnectionSurfaceGeometry(curve_bounded_plane) connection_geometry = self.file.createIfcConnectionSurfaceGeometry(curve_bounded_plane)
self.rel_space_boundary.ConnectionGeometry = connection_geometry self.rel_space_boundary.ConnectionGeometry = connection_geometry
def create_point(self, point): def create_point(self, point: npt.NDArray) -> ifcopenshell.entity_instance:
return self.file.createIfcCartesianPoint(point / self.unit_scale) return self.file.create_enitty("IfcCartesianPoint", ifc_safe_vector_type(point / self.unit_scale))
def close_polyline(self, points): def close_polyline(
self, points: tuple[ifcopenshell.entity_instance, ...]
) -> tuple[ifcopenshell.entity_instance, ...]:
return points + (points[0],) return points + (points[0],)
def create_polyline(self, points): def create_polyline(self, points: npt.NDArray) -> ifcopenshell.entity_instance:
if points[0] == points[-1]: if np.allclose(points[0], points[-1]):
points = points[0 : len(points) - 1] points = points[0 : len(points) - 1]
return self.file.createIfcPolyline(self.close_polyline(tuple(self.create_point(point) for point in points))) ifc_points = tuple(self.create_point(point) for point in points)
return self.file.createIfcPolyline(self.close_polyline(ifc_points))
def create_plane(self, location, axis, ref_direction): def create_plane(
self, location: npt.NDArray, axis: npt.NDArray, ref_direction: npt.NDArray
) -> ifcopenshell.entity_instance:
return self.file.createIfcPlane( return self.file.createIfcPlane(
self.file.createIfcAxis2Placement3D( self.file.createIfcAxis2Placement3D(
self.create_point(location), self.create_point(location),
@@ -20,7 +20,7 @@ import ifcopenshell
import ifcopenshell.guid import ifcopenshell.guid
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.date import ifcopenshell.util.date
from typing import Union from typing import Union, Any
def add_classification( def add_classification(
@@ -61,9 +61,7 @@ def add_classification(
classification library. The latter approach is preferred if you are classification library. The latter approach is preferred if you are
using a commonly known system such as Uniclass, as this will ensure using a commonly known system such as Uniclass, as this will ensure
all metadata is added correctly. all metadata is added correctly.
:type classification: str,ifcopenshell.entity_instance
:return: The added IfcClassification element :return: The added IfcClassification element
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -81,28 +79,28 @@ def add_classification(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = { return usecase.execute(classification)
"classification": classification,
}
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
if isinstance(self.settings["classification"], str):
classification = self.file.createIfcClassification(Name=self.settings["classification"]) def execute(self, classification: Union[str, ifcopenshell.entity_instance]) -> ifcopenshell.entity_instance:
self.classification = classification
if isinstance(self.classification, str):
classification = self.file.create_entity("IfcClassification", Name=self.classification)
self.relate_to_project(classification) self.relate_to_project(classification)
return classification return classification
return self.add_from_library() return self.add_from_library()
def add_from_library(self): def add_from_library(self) -> ifcopenshell.entity_instance:
edition_date = None edition_date = None
if self.settings["classification"].EditionDate: if self.classification.EditionDate:
edition_date = ifcopenshell.util.date.ifc2datetime(self.settings["classification"].EditionDate) edition_date = ifcopenshell.util.date.ifc2datetime(self.classification.EditionDate)
self.settings["classification"].EditionDate = None self.classification.EditionDate = None
migrator = ifcopenshell.util.schema.Migrator() migrator = ifcopenshell.util.schema.Migrator()
result = migrator.migrate(self.settings["classification"], self.file) result = migrator.migrate(self.classification, self.file)
# TODO: should auto date migration be part of the migrator? # TODO: should auto date migration be part of the migrator?
if self.file.schema == "IFC2X3" and edition_date: if self.file.schema == "IFC2X3" and edition_date:
@@ -118,7 +116,7 @@ class Usecase:
return result return result
def relate_to_project(self, classification): def relate_to_project(self, classification: ifcopenshell.entity_instance) -> None:
self.file.create_entity( self.file.create_entity(
"IfcRelAssociatesClassification", "IfcRelAssociatesClassification",
GlobalId=ifcopenshell.guid.new(), GlobalId=ifcopenshell.guid.new(),
@@ -21,7 +21,7 @@ import ifcopenshell.api.owner
import ifcopenshell.guid import ifcopenshell.guid
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.schema import ifcopenshell.util.schema
from typing import Optional, Union from typing import Optional, Union, Any
def add_reference( def add_reference(
@@ -65,23 +65,18 @@ def add_reference(
:param product: The list of IFC objects, properties, or resources you want to :param product: The list of IFC objects, properties, or resources you want to
associate the classification reference to. associate the classification reference to.
:type product: list[ifcopenshell.entity_instance]
:param reference: The classification reference entity taken from an :param reference: The classification reference entity taken from an
IFC classification library. If you supply this parameter, you will IFC classification library. If you supply this parameter, you will
use option 2. use option 2.
:type reference: ifcopenshell.entity_instance, optional
:param identification: If you choose option 1 and do not specify a :param identification: If you choose option 1 and do not specify a
reference, you may manually specify an identification code. The code reference, you may manually specify an identification code. The code
is typically a short identifier and may have punctuation to separate is typically a short identifier and may have punctuation to separate
the levels of hierarchy in the classificaion (e.g. Pr_12_23_34). the levels of hierarchy in the classificaion (e.g. Pr_12_23_34).
:type identification: str, optional
:param name: If you choose option 1 and do not specify a reference, you :param name: If you choose option 1 and do not specify a reference, you
may manually specify a name. The name is typically human readable. may manually specify a name. The name is typically human readable.
:type name: str, optional
:param classification: The IfcClassification entity in your IFC model :param classification: The IfcClassification entity in your IFC model
(not the library, if you are doing option 2) that the reference is (not the library, if you are doing option 2) that the reference is
part of. part of.
:type classification: ifcopenshell.entity_instance
:param is_lightweight: If you are doing option 2, choose whether or not :param is_lightweight: If you are doing option 2, choose whether or not
to only add that particular reference (lighweight) or also add all to only add that particular reference (lighweight) or also add all
of its parent references in the classification hierarchy (not of its parent references in the classification hierarchy (not
@@ -91,13 +86,11 @@ def add_reference(
references merely help describe the "tree" of classifications, but references merely help describe the "tree" of classifications, but
is generally unnecessary. Using lightweight classifications are is generally unnecessary. Using lightweight classifications are
recommended and is the default. recommended and is the default.
:type is_lightweight: bool, optional
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements. :raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
:return: The newly added IfcClassificationReference :return: The newly added IfcClassificationReference
or `None` if `products` was empty list. or `None` if `products` was empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example: Example:
@@ -136,6 +129,9 @@ def add_reference(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
if not self.settings["products"]: if not self.settings["products"]:
return return
@@ -28,11 +28,8 @@ def edit_classification(
IfcClassification, consult the IFC documentation. IfcClassification, consult the IFC documentation.
:param classification: The IfcClassification entity you want to edit :param classification: The IfcClassification entity you want to edit
:type classification: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -43,7 +40,5 @@ def edit_classification(
ifcopenshell.api.classification.edit_classification(model, ifcopenshell.api.classification.edit_classification(model,
classification=classification, attributes={"Name": "Foo"}) classification=classification, attributes={"Name": "Foo"})
""" """
settings = {"classification": classification, "attributes": attributes or {}} for name, value in attributes.items():
setattr(classification, name, value)
for name, value in settings["attributes"].items():
setattr(settings["classification"], name, value)
@@ -28,11 +28,8 @@ def edit_reference(
IfcClassificationReference, consult the IFC documentation. IfcClassificationReference, consult the IFC documentation.
:param reference: The IfcClassificationReference entity you want to edit :param reference: The IfcClassificationReference entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -43,7 +40,5 @@ def edit_reference(
ifcopenshell.api.classification.edit_reference(model, ifcopenshell.api.classification.edit_reference(model,
reference=reference, attributes={"Name": "Foo"}) reference=reference, attributes={"Name": "Foo"})
""" """
settings = {"reference": reference, "attributes": attributes or {}} for name, value in attributes.items():
setattr(reference, name, value)
for name, value in settings["attributes"].items():
setattr(settings["reference"], name, value)
@@ -28,9 +28,7 @@ def remove_classification(file: ifcopenshell.file, classification: ifcopenshell.
removed from a project. removed from a project.
:param classification: The IfcClassification entity you want to remove :param classification: The IfcClassification entity you want to remove
:type classification: ifcopenshell.entity_instance
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,16 +40,17 @@ def remove_classification(file: ifcopenshell.file, classification: ifcopenshell.
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"classification": classification} return usecase.execute(classification)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
references = self.get_references(self.settings["classification"])
def execute(self, classification: ifcopenshell.entity_instance) -> None:
references = self.get_references(classification)
for reference in references: for reference in references:
self.file.remove(reference) self.file.remove(reference)
self.file.remove(self.settings["classification"]) self.file.remove(classification)
for rel in self.file.by_type("IfcRelAssociatesClassification"): for rel in self.file.by_type("IfcRelAssociatesClassification"):
if not rel.RelatingClassification: if not rel.RelatingClassification:
history = rel.OwnerHistory history = rel.OwnerHistory
@@ -33,15 +33,12 @@ def remove_reference(
:param reference: The IfcClassificationReference entity of the :param reference: The IfcClassificationReference entity of the
relationship you want to remove. relationship you want to remove.
:type reference: ifcopenshell.entity_instance
:param product: The list fo object entities of the relationship you want to :param product: The list fo object entities of the relationship you want to
remove. remove.
:type product: list[ifcopenshell.entity_instance]
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements. :raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
:return: None :return: None
:rtype: None
Example: Example:
@@ -56,20 +53,18 @@ def remove_reference(
ifcopenshell.api.classification.remove_reference(model, ifcopenshell.api.classification.remove_reference(model,
reference=reference, products=[wall_type]) reference=reference, products=[wall_type])
""" """
settings = {"reference": reference, "products": products}
is_ifc2x3 = file.schema == "IFC2X3" is_ifc2x3 = file.schema == "IFC2X3"
products = set(settings["products"]) products_set = set(products)
referenced = ifcopenshell.util.element.get_referenced_elements(settings["reference"]) referenced = ifcopenshell.util.element.get_referenced_elements(reference)
products -= products.difference(referenced) products_set -= products_set.difference(referenced)
# all products are already unassigned from a reference # all products are already unassigned from a reference
if not products: if not products_set:
return return
rooted_products: set[ifcopenshell.entity_instance] = set() rooted_products: set[ifcopenshell.entity_instance] = set()
non_rooted_products: set[ifcopenshell.entity_instance] = set() non_rooted_products: set[ifcopenshell.entity_instance] = set()
for product in settings["products"]: for product in products:
if product.is_a("IfcRoot"): if product.is_a("IfcRoot"):
rooted_products.add(product) rooted_products.add(product)
else: else:
@@ -86,7 +81,7 @@ def remove_reference(
reference_rels = { reference_rels = {
rel rel
for rel in reference_rels for rel in reference_rels
if rel.is_a("IfcRelAssociatesClassification") and rel.RelatingClassification == settings["reference"] if rel.is_a("IfcRelAssociatesClassification") and rel.RelatingClassification == reference
} }
for rel in reference_rels: for rel in reference_rels:
@@ -108,7 +103,7 @@ def remove_reference(
rels = getattr(product, "HasExternalReference", []) rels = getattr(product, "HasExternalReference", [])
reference_rels.update(rels) reference_rels.update(rels)
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == settings["reference"]} reference_rels = {rel for rel in reference_rels if rel.RelatingReference == reference}
for rel in reference_rels: for rel in reference_rels:
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
if related_objects: if related_objects:
@@ -117,6 +112,6 @@ def remove_reference(
file.remove(rel) file.remove(rel)
# TODO: we only handle lightweight classifications here # TODO: we only handle lightweight classifications here
referenced_elements = ifcopenshell.util.element.get_referenced_elements(settings["reference"]) referenced_elements = ifcopenshell.util.element.get_referenced_elements(reference)
if not referenced_elements: if not referenced_elements:
file.remove(settings["reference"]) file.remove(reference)
@@ -26,16 +26,14 @@ def add_metric_reference(
Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute" Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute"
Used to reference a value of an attribute of an instance through a metric objective entity. Used to reference a value of an attribute of an instance through a metric objective entity.
""" """
settings = {"metric": metric, "reference_path": reference_path}
references_created = [] references_created = []
if settings["reference_path"]: if reference_path:
attributes = settings["reference_path"].split(".") attributes = reference_path.split(".")
for i in range(len(attributes)): for i in range(len(attributes)):
if i == 0: if i == 0:
reference = file.create_entity("IfcReference") reference = file.create_entity("IfcReference")
reference.AttributeIdentifier = attributes[i] reference.AttributeIdentifier = attributes[i]
settings["metric"].ReferencePath = reference metric.ReferencePath = reference
references_created.append(reference) references_created.append(reference)
else: else:
reference = file.create_entity("IfcReference") reference = file.create_entity("IfcReference")
@@ -39,36 +39,29 @@ def assign_constraint(
:param products: The list of products the constraint applies to. This is anything :param products: The list of products the constraint applies to. This is anything
which can have properties or quantities. which can have properties or quantities.
:type products: list[ifcopenshell.entity_instance]
:param constraint: The IfcObjective constraint :param constraint: The IfcObjective constraint
:type constraint: ifcopenshell.entity_instance
:return: The new or updated IfcRelAssociatesConstraint relationship :return: The new or updated IfcRelAssociatesConstraint relationship
or `None` if `products` was an empty list. or `None` if `products` was an empty list.
:rtype: ifcopenshell.entity_instance
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = { return usecase.execute(products, constraint)
"products": products,
"constraint": constraint,
}
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
products = set(self.settings["products"])
def execute(self, products: list[ifcopenshell.entity_instance], constraint: ifcopenshell.entity_instance):
if not products: if not products:
return return
products_set = set(products)
self.constraint = self.settings["constraint"] rels = self.get_constraint_rels(constraint)
rels = self.get_constraint_rels()
related_objects = set() related_objects = set()
for rel in rels: for rel in rels:
related_objects.update(rel.RelatedObjects) related_objects.update(rel.RelatedObjects)
products_to_assign = products - related_objects products_to_assign = products_set - related_objects
if not products_to_assign: if not products_to_assign:
return rels[0] return rels[0]
@@ -85,14 +78,14 @@ class Usecase:
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(self.file), "OwnerHistory": ifcopenshell.api.owner.create_owner_history(self.file),
"RelatingConstraint": self.constraint, "RelatingConstraint": constraint,
"RelatedObjects": list(products_to_assign), "RelatedObjects": list(products_to_assign),
} }
) )
def get_constraint_rels(self) -> list[ifcopenshell.entity_instance]: def get_constraint_rels(self, constraint: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
rels = [] rels = []
for rel in self.file.get_inverse(self.constraint): for rel in self.file.get_inverse(constraint):
if rel.is_a("IfcRelAssociatesConstraint"): if rel.is_a("IfcRelAssociatesConstraint"):
rels.append(rel) rels.append(rel)
return rels return rels
@@ -26,11 +26,8 @@ def edit_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance, a
IfcMetric, consult the IFC documentation. IfcMetric, consult the IFC documentation.
:param metric: The IfcMetric you want to edit. :param metric: The IfcMetric you want to edit.
:type metric: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,7 +39,5 @@ def edit_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance, a
ifcopenshell.api.constraint.edit_metric(model, ifcopenshell.api.constraint.edit_metric(model,
metric=metric, attributes={"ConstraintGrade": "HARD"}) metric=metric, attributes={"ConstraintGrade": "HARD"})
""" """
settings = {"metric": metric, "attributes": attributes or {}} for name, value in attributes.items():
setattr(metric, name, value)
for name, value in settings["attributes"].items():
setattr(settings["metric"], name, value)
@@ -28,11 +28,8 @@ def edit_objective(
IfcObjective, consult the IFC documentation. IfcObjective, consult the IFC documentation.
:param objective: The IfcObjective you want to edit. :param objective: The IfcObjective you want to edit.
:type objective: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,7 +39,5 @@ def edit_objective(
ifcopenshell.api.constraint.edit_objective(model, ifcopenshell.api.constraint.edit_objective(model,
objective=objective, attributes={"ConstraintGrade": "HARD"}) objective=objective, attributes={"ConstraintGrade": "HARD"})
""" """
settings = {"objective": objective, "attributes": attributes or {}} for name, value in attributes.items():
setattr(objective, name, value)
for name, value in settings["attributes"].items():
setattr(settings["objective"], name, value)
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element
def remove_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance) -> None: def remove_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance) -> None:
@@ -41,17 +42,18 @@ def remove_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance)
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"metric": metric} return usecase.execute(metric)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
if self.settings["metric"].ReferencePath:
reference = self.settings["metric"].ReferencePath def execute(self, metric: ifcopenshell.entity_instance) -> None:
if metric.ReferencePath:
reference = metric.ReferencePath
self.delete_reference(reference) self.delete_reference(reference)
self.file.remove(self.settings["metric"]) self.file.remove(metric)
for rel in self.file.by_type("IfcRelAssociatesConstraint"): for rel in self.file.by_type("IfcRelAssociatesConstraint"):
if not rel.RelatingConstraint: if not rel.RelatingConstraint:
history = rel.OwnerHistory history = rel.OwnerHistory
@@ -62,7 +64,7 @@ class Usecase:
if not resource_rel.RelatingConstraint: if not resource_rel.RelatingConstraint:
self.file.remove(resource_rel) self.file.remove(resource_rel)
def delete_reference(self, reference): def delete_reference(self, reference: ifcopenshell.entity_instance) -> None:
if reference.InnerReference: if reference.InnerReference:
self.delete_reference(reference.InnerReference) self.delete_reference(reference.InnerReference)
self.file.remove(reference) self.file.remove(reference)
@@ -32,41 +32,35 @@ def unassign_constraint(
other products. other products.
:param products: The list of products the constraint applies to. :param products: The list of products the constraint applies to.
:type products: list[ifcopenshell.entity_instance]
:param constraint: The IfcObjective constraint :param constraint: The IfcObjective constraint
:type constraint: ifcopenshell.entity_instance
:return: None :return: None
:rtype: None
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = { return usecase.execute(products, constraint)
"products": products,
"constraint": constraint,
}
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
products = set(self.settings["products"])
if not products:
return
self.constraint = self.settings["constraint"] def execute(self, products_: list[ifcopenshell.entity_instance], constraint: ifcopenshell.entity_instance):
rels = self.get_constraint_rels() if not products_:
return
products_set = set(products_)
rels = self.get_constraint_rels(constraint)
related_objects = set() related_objects = set()
for rel in rels: for rel in rels:
related_objects.update(rel.RelatedObjects) related_objects.update(rel.RelatedObjects)
if not related_objects.intersection(products): if not related_objects.intersection(products_set):
return return
for rel in rels: for rel in rels:
related_objects = set(rel.RelatedObjects) related_objects = set(rel.RelatedObjects)
if not related_objects.intersection(products): if not related_objects.intersection(products_set):
continue continue
related_objects -= products related_objects -= products_set
if related_objects: if related_objects:
rel.RelatedObjects = list(related_objects) rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(self.file, **{"element": rel}) ifcopenshell.api.owner.update_owner_history(self.file, **{"element": rel})
@@ -77,9 +71,9 @@ class Usecase:
if history: if history:
ifcopenshell.util.element.remove_deep2(self.file, history) ifcopenshell.util.element.remove_deep2(self.file, history)
def get_constraint_rels(self) -> list[ifcopenshell.entity_instance]: def get_constraint_rels(self, cosntraint: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
rels = [] rels = []
for rel in self.file.get_inverse(self.constraint): for rel in self.file.get_inverse(cosntraint):
if rel.is_a("IfcRelAssociatesConstraint"): if rel.is_a("IfcRelAssociatesConstraint"):
rels.append(rel) rels.append(rel)
return rels return rels
@@ -27,11 +27,8 @@ def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance,
IfcGeometricRepresentationContext, consult the IFC documentation. IfcGeometricRepresentationContext, consult the IFC documentation.
:param context: The IfcGeometricRepresentationContext entity you want to edit :param context: The IfcGeometricRepresentationContext entity you want to edit
:type context: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -47,7 +44,5 @@ def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance,
ifcopenshell.api.context.edit_context(model, ifcopenshell.api.context.edit_context(model,
context=body, attributes={"ContextIdentifier": "Body"}) context=body, attributes={"ContextIdentifier": "Body"})
""" """
settings = {"context": context, "attributes": attributes} for name, value in attributes.items():
setattr(context, name, value)
for name, value in settings["attributes"].items():
setattr(settings["context"], name, value)
@@ -25,8 +25,6 @@ from typing import Union
def copy_cost_item( def copy_cost_item(
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]: ) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
# TODO: currently it never returns list of duplicated cost items
# though it is stated in the docs
"""Copies all cost items and related relationships """Copies all cost items and related relationships
The following relationships are also duplicated: The following relationships are also duplicated:
@@ -36,9 +34,7 @@ def copy_cost_item(
* The copy will have duplicated nested cost items * The copy will have duplicated nested cost items
:param cost_item: The cost item to be duplicated :param cost_item: The cost item to be duplicated
:type cost_item: ifcopenshell.entity_instance
:return: The duplicated cost item or the list of duplicated cost items if the latter has children :return: The duplicated cost item or the list of duplicated cost items if the latter has children
:rtype: ifcopenshell.entity_instance or list[ifcopenshell.entity_instance]
Example: Example:
.. code:: python .. code:: python
@@ -53,22 +49,29 @@ def copy_cost_item(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"cost_item": cost_item} return usecase.execute(cost_item)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
self.new_cost_items = [] new_cost_items: list[ifcopenshell.entity_instance]
return self.duplicate_cost_item(self.settings["cost_item"])
def duplicate_cost_item(self, cost_item): def execute(
self, cost_item: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
self.new_cost_items = []
self.duplicate_cost_item(cost_item)
return self.new_cost_items[0] if len(self.new_cost_items) == 1 else self.new_cost_items
def duplicate_cost_item(self, cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
new_cost_item = ifcopenshell.util.element.copy_deep(self.file, cost_item) new_cost_item = ifcopenshell.util.element.copy_deep(self.file, cost_item)
self.new_cost_items.append(new_cost_item) self.new_cost_items.append(new_cost_item)
self.copy_indirect_attributes(cost_item, new_cost_item) self.copy_indirect_attributes(cost_item, new_cost_item)
return new_cost_item return new_cost_item
def copy_indirect_attributes(self, from_element, 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): for inverse in self.file.get_inverse(from_element):
if inverse.is_a("IfcRelDefinesByProperties"): if inverse.is_a("IfcRelDefinesByProperties"):
inverse = ifcopenshell.util.element.copy(self.file, inverse) inverse = ifcopenshell.util.element.copy(self.file, inverse)
@@ -28,11 +28,8 @@ def edit_cost_item(
IfcCostItem, consult the IFC documentation. IfcCostItem, consult the IFC documentation.
:param cost_item: The IfcCostItem entity you want to edit :param cost_item: The IfcCostItem entity you want to edit
:type cost_item: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,7 +39,5 @@ def edit_cost_item(
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule) item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.cost.edit_cost_item(model, cost_item=item, attributes={"Name": "Foo"}) ifcopenshell.api.cost.edit_cost_item(model, cost_item=item, attributes={"Name": "Foo"})
""" """
settings = {"cost_item": cost_item, "attributes": attributes or {}} for name, value in attributes.items():
setattr(cost_item, name, value)
for name, value in settings["attributes"].items():
setattr(settings["cost_item"], name, value)
@@ -28,11 +28,8 @@ def edit_cost_item_quantity(
IfcPhysicalQuantity, consult the IFC documentation. IfcPhysicalQuantity, consult the IFC documentation.
:param physical_quantity: The IfcPhysicalQuantity entity you want to edit :param physical_quantity: The IfcPhysicalQuantity entity you want to edit
:type physical_quantity: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -50,7 +47,5 @@ def edit_cost_item_quantity(
ifcopenshell.api.cost.edit_cost_item_quantity(model, ifcopenshell.api.cost.edit_cost_item_quantity(model,
physical_quantity=quantity, "attributes": {"VolumeValue": 3.0}) physical_quantity=quantity, "attributes": {"VolumeValue": 3.0})
""" """
settings = {"physical_quantity": physical_quantity, "attributes": attributes or {}} for name, value in attributes.items():
setattr(physical_quantity, name, value)
for name, value in settings["attributes"].items():
setattr(settings["physical_quantity"], name, value)
@@ -28,11 +28,8 @@ def edit_cost_schedule(
IfcCostSchedule, consult the IFC documentation. IfcCostSchedule, consult the IFC documentation.
:param cost_schedule: The IfcCostSchedule entity you want to edit :param cost_schedule: The IfcCostSchedule entity you want to edit
:type cost_schedule: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,8 +39,5 @@ def edit_cost_schedule(
ifcopenshell.api.cost.edit_cost_schedule(model, ifcopenshell.api.cost.edit_cost_schedule(model,
cost_schedule=schedule, attributes={"Name": "Foo"}) cost_schedule=schedule, attributes={"Name": "Foo"})
""" """
for name, value in attributes.items():
settings = {"cost_schedule": cost_schedule, "attributes": attributes or {}} setattr(cost_schedule, name, value)
for name, value in settings["attributes"].items():
setattr(settings["cost_schedule"], name, value)
@@ -31,11 +31,8 @@ def edit_cost_value(
IfcCostValue, consult the IFC documentation. IfcCostValue, consult the IFC documentation.
:param cost_value: The IfcCostValue entity you want to edit :param cost_value: The IfcCostValue entity you want to edit
:type cost_value: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -49,14 +46,12 @@ def edit_cost_value(
ifcopenshell.api.cost.edit_cost_value(model, cost_value=value, ifcopenshell.api.cost.edit_cost_value(model, cost_value=value,
attributes={"AppliedValue": 42.0}) attributes={"AppliedValue": 42.0})
""" """
settings = {"cost_value": cost_value, "attributes": attributes or {}} for name, value in attributes.items():
for name, value in settings["attributes"].items():
if name == "AppliedValue" and value is not None: if name == "AppliedValue" and value is not None:
# TODO: support all applied value select types # TODO: support all applied value select types
value = file.createIfcMonetaryMeasure(value) value = file.createIfcMonetaryMeasure(value)
elif name == "UnitBasis": elif name == "UnitBasis":
old_unit_basis = settings["cost_value"].UnitBasis old_unit_basis = cost_value.UnitBasis
if value: if value:
value_component = file.create_entity( value_component = file.create_entity(
ifcopenshell.util.unit.get_unit_measure_class(value["UnitComponent"].UnitType), ifcopenshell.util.unit.get_unit_measure_class(value["UnitComponent"].UnitType),
@@ -65,4 +60,4 @@ def edit_cost_value(
value = file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"]) value = file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"])
if old_unit_basis and len(file.get_inverse(old_unit_basis)) == 0: if old_unit_basis and len(file.get_inverse(old_unit_basis)) == 0:
ifcopenshell.util.element.remove_deep(file, old_unit_basis) ifcopenshell.util.element.remove_deep(file, old_unit_basis)
setattr(settings["cost_value"], name, value) setattr(cost_value, name, value)
@@ -30,12 +30,9 @@ def unassign_cost_item_quantity(
have any impact on the cost item. have any impact on the cost item.
:param cost_item: The IfcCostItem to remove quantities from :param cost_item: The IfcCostItem to remove quantities from
:type cost_item: ifcopenshell.entity_instance
:param products: A list of IfcProducts that may have parametrically :param products: A list of IfcProducts that may have parametrically
connected quantities to the cost item connected quantities to the cost item
:type products: list[ifcopenshell.entity_instance]
:return: None :return: None
:rtype: None
Example: Example:
@@ -69,38 +66,39 @@ def unassign_cost_item_quantity(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"cost_item": cost_item, "products": products or []} return usecase.execute(cost_item, products or [])
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
for quantity in self.settings["cost_item"].CostQuantities or []: def execute(self, cost_item: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]) -> None:
quantities = set(cost_item.CostQuantities or [])
for quantity in cost_item.CostQuantities or []:
for inverse in self.file.get_inverse(quantity): for inverse in self.file.get_inverse(quantity):
if not inverse.is_a("IfcElementQuantity"): if not inverse.is_a("IfcElementQuantity"):
continue continue
for rel in inverse.DefinesOccurrence or []: for rel in inverse.DefinesOccurrence or []:
for related_object in rel.RelatedObjects: for related_object in rel.RelatedObjects:
if related_object in self.settings["products"]: if related_object in products:
self.quantities.remove(quantity) quantities.remove(quantity)
self.settings["cost_item"].CostQuantities = list(self.quantities) cost_item.CostQuantities = list(quantities)
for product in self.settings["products"]: for product in products:
ifcopenshell.api.control.unassign_control( ifcopenshell.api.control.unassign_control(
self.file, self.file,
related_object=product, related_object=product,
relating_control=self.settings["cost_item"], relating_control=cost_item,
) )
self.update_cost_item_count() self.update_cost_item_count(cost_item)
def update_cost_item_count(self): def update_cost_item_count(self, cost_item: ifcopenshell.entity_instance) -> None:
# This is a bold assumption # This is a bold assumption
# https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564 # https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564
if len(self.settings["cost_item"].CostQuantities) == 1: if len(cost_item.CostQuantities) == 1:
quantity = self.settings["cost_item"].CostQuantities[0] quantity = cost_item.CostQuantities[0]
if quantity.is_a("IfcQuantityCount"): if quantity.is_a("IfcQuantityCount"):
count = 0 count = 0
for rel in self.settings["cost_item"].Controls: for rel in cost_item.Controls:
count += len(rel.RelatedObjects) count += len(rel.RelatedObjects)
if count: if count:
quantity[3] = count quantity[3] = count
@@ -41,15 +41,12 @@ def assign_document(
:param product: The list of objects to associate the document to. This could be :param product: The list of objects to associate the document to. This could be
almost any sensible object in IFC. almost any sensible object in IFC.
:type product: list[ifcopenshell.entity_instance]
:param document: The IfcDocumentReference to associate to, or :param document: The IfcDocumentReference to associate to, or
alternatively an IfcDocumentInformation, though this is not alternatively an IfcDocumentInformation, though this is not
recommended. recommended.
:type document: ifcopenshell.entity_instance
:return: The IfcRelAssociatesDocument relationship :return: The IfcRelAssociatesDocument relationship
or `None` if `products` was an empty list or all products were or `None` if `products` was an empty list or all products were
already assigned to the `document`. already assigned to the `document`.
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -65,43 +62,41 @@ def assign_document(
# Let's imagine storey represents an IfcBuildingStorey for the ground floor # Let's imagine storey represents an IfcBuildingStorey for the ground floor
ifcopenshell.api.document.assign_document(model, products=[storey], document=reference) ifcopenshell.api.document.assign_document(model, products=[storey], document=reference)
""" """
settings = {
"products": products,
"document": document,
}
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference? # TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
# NOTE: reuses code from `library.assign_reference` # NOTE: reuses code from `library.assign_reference`
referenced_elements = ifcopenshell.util.element.get_referenced_elements(settings["document"]) referenced_elements = ifcopenshell.util.element.get_referenced_elements(document)
products: set[ifcopenshell.entity_instance] = set(settings["products"]) products_set: set[ifcopenshell.entity_instance] = set(products)
products = products - referenced_elements products_set = products_set - referenced_elements
if not products: if not products_set:
return return
if file.schema == "IFC2X3": if file.schema == "IFC2X3":
rel = next( rel = next(
(r for r in file.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == settings["document"]), (r for r in file.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == document),
None, None,
) )
else: else:
ifc_class = settings["document"].is_a() ifc_class = document.is_a()
if ifc_class == "IfcDocumentReference": if ifc_class == "IfcDocumentReference":
rel = next(iter(settings["document"].DocumentRefForObjects), None) rel = next(iter(document.DocumentRefForObjects), None)
elif ifc_class == "IfcDocumentInformation": elif ifc_class == "IfcDocumentInformation":
rel = next(iter(settings["document"].DocumentInfoForObjects), None) rel = next(iter(document.DocumentInfoForObjects), None)
else:
assert False, f"Unexpected document type: {ifc_class}"
if not rel: if not rel:
return file.create_entity( return file.create_entity(
"IfcRelAssociatesDocument", "IfcRelAssociatesDocument",
GlobalId=ifcopenshell.guid.new(), GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(file), OwnerHistory=ifcopenshell.api.owner.create_owner_history(file),
RelatedObjects=list(products), RelatedObjects=list(products_set),
RelatingDocument=settings["document"], RelatingDocument=document,
) )
related_objects = set(rel.RelatedObjects) | products related_objects = set(rel.RelatedObjects) | products_set
rel.RelatedObjects = list(related_objects) rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(file, element=rel) ifcopenshell.api.owner.update_owner_history(file, element=rel)
return rel return rel
@@ -30,11 +30,8 @@ def edit_information(
IfcDocumentInformation, consult the IFC documentation. IfcDocumentInformation, consult the IFC documentation.
:param reference: The IfcDocumentInformation entity you want to edit :param reference: The IfcDocumentInformation entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -46,7 +43,5 @@ def edit_information(
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan", attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
"Location": "A-GA-6100 - Overall Plan.pdf"}) "Location": "A-GA-6100 - Overall Plan.pdf"})
""" """
settings = {"information": information, "attributes": attributes} for name, value in attributes.items():
setattr(information, name, value)
for name, value in settings["attributes"].items():
setattr(settings["information"], name, value)
@@ -30,11 +30,8 @@ def edit_reference(
IfcDocumentReference, consult the IFC documentation. IfcDocumentReference, consult the IFC documentation.
:param reference: The IfcDocumentReference entity you want to edit :param reference: The IfcDocumentReference entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -49,7 +46,5 @@ def edit_reference(
ifcopenshell.api.document.edit_reference(model, ifcopenshell.api.document.edit_reference(model,
reference=reference, attributes={"Identification": "2.1.15"}) reference=reference, attributes={"Identification": "2.1.15"})
""" """
settings = {"reference": reference, "attributes": attributes} for name, value in attributes.items():
setattr(reference, name, value)
for name, value in settings["attributes"].items():
setattr(settings["reference"], name, value)
@@ -28,11 +28,8 @@ def edit_text_literal(
IfcTextLiteral, consult the IFC documentation. IfcTextLiteral, consult the IFC documentation.
:param reference: The IfcTextLiteral entity you want to edit :param reference: The IfcTextLiteral entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,7 +39,5 @@ def edit_text_literal(
ifcopenshell.api.drawing.edit_text_literal(model, ifcopenshell.api.drawing.edit_text_literal(model,
text_literal=text, attributes={"Literal": "MY ANNOTATION"}) text_literal=text, attributes={"Literal": "MY ANNOTATION"})
""" """
settings = {"text_literal": text_literal, "attributes": attributes or {}} for name, value in attributes.items():
setattr(text_literal, name, value)
for name, value in settings["attributes"].items():
setattr(settings["text_literal"], name, value)
@@ -17,7 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit import ifcopenshell.util.unit
from typing import Union from typing import Union, Any
COORD = Union[tuple[float, float], tuple[float, float, float]] COORD = Union[tuple[float, float], tuple[float, float, float]]
@@ -82,6 +82,9 @@ def add_axis_representation(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
is_2d = len(self.settings["axis"][0]) == 2 is_2d = len(self.settings["axis"][0]) == 2
@@ -26,14 +26,9 @@ def add_footprint_representation(
# A list of IFC curves to include in the curve set # A list of IFC curves to include in the curve set
curves: list[ifcopenshell.entity_instance], curves: list[ifcopenshell.entity_instance],
) -> ifcopenshell.entity_instance: ) -> ifcopenshell.entity_instance:
settings = {
"context": context,
"curves": curves,
}
return file.createIfcShapeRepresentation( return file.createIfcShapeRepresentation(
settings["context"], context,
settings["context"].ContextIdentifier, context.ContextIdentifier,
"GeometricCurveSet", "GeometricCurveSet",
[file.createIfcGeometricCurveSet(settings["curves"])], [file.createIfcGeometricCurveSet(curves)],
) )
@@ -17,7 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit import ifcopenshell.util.unit
from typing import Optional from typing import Optional, Any
COORD_3D = tuple[float, float, float] COORD_3D = tuple[float, float, float]
@@ -60,6 +60,9 @@ def add_mesh_representation(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
if self.settings["unit_scale"] is None: if self.settings["unit_scale"] is None:
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
@@ -52,6 +52,9 @@ def add_profile_representation(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
self.settings["clippings"] = [Clipping.parse(c) for c in self.settings["clippings"]] self.settings["clippings"] = [Clipping.parse(c) for c in self.settings["clippings"]]
@@ -426,6 +426,9 @@ def add_window_representation(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
builder = ShapeBuilder(self.file) builder = ShapeBuilder(self.file)
np_X, np_Y, np_Z = 0, 1, 2 np_X, np_Y, np_Z = 0, 1, 2
@@ -31,53 +31,33 @@ def connect_path(
related_connection: str = "NOTDEFINED", related_connection: str = "NOTDEFINED",
description: Optional[str] = None, description: Optional[str] = None,
) -> ifcopenshell.entity_instance: ) -> ifcopenshell.entity_instance:
settings = { incompatible_connections: list[ifcopenshell.entity_instance] = []
"relating_element": relating_element, for rel in relating_element.ConnectedTo:
"related_element": related_element,
"relating_connection": relating_connection,
"related_connection": related_connection,
"description": description,
}
incompatible_connections = []
for rel in settings["relating_element"].ConnectedTo:
if not rel.is_a("IfcRelConnectsPathElements"): if not rel.is_a("IfcRelConnectsPathElements"):
continue continue
if rel.RelatedElement == settings["related_element"]: if rel.RelatedElement == related_element:
incompatible_connections.append(rel) incompatible_connections.append(rel)
elif ( elif rel.RelatingConnectionType in ["ATSTART", "ATEND"] and rel.RelatingConnectionType == relating_connection:
rel.RelatingConnectionType in ["ATSTART", "ATEND"]
and rel.RelatingConnectionType == settings["relating_connection"]
):
incompatible_connections.append(rel) incompatible_connections.append(rel)
for rel in settings["relating_element"].ConnectedFrom: for rel in relating_element.ConnectedFrom:
if not rel.is_a("IfcRelConnectsPathElements"): if not rel.is_a("IfcRelConnectsPathElements"):
continue continue
if ( if rel.RelatedConnectionType in ["ATSTART", "ATEND"] and rel.RelatedConnectionType == relating_connection:
rel.RelatedConnectionType in ["ATSTART", "ATEND"]
and rel.RelatedConnectionType == settings["relating_connection"]
):
incompatible_connections.append(rel) incompatible_connections.append(rel)
for rel in settings["related_element"].ConnectedFrom: for rel in related_element.ConnectedFrom:
if not rel.is_a("IfcRelConnectsPathElements"): if not rel.is_a("IfcRelConnectsPathElements"):
continue continue
if ( if rel.RelatedConnectionType in ["ATSTART", "ATEND"] and rel.RelatedConnectionType == related_connection:
rel.RelatedConnectionType in ["ATSTART", "ATEND"]
and rel.RelatedConnectionType == settings["related_connection"]
):
incompatible_connections.append(rel) incompatible_connections.append(rel)
for rel in settings["related_element"].ConnectedTo: for rel in related_element.ConnectedTo:
if not rel.is_a("IfcRelConnectsPathElements"): if not rel.is_a("IfcRelConnectsPathElements"):
continue continue
if rel.RelatedElement == settings["relating_element"]: if rel.RelatedElement == relating_element:
incompatible_connections.append(rel) incompatible_connections.append(rel)
elif ( elif rel.RelatingConnectionType in ["ATSTART", "ATEND"] and rel.RelatingConnectionType == related_connection:
rel.RelatingConnectionType in ["ATSTART", "ATEND"]
and rel.RelatingConnectionType == settings["related_connection"]
):
incompatible_connections.append(rel) incompatible_connections.append(rel)
if incompatible_connections: if incompatible_connections:
@@ -87,14 +67,15 @@ def connect_path(
if history: if history:
ifcopenshell.util.element.remove_deep2(file, history) ifcopenshell.util.element.remove_deep2(file, history)
return file.createIfcRelConnectsPathElements( return file.create_entity(
"IfcRelConnectsPathElements",
ifcopenshell.guid.new(), ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(file), OwnerHistory=ifcopenshell.api.owner.create_owner_history(file),
Description=settings["description"], Description=description,
RelatingElement=settings["relating_element"], RelatingElement=relating_element,
RelatedElement=settings["related_element"], RelatedElement=related_element,
RelatingConnectionType=settings["relating_connection"], RelatingConnectionType=relating_connection,
RelatedConnectionType=settings["related_connection"], RelatedConnectionType=related_connection,
RelatingPriorities=[], RelatingPriorities=[],
RelatedPriorities=[], RelatedPriorities=[],
) )
@@ -46,29 +46,25 @@ def assign_group(
ifcopenshell.api.group.assign_group(model, ifcopenshell.api.group.assign_group(model,
products=model.by_type("IfcFurniture"), group=group) products=model.by_type("IfcFurniture"), group=group)
""" """
settings = { if not products:
"products": products,
"group": group,
}
if not settings["products"]:
return return
if not settings["group"].IsGroupedBy: is_grouped_by: tuple[ifcopenshell.entity_instance, ...]
if not (is_grouped_by := group.IsGroupedBy):
return file.create_entity( return file.create_entity(
"IfcRelAssignsToGroup", "IfcRelAssignsToGroup",
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file), "OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": settings["products"], "RelatedObjects": products,
"RelatingGroup": settings["group"], "RelatingGroup": group,
} },
) )
rel = settings["group"].IsGroupedBy[0] rel = is_grouped_by[0]
related_objects = set(rel.RelatedObjects) or set() related_objects = set(rel.RelatedObjects) or set()
products = set(settings["products"]) products_set = set(products)
if products.issubset(related_objects): if products_set.issubset(related_objects):
return rel return rel
rel.RelatedObjects = list(related_objects | products) rel.RelatedObjects = list(related_objects | products_set)
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel}) ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
return rel return rel
@@ -26,11 +26,8 @@ def edit_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance, att
IfcGroup, consult the IFC documentation. IfcGroup, consult the IFC documentation.
:param group: The IfcGroup entity you want to edit :param group: The IfcGroup entity you want to edit
:type group: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -40,7 +37,5 @@ def edit_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance, att
ifcopenshell.api.group.edit_group(model, ifcopenshell.api.group.edit_group(model,
group=group, attributes={"Description": "All furniture and joinery included in the unit"}) group=group, attributes={"Description": "All furniture and joinery included in the unit"})
""" """
settings = {"group": group, "attributes": attributes} for name, value in attributes.items():
setattr(group, name, value)
for name, value in settings["attributes"].items():
setattr(settings["group"], name, value)
@@ -46,17 +46,12 @@ def unassign_group(
bad_furniture = furniture[0] bad_furniture = furniture[0]
ifcopenshell.api.group.unassign_group(model, products=[bad_furniture], group=group) ifcopenshell.api.group.unassign_group(model, products=[bad_furniture], group=group)
""" """
settings = { if not group.IsGroupedBy:
"products": products,
"group": group,
}
if not settings["group"].IsGroupedBy:
return return
rel = settings["group"].IsGroupedBy[0] rel = group.IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set() related_objects = set(rel.RelatedObjects) or set()
products = set(settings["products"]) products_set = set(products)
related_objects -= products related_objects -= products_set
if related_objects: if related_objects:
rel.RelatedObjects = list(related_objects) rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel}) ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
@@ -45,24 +45,19 @@ def update_group_products(
ifcopenshell.api.group.update_group_products(model, ifcopenshell.api.group.update_group_products(model,
products=model.by_type("IfcFurniture"), group=group) products=model.by_type("IfcFurniture"), group=group)
""" """
settings = { if not group.IsGroupedBy:
"group": group,
"products": products,
}
if not settings["group"].IsGroupedBy:
return file.create_entity( return file.create_entity(
"IfcRelAssignsToGroup", "IfcRelAssignsToGroup",
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file), "OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": settings["products"], "RelatedObjects": products,
"RelatingGroup": settings["group"], "RelatingGroup": group,
} }
) )
else: else:
rels = settings["group"].IsGroupedBy rels = group.IsGroupedBy
objects = set(settings["products"]) objects = set(products)
for rel in rels: for rel in rels:
objects.update([g for g in rel.RelatedObjects if g.is_a("IfcGroup")]) objects.update([g for g in rel.RelatedObjects if g.is_a("IfcGroup")])
to_purge = rels[1:] to_purge = rels[1:]
@@ -37,7 +37,5 @@ def edit_layer(file: ifcopenshell.file, layer: ifcopenshell.entity_instance, att
ifcopenshell.api.layer.edit_layer(model, ifcopenshell.api.layer.edit_layer(model,
layer=layer, attributes={"Description": "All walls, based on the AIA standard."}) layer=layer, attributes={"Description": "All walls, based on the AIA standard."})
""" """
settings = {"layer": layer, "attributes": attributes} for name, value in attributes.items():
setattr(layer, name, value)
for name, value in settings["attributes"].items():
setattr(settings["layer"], name, value)
@@ -61,17 +61,11 @@ def unassign_layer(
# Let's undo it! # Let's undo it!
ifcopenshell.api.layer.unassign_layer(model, items=[representation.Items[0]], layer=layer) ifcopenshell.api.layer.unassign_layer(model, items=[representation.Items[0]], layer=layer)
""" """
settings = {
"items": items,
"layer": layer,
}
layer = settings["layer"]
assigned_items = set(layer.AssignedItems) or set() assigned_items = set(layer.AssignedItems) or set()
items = set(settings["items"]) items_set = set(items)
if not items.issubset(assigned_items): if not items_set.issubset(assigned_items):
return return
assigned_items = list(assigned_items - items) assigned_items = list(assigned_items - items_set)
# keep IFC valid in case if there are no items left # keep IFC valid in case if there are no items left
if assigned_items: if assigned_items:
@@ -28,11 +28,8 @@ def edit_library(file: ifcopenshell.file, library: ifcopenshell.entity_instance,
IfcLibraryInformation, consult the IFC documentation. IfcLibraryInformation, consult the IFC documentation.
:param library: The IfcLibraryInformation entity you want to edit :param library: The IfcLibraryInformation entity you want to edit
:type library: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -28,11 +28,8 @@ def edit_reference(
IfcLibraryReference, consult the IFC documentation. IfcLibraryReference, consult the IFC documentation.
:param reference: The IfcLibraryReference entity you want to edit :param reference: The IfcLibraryReference entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -44,7 +41,5 @@ def edit_reference(
ifcopenshell.api.library.edit_reference(model, ifcopenshell.api.library.edit_reference(model,
reference=reference, attributes={"Identification": "http://example.org/digitaltwin#AHU01"}) reference=reference, attributes={"Identification": "http://example.org/digitaltwin#AHU01"})
""" """
settings = {"reference": reference, "attributes": attributes} for name, value in attributes.items():
setattr(reference, name, value)
for name, value in settings["attributes"].items():
setattr(settings["reference"], name, value)
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.representation import ifcopenshell.util.representation
from typing import Any
def assign_profile( def assign_profile(
@@ -97,6 +98,7 @@ def assign_profile(
class Usecase: class Usecase:
file: ifcopenshell.file file: ifcopenshell.file
settings: dict[str, Any]
def execute(self) -> None: def execute(self) -> None:
# TODO: handle composite profiles # TODO: handle composite profiles
@@ -28,11 +28,8 @@ def edit_assigned_material(
IfcMaterial, consult the IFC documentation. IfcMaterial, consult the IFC documentation.
:param element: The IfcMaterial entity you want to edit :param element: The IfcMaterial entity you want to edit
:type element: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,7 +39,5 @@ def edit_assigned_material(
ifcopenshell.api.material.edit_assigned_material(model, ifcopenshell.api.material.edit_assigned_material(model,
element=concrete, attributes={"Description": "40MPA concrete with broom finish"}) element=concrete, attributes={"Description": "40MPA concrete with broom finish"})
""" """
settings = {"element": element, "attributes": attributes} for name, value in attributes.items():
setattr(element, name, value)
for name, value in settings["attributes"].items():
setattr(settings["element"], name, value)
@@ -31,13 +31,9 @@ def edit_constituent(
IfcMaterialConstituent, consult the IFC documentation. IfcMaterialConstituent, consult the IFC documentation.
:param constituent: The IfcMaterialConstituent entity you want to edit :param constituent: The IfcMaterialConstituent entity you want to edit
:type constituent: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict, optional
:param material: The IfcMaterial entity you want to change the constituent to :param material: The IfcMaterial entity you want to change the constituent to
:type material: ifcopenshell.entity_instance, optional
:return: None :return: None
:rtype: None
Example: Example:
@@ -65,8 +61,6 @@ def edit_constituent(
ifcopenshell.api.material.edit_constituent(model, ifcopenshell.api.material.edit_constituent(model,
constituent=constituent, attributes={"Name": "Glazing"}) constituent=constituent, attributes={"Name": "Glazing"})
""" """
settings = {"constituent": constituent, "attributes": attributes or {}, "material": material} for name, value in (attributes or {}).items():
setattr(constituent, name, value)
for name, value in settings["attributes"].items(): constituent.Material = material
setattr(settings["constituent"], name, value)
settings["constituent"].Material = settings["material"]
@@ -31,14 +31,10 @@ def edit_layer(
IfcMaterialLayer, consult the IFC documentation. IfcMaterialLayer, consult the IFC documentation.
:param layer: The IfcMaterialLayer entity you want to edit :param layer: The IfcMaterialLayer entity you want to edit
:type layer: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict, optional
:param material: The IfcMaterial entity you want the layer to be made :param material: The IfcMaterial entity you want the layer to be made
from. from.
:type material: ifcopenshell.entity_instance, optional
:return: None :return: None
:rtype: None
Example: Example:
@@ -63,9 +59,7 @@ def edit_layer(
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum) layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13}) ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13})
""" """
settings = {"layer": layer, "attributes": attributes or {}, "material": material} for name, value in (attributes or {}).items():
setattr(layer, name, value)
for name, value in settings["attributes"].items(): if material:
setattr(settings["layer"], name, value) layer.Material = material
if settings["material"]:
settings["layer"].Material = settings["material"]
@@ -29,11 +29,8 @@ def edit_layer_usage(file: ifcopenshell.file, usage: ifcopenshell.entity_instanc
IfcMaterialLayerSetUsage, consult the IFC documentation. IfcMaterialLayerSetUsage, consult the IFC documentation.
:param usage: The IfcMaterialLayerSetUsage entity you want to edit :param usage: The IfcMaterialLayerSetUsage entity you want to edit
:type usage: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -75,7 +72,5 @@ def edit_layer_usage(file: ifcopenshell.file, usage: ifcopenshell.entity_instanc
ifcopenshell.api.material.edit_layer_usage(model, ifcopenshell.api.material.edit_layer_usage(model,
usage=rel.RelatingMaterial, attributes={"OffsetFromReferenceLine": 200}) usage=rel.RelatingMaterial, attributes={"OffsetFromReferenceLine": 200})
""" """
settings = {"usage": usage, "attributes": attributes} for name, value in attributes.items():
setattr(usage, name, value)
for name, value in settings["attributes"].items():
setattr(settings["usage"], name, value)
@@ -34,17 +34,12 @@ def edit_profile(
IfcMaterialProfile, consult the IFC documentation. IfcMaterialProfile, consult the IFC documentation.
:param profile: The IfcMaterialProfile entity you want to edit :param profile: The IfcMaterialProfile entity you want to edit
:type profile: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict, optional
:param profile_def: The IfcProfileDef entity the profile curve should be :param profile_def: The IfcProfileDef entity the profile curve should be
extruded from. extruded from.
:type profile_def: ifcopenshell.entity_instance, optional
:param material: The IfcMaterial entity you want to change the profile :param material: The IfcMaterial entity you want to change the profile
to be made from. to be made from.
:type material: ifcopenshell.entity_instance, optional
:return: None :return: None
:rtype: None
Example: Example:
@@ -80,16 +75,9 @@ def edit_profile(
ifcopenshell.api.material.edit_profile(model, ifcopenshell.api.material.edit_profile(model,
profile=profile_item, profile_def=hea200, material=steel2) profile=profile_item, profile_def=hea200, material=steel2)
""" """
settings = { for name, value in (attributes or {}).items():
"profile": profile, setattr(profile, name, value)
"attributes": attributes or {}, if material:
"profile_def": profile_def, profile.Material = material
"material": material, if profile_def:
} profile.Profile = profile_def
for name, value in settings["attributes"].items():
setattr(settings["profile"], name, value)
if settings["material"]:
settings["profile"].Material = settings["material"]
if settings["profile_def"]:
settings["profile"].Profile = settings["profile_def"]
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.geom import ifcopenshell.geom
import ifcopenshell.util.representation import ifcopenshell.util.representation
from ifcopenshell.geom import ShapeType
from typing import Any from typing import Any
@@ -34,11 +35,8 @@ def edit_profile_usage(
IfcMaterialProfileSetUsage, consult the IFC documentation. IfcMaterialProfileSetUsage, consult the IFC documentation.
:param usage: The IfcMaterialProfileSetUsage entity you want to edit :param usage: The IfcMaterialProfileSetUsage entity you want to edit
:type usage: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -98,16 +96,19 @@ def edit_profile_usage(
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
self.cardinal_point = self.settings["attributes"].get("CardinalPoint")
if self.cardinal_point and self.cardinal_point != self.settings["usage"].CardinalPoint: def execute(self, usage: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
self.attributes = attributes
self.cardinal_point = attributes.get("CardinalPoint")
if self.cardinal_point and self.cardinal_point != usage.CardinalPoint:
self.update_cardinal_point() self.update_cardinal_point()
for name, value in self.settings["attributes"].items(): for name, value in attributes.items():
setattr(self.settings["usage"], name, value) setattr(usage, name, value)
def update_cardinal_point(self): def update_cardinal_point(self):
material_set = self.settings["usage"].ForProfileSet material_set = self.attributes["usage"].ForProfileSet
self.profile = material_set.CompositeProfile self.profile = material_set.CompositeProfile
if not self.profile and material_set.MaterialProfiles: if not self.profile and material_set.MaterialProfiles:
self.profile = material_set.MaterialProfiles[0].Profile self.profile = material_set.MaterialProfiles[0].Profile
@@ -117,13 +118,13 @@ class Usecase:
self.position = self.calculate_position() self.position = self.calculate_position()
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
for rel in self.file.get_inverse(self.settings["usage"]): for rel in self.file.get_inverse(self.attributes["usage"]):
if not rel.is_a("IfcRelAssociatesMaterial"): if not rel.is_a("IfcRelAssociatesMaterial"):
continue continue
for element in rel.RelatedObjects: for element in rel.RelatedObjects:
self.update_representation(element) self.update_representation(element)
else: else:
for rel in self.settings["usage"].AssociatedTo: for rel in self.attributes["usage"].AssociatedTo:
for element in rel.RelatedObjects: for element in rel.RelatedObjects:
self.update_representation(element) self.update_representation(element)
@@ -166,7 +167,7 @@ class Usecase:
elif self.cardinal_point == 9: elif self.cardinal_point == 9:
return self.get_top_right(shape) return self.get_top_right(shape)
def get_bottom_left(self, shape): def get_bottom_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
x = [v[i] for i in range(0, len(v), 3)] x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)] y = [v[i + 1] for i in range(0, len(v), 3)]
@@ -174,13 +175,13 @@ class Usecase:
height = max(y) - min(y) height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, height / 2, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, height / 2, 0.0)))
def get_bottom_centre(self, shape): def get_bottom_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
y = [v[i + 1] for i in range(0, len(v), 3)] y = [v[i + 1] for i in range(0, len(v), 3)]
height = max(y) - min(y) height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, height / 2, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, height / 2, 0.0)))
def get_bottom_right(self, shape): def get_bottom_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
x = [v[i] for i in range(0, len(v), 3)] x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)] y = [v[i + 1] for i in range(0, len(v), 3)]
@@ -188,22 +189,22 @@ class Usecase:
height = max(y) - min(y) height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, height / 2, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, height / 2, 0.0)))
def get_mid_depth_left(self, shape): def get_mid_depth_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
x = [v[i] for i in range(0, len(v), 3)] x = [v[i] for i in range(0, len(v), 3)]
width = max(x) - min(x) width = max(x) - min(x)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, 0.0, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, 0.0, 0.0)))
def get_mid_depth_centre(self, shape): def get_mid_depth_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)))
def get_mid_depth_right(self, shape): def get_mid_depth_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
x = [v[i] for i in range(0, len(v), 3)] x = [v[i] for i in range(0, len(v), 3)]
width = max(x) - min(x) width = max(x) - min(x)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, 0.0, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, 0.0, 0.0)))
def get_top_left(self, shape): def get_top_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
x = [v[i] for i in range(0, len(v), 3)] x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)] y = [v[i + 1] for i in range(0, len(v), 3)]
@@ -211,13 +212,13 @@ class Usecase:
height = max(y) - min(y) height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, -height / 2, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, -height / 2, 0.0)))
def get_top_centre(self, shape): def get_top_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
y = [v[i + 1] for i in range(0, len(v), 3)] y = [v[i + 1] for i in range(0, len(v), 3)]
height = max(y) - min(y) height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, -height / 2, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, -height / 2, 0.0)))
def get_top_right(self, shape): def get_top_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
v = shape.verts v = shape.verts
x = [v[i] for i in range(0, len(v), 3)] x = [v[i] for i in range(0, len(v), 3)]
y = [v[i + 1] for i in range(0, len(v), 3)] y = [v[i + 1] for i in range(0, len(v), 3)]
@@ -225,7 +226,7 @@ class Usecase:
height = max(y) - min(y) height = max(y) - min(y)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, -height / 2, 0.0))) return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, -height / 2, 0.0)))
def update_representation(self, element): def update_representation(self, element: ifcopenshell.entity_instance) -> None:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation: if not representation:
return return
@@ -234,5 +235,5 @@ class Usecase:
if subelement.is_a("IfcSweptAreaSolid") and subelement.SweptArea == self.profile: if subelement.is_a("IfcSweptAreaSolid") and subelement.SweptArea == self.profile:
self.update_swept_area_solid(subelement) self.update_swept_area_solid(subelement)
def update_swept_area_solid(self, element): def update_swept_area_solid(self, element: ifcopenshell.entity_instance) -> None:
element.Position = self.position element.Position = self.position
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element
def remove_constituent( def remove_constituent(
@@ -54,8 +54,6 @@ def remove_list_item(
# Let's remove the glass # Let's remove the glass
ifcopenshell.api.material.remove_list_item(model, material_list=material_set, material_index=1) ifcopenshell.api.material.remove_list_item(model, material_list=material_set, material_index=1)
""" """
settings = {"material_list": material_list, "material_index": material_index} materials = list(material_list.Materials)
materials.pop(material_index)
materials = list(settings["material_list"].Materials) material_list.Materials = materials
materials.pop(settings["material_index"])
settings["material_list"].Materials = materials
@@ -57,16 +57,17 @@ def reorder_set_item(
ifcopenshell.api.material.reorder_set_item(model, ifcopenshell.api.material.reorder_set_item(model,
material_set=material_set, old_index=0, new_index=1) material_set=material_set, old_index=0, new_index=1)
""" """
settings = {"material_set": material_set, "old_index": old_index, "new_index": new_index} if material_set.is_a("IfcMaterialConstituentSet"):
if settings["material_set"].is_a("IfcMaterialConstituentSet"):
set_name = "MaterialConstituents" set_name = "MaterialConstituents"
elif settings["material_set"].is_a("IfcMaterialLayerSet"): elif material_set.is_a("IfcMaterialLayerSet"):
set_name = "MaterialLayers" set_name = "MaterialLayers"
elif settings["material_set"].is_a("IfcMaterialProfileSet"): elif material_set.is_a("IfcMaterialProfileSet"):
set_name = "MaterialProfiles" set_name = "MaterialProfiles"
elif settings["material_set"].is_a("IfcMaterialList"): elif material_set.is_a("IfcMaterialList"):
set_name = "Materials" set_name = "Materials"
items = list(getattr(settings["material_set"], set_name) or []) else:
items.insert(settings["new_index"], items.pop(settings["old_index"])) raise ValueError(f"Unexpected material set type: '{material_set.is_a()}'.")
setattr(settings["material_set"], set_name, items)
items = list(getattr(material_set, set_name) or [])
items.insert(new_index, items.pop(old_index))
setattr(material_set, set_name, items)
@@ -19,6 +19,7 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api.owner import ifcopenshell.api.owner
import ifcopenshell.util.element import ifcopenshell.util.element
from typing import Any
def unassign_material(file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]) -> None: def unassign_material(file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]) -> None:
@@ -58,6 +59,9 @@ def unassign_material(file: ifcopenshell.file, products: list[ifcopenshell.entit
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
self.products = set(self.settings["products"]) self.products = set(self.settings["products"])
if not self.products: if not self.products:
@@ -17,7 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.api import ifcopenshell.api
from typing import Optional from typing import Optional, Any
def add_application( def add_application(
@@ -68,6 +68,9 @@ def add_application(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
if not self.settings["application_developer"]: if not self.settings["application_developer"]:
self.settings["application_developer"] = self.create_application_organisation() self.settings["application_developer"] = self.create_application_organisation()
@@ -48,16 +48,14 @@ def add_role(
identification="AWB", name="Architects Without Ballpens") identification="AWB", name="Architects Without Ballpens")
ifcopenshell.api.owner.add_role(model, assigned_object=organisation, role="ARCHITECT") ifcopenshell.api.owner.add_role(model, assigned_object=organisation, role="ARCHITECT")
""" """
settings = {"assigned_object": assigned_object, "role": role} element = file.create_entity("IfcActorRole", Role="ARCHITECT")
if role:
element = file.createIfcActorRole("ARCHITECT")
if settings["role"]:
try: try:
element.Role = settings["role"] element.Role = role
except: except:
element.Role = "USERDEFINED" element.Role = "USERDEFINED"
element.UserDefinedRole = settings["role"] element.UserDefinedRole = role
roles = list(settings["assigned_object"].Roles) if settings["assigned_object"].Roles else [] roles = list(assigned_object.Roles) if assigned_object.Roles else []
roles.append(element) roles.append(element)
settings["assigned_object"].Roles = roles assigned_object.Roles = roles
return element return element
@@ -26,11 +26,8 @@ def edit_actor(file: ifcopenshell.file, actor: ifcopenshell.entity_instance, att
IfcActor, consult the IFC documentation. IfcActor, consult the IFC documentation.
:param actor: The IfcActor entity you want to edit :param actor: The IfcActor entity you want to edit
:type actor: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -49,7 +46,5 @@ def edit_actor(file: ifcopenshell.file, actor: ifcopenshell.entity_instance, att
ifcopenshell.api.actor.edit_actor(model, ifcopenshell.api.actor.edit_actor(model,
actor=actor, attributes={"Description": "Responsible for buildings A, B, and C."}) actor=actor, attributes={"Description": "Responsible for buildings A, B, and C."})
""" """
settings = {"actor": actor, "attributes": attributes} for name, value in attributes.items():
setattr(actor, name, value)
for name, value in settings["attributes"].items():
setattr(settings["actor"], name, value)
@@ -26,11 +26,8 @@ def edit_address(file: ifcopenshell.file, address: ifcopenshell.entity_instance,
IfcAddress, consult the IFC documentation. IfcAddress, consult the IFC documentation.
:param address: The IfcAddress entity you want to edit :param address: The IfcAddress entity you want to edit
:type address: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -51,7 +48,5 @@ def edit_address(file: ifcopenshell.file, address: ifcopenshell.entity_instance,
"ElectronicMailAddresses": ["bobthebuilder@example.com"], "ElectronicMailAddresses": ["bobthebuilder@example.com"],
"WWWHomePageURL": "https://thinkmoult.com"}) "WWWHomePageURL": "https://thinkmoult.com"})
""" """
settings = {"address": address, "attributes": attributes} for name, value in attributes.items():
setattr(address, name, value)
for name, value in settings["attributes"].items():
setattr(settings["address"], name, value)
@@ -28,11 +28,8 @@ def edit_organisation(
IfcOrganization, consult the IFC documentation. IfcOrganization, consult the IFC documentation.
:param organisation: The IfcOrganization entity you want to edit :param organisation: The IfcOrganization entity you want to edit
:type organisation: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -43,7 +40,5 @@ def edit_organisation(
ifcopenshell.api.owner.edit_organisation(model, organisation=organisation, ifcopenshell.api.owner.edit_organisation(model, organisation=organisation,
attributes={"name": "Architects Without Ballpens"}) attributes={"name": "Architects Without Ballpens"})
""" """
settings = {"organisation": organisation, "attributes": attributes} for name, value in attributes.items():
setattr(organisation, name, value)
for name, value in settings["attributes"].items():
setattr(settings["organisation"], name, value)
@@ -26,11 +26,8 @@ def edit_person(file: ifcopenshell.file, person: ifcopenshell.entity_instance, a
IfcPerson, consult the IFC documentation. IfcPerson, consult the IFC documentation.
:param person: The IfcPerson entity you want to edit :param person: The IfcPerson entity you want to edit
:type person: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -41,7 +38,5 @@ def edit_person(file: ifcopenshell.file, person: ifcopenshell.entity_instance, a
ifcopenshell.api.owner.edit_person(model, person=person, ifcopenshell.api.owner.edit_person(model, person=person,
attributes={"MiddleNames": ["The"], "FamilyName": "Builder"}) attributes={"MiddleNames": ["The"], "FamilyName": "Builder"})
""" """
settings = {"person": person, "attributes": attributes} for name, value in attributes.items():
setattr(person, name, value)
for name, value in settings["attributes"].items():
setattr(settings["person"], name, value)
@@ -26,11 +26,8 @@ def edit_role(file: ifcopenshell.file, role: ifcopenshell.entity_instance, attri
IfcActorRole, consult the IFC documentation. IfcActorRole, consult the IFC documentation.
:param role: The IfcActorRole entity you want to edit :param role: The IfcActorRole entity you want to edit
:type role: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -45,7 +42,5 @@ def edit_role(file: ifcopenshell.file, role: ifcopenshell.entity_instance, attri
# But Bob is not an architect # But Bob is not an architect
ifcopenshell.api.owner.edit_role(model, role=role, attributes={"Role": "CONSTRUCTIONMANAGER"}) ifcopenshell.api.owner.edit_role(model, role=role, attributes={"Role": "CONSTRUCTIONMANAGER"})
""" """
settings = {"role": role, "attributes": attributes} for name, value in attributes.items():
setattr(role, name, value)
for name, value in settings["attributes"].items():
setattr(settings["role"], name, value)
@@ -16,12 +16,14 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import numpy.typing as npt
import ifcopenshell.util.unit import ifcopenshell.util.unit
from typing import Optional from ifcopenshell.util.shape_builder import V, SequenceOfVectors, ifc_safe_vector_type
from typing import Optional, Union
def add_arbitrary_profile( def add_arbitrary_profile(
file: ifcopenshell.file, profile: list[tuple[float, float]], name: Optional[str] = None file: ifcopenshell.file, profile: SequenceOfVectors, name: Optional[str] = None
) -> ifcopenshell.entity_instance: ) -> ifcopenshell.entity_instance:
"""Adds a new arbitrary polyline-based profile """Adds a new arbitrary polyline-based profile
@@ -33,13 +35,10 @@ def add_arbitrary_profile(
identical. identical.
:param profile: A list of coordinates :param profile: A list of coordinates
:type profile: list[tuple[float, float]]
:param name: If the profile is semantically significant (i.e. to be :param name: If the profile is semantically significant (i.e. to be
managed and reused by the user) then it must be named. Otherwise, managed and reused by the user) then it must be named. Otherwise,
this may be left as none. this may be left as none.
:type name: str, optional
:return: The newly created IfcArbitraryClosedProfileDef :return: The newly created IfcArbitraryClosedProfileDef
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -53,26 +52,30 @@ def add_arbitrary_profile(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"profile": profile, "name": name} return usecase.execute(V(profile), name)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
points = [self.convert_si_to_unit(p) for p in self.settings["profile"]]
if self.file.schema == "IFC2X3":
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
else:
dimensions = len(points[0])
if dimensions == 2:
ifc_points = self.file.createIfcCartesianPointList2D(points)
elif dimensions == 3:
ifc_points = self.file.createIfcCartesianPointList3D(points)
curve = self.file.createIfcIndexedPolyCurve(ifc_points)
return self.file.createIfcArbitraryClosedProfileDef("AREA", self.settings["name"], curve)
def convert_si_to_unit(self, co): def execute(self, profile: npt.NDArray, name: Union[str, None]):
if isinstance(co, (tuple, list)): self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
return [self.convert_si_to_unit(o) for o in co] points = self.convert_si_to_unit(profile)
return co / self.settings["unit_scale"] if self.file.schema == "IFC2X3":
curve = self.file.create_entity(
"IfcPolyline",
[self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(p)) for p in points],
)
else:
dimensions = points.shape[1]
if dimensions == 2:
ifc_points = self.file.create_entity("IfcCartesianPointList2D", ifc_safe_vector_type(points))
elif dimensions == 3:
ifc_points = self.file.create_entity("IfcCartesianPointList3D", ifc_safe_vector_type(points))
else:
assert False, f"Invalid dimensions: {dimensions}."
curve = self.file.create_entity("IfcIndexedPolyCurve", ifc_points)
return self.file.create_entity("IfcArbitraryClosedProfileDef", "AREA", name, curve)
def convert_si_to_unit(self, co: npt.NDArray) -> npt.NDArray:
return co / self.unit_scale
@@ -17,13 +17,15 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit import ifcopenshell.util.unit
from typing import Optional import numpy.typing as npt
from ifcopenshell.util.shape_builder import SequenceOfVectors, ifc_safe_vector_type, V
from typing import Optional, Union
def add_arbitrary_profile_with_voids( def add_arbitrary_profile_with_voids(
file: ifcopenshell.file, file: ifcopenshell.file,
outer_profile: list[tuple[float, float]], outer_profile: SequenceOfVectors,
inner_profiles: list[list[tuple[float, float]]], inner_profiles: list[SequenceOfVectors],
name: Optional[str] = None, name: Optional[str] = None,
) -> ifcopenshell.entity_instance: ) -> ifcopenshell.entity_instance:
"""Adds a new arbitrary polyline-based profile with voids """Adds a new arbitrary polyline-based profile with voids
@@ -41,15 +43,11 @@ def add_arbitrary_profile_with_voids(
provided in SI meters. provided in SI meters.
:param outer_profile: A list of coordinates :param outer_profile: A list of coordinates
:type profile: list[tuple[float, float]]
:param inner_profiles: A list of polylines :param inner_profiles: A list of polylines
:type profile: list[list[tuple[float, float]]]
:param name: If the profile is semantically significant (i.e. to be :param name: If the profile is semantically significant (i.e. to be
managed and reused by the user) then it must be named. Otherwise, managed and reused by the user) then it must be named. Otherwise,
this may be left as none. this may be left as none.
:type name: str, optional
:return: The newly created IfcArbitraryProfileDefWithVoids :return: The newly created IfcArbitraryProfileDefWithVoids
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -63,37 +61,52 @@ def add_arbitrary_profile_with_voids(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"outer_profile": outer_profile, "inner_profiles": inner_profiles, "name": name} return usecase.execute(V(outer_profile), [V(p) for p in inner_profiles], name)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
outer_points = [self.convert_si_to_unit(p) for p in self.settings["outer_profile"]] def execute(
inner_points = [] self,
for inner_profile in self.settings["inner_profiles"]: outer_profile: npt.NDArray,
inner_points.append([self.convert_si_to_unit(p) for p in inner_profile]) inner_profiles: list[npt.NDArray],
name: Union[str, None],
):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
outer_points = self.convert_si_to_unit(outer_profile)
inner_points: list[npt.NDArray] = []
for inner_profile in inner_profiles:
inner_points.append(self.convert_si_to_unit(inner_profile))
inner_curves: list[ifcopenshell.entity_instance] = []
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
outer_curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in outer_points]) outer_curve = self.file.create_entity(
inner_curves = [] "IfcPolyline",
[self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(p)) for p in outer_points],
)
for inner_point in inner_points: for inner_point in inner_points:
inner_curves.append( inner_curves.append(
self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in inner_point]) self.file.create_entity(
"IfcPolyline",
[self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(p)) for p in inner_point],
)
) )
else: else:
outer_curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(outer_points)) outer_curve = self.file.create_entity(
inner_curves = [] "IfcIndexedPolyCurve",
(self.file.create_entity("IfcCartesianPointList3D", ifc_safe_vector_type(outer_points))),
)
for inner_point in inner_points: for inner_point in inner_points:
dimensions = len(inner_point[0]) dimensions = inner_point.shape[1]
if dimensions == 2: if dimensions == 2:
ifc_points = self.file.createIfcCartesianPointList2D(inner_point) ifc_points = self.file.create_entity("IfcCartesianPointList2D", ifc_safe_vector_type(inner_point))
elif dimensions == 3: elif dimensions == 3:
ifc_points = self.file.createIfcCartesianPointList3D(inner_point) ifc_points = self.file.create_entity("IfcCartesianPointList3D", ifc_safe_vector_type(inner_point))
inner_curves.append(self.file.createIfcIndexedPolyCurve(ifc_points)) else:
return self.file.createIfcArbitraryProfileDefWithVoids("AREA", self.settings["name"], outer_curve, inner_curves) assert False, f"Invalid dimensions: {dimensions}."
inner_curves.append(self.file.create_entity("IfcIndexedPolyCurve", ifc_points))
return self.file.create_entity("IfcArbitraryProfileDefWithVoids", "AREA", name, outer_curve, inner_curves)
def convert_si_to_unit(self, co): def convert_si_to_unit(self, co: npt.NDArray) -> npt.NDArray:
if isinstance(co, (tuple, list)): return co / self.unit_scale
return [self.convert_si_to_unit(o) for o in co]
return co / self.settings["unit_scale"]
@@ -26,11 +26,8 @@ def edit_profile(file: ifcopenshell.file, profile: ifcopenshell.entity_instance,
IfcProfileDef, consult the IFC documentation. IfcProfileDef, consult the IFC documentation.
:param profile: The IfcProfileDef entity you want to edit :param profile: The IfcProfileDef entity you want to edit
:type profile: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -43,7 +40,5 @@ def edit_profile(file: ifcopenshell.file, profile: ifcopenshell.entity_instance,
ifcopenshell.api.profile.edit_profile(model, ifcopenshell.api.profile.edit_profile(model,
profile=circle, attributes={"ProfileName": "1000mm Dia"}) profile=circle, attributes={"ProfileName": "1000mm Dia"})
""" """
settings = {"profile": profile, "attributes": attributes} for name, value in attributes.items():
setattr(profile, name, value)
for name, value in settings["attributes"].items():
setattr(settings["profile"], name, value)
@@ -20,7 +20,7 @@ import ifcopenshell
import ifcopenshell.api.owner import ifcopenshell.api.owner
import ifcopenshell.api.pset import ifcopenshell.api.pset
import ifcopenshell.guid import ifcopenshell.guid
from typing import Optional from typing import Optional, Any
def add_pset( def add_pset(
@@ -92,12 +92,11 @@ def add_pset(
# Add a fire rating property standardised by buildingSMART. # Add a fire rating property standardised by buildingSMART.
ifcopenshell.api.pset.edit_pset(model, pset=pset, properties={"FireRating": "2HR"}) ifcopenshell.api.pset.edit_pset(model, pset=pset, properties={"FireRating": "2HR"})
""" """
settings = {"product": product, "name": name}
is_ifc2x3 = file.schema == "IFC2X3" is_ifc2x3 = file.schema == "IFC2X3"
if settings["product"].is_a("IfcObject") or settings["product"].is_a("IfcContext"): if product.is_a("IfcObject") or product.is_a("IfcContext"):
for rel in settings["product"].IsDefinedBy or []: for rel in product.IsDefinedBy or []:
if rel.is_a("IfcRelDefinesByProperties") and rel.RelatingPropertyDefinition.Name == settings["name"]: if rel.is_a("IfcRelDefinesByProperties") and rel.RelatingPropertyDefinition.Name == name:
return rel.RelatingPropertyDefinition return rel.RelatingPropertyDefinition
pset = file.create_entity( pset = file.create_entity(
@@ -105,15 +104,15 @@ def add_pset(
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file), "OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"Name": settings["name"], "Name": name,
}, },
) )
ifcopenshell.api.pset.assign_pset(file, [settings["product"]], pset) ifcopenshell.api.pset.assign_pset(file, [product], pset)
return pset return pset
elif settings["product"].is_a("IfcTypeObject"): elif product.is_a("IfcTypeObject"):
for definition in settings["product"].HasPropertySets or []: for definition in product.HasPropertySets or []:
if definition.Name == settings["name"]: if definition.Name == name:
return definition return definition
pset = file.create_entity( pset = file.create_entity(
@@ -121,42 +120,43 @@ def add_pset(
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file), "OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"Name": settings["name"], "Name": name,
}, },
) )
ifcopenshell.api.pset.assign_pset(file, [settings["product"]], pset) ifcopenshell.api.pset.assign_pset(file, [product], pset)
return pset return pset
# in IFC2X3 IfcMaterialDefinition not yet existed # in IFC2X3 IfcMaterialDefinition not yet existed
elif settings["product"].is_a("IfcMaterialDefinition") or settings["product"].is_a("IfcMaterial"): elif product.is_a("IfcMaterialDefinition") or product.is_a("IfcMaterial"):
kwargs = {"Material": settings["product"]} kwargs: dict[str, Any]
kwargs = {"Material": product}
if file.schema == "IFC2X3": if file.schema == "IFC2X3":
ifc_class = ifc2x3_subclass or "IfcExtendedMaterialProperties" ifc_class = ifc2x3_subclass or "IfcExtendedMaterialProperties"
definitions = (d for d in file.by_type("IfcMaterialProperties") if d.Material == settings["product"]) definitions = (d for d in file.by_type("IfcMaterialProperties") if d.Material == product)
if ifc_class == "IfcExtendedMaterialProperties": if ifc_class == "IfcExtendedMaterialProperties":
kwargs["Name"] = settings["name"] kwargs["Name"] = name
else: else:
ifc_class = "IfcMaterialProperties" ifc_class = "IfcMaterialProperties"
definitions = settings["product"].HasProperties definitions = product.HasProperties
kwargs["Name"] = settings["name"] kwargs["Name"] = name
for definition in definitions: for definition in definitions:
# In IFC2X3 not all IfcMaterialProperties has Name # In IFC2X3 not all IfcMaterialProperties has Name
if getattr(definition, "Name", None) == settings["name"]: if getattr(definition, "Name", None) == name:
return definition return definition
return file.create_entity(ifc_class, **kwargs) return file.create_entity(ifc_class, **kwargs)
elif settings["product"].is_a("IfcProfileDef"): elif product.is_a("IfcProfileDef"):
# in IFC2X3 IfcProfileProperties doesn't have Name and we cannot identify them # in IFC2X3 IfcProfileProperties doesn't have Name and we cannot identify them
if file.schema != "IFC2X3": if file.schema != "IFC2X3":
for definition in settings["product"].HasProperties or []: for definition in product.HasProperties or []:
if definition.Name == settings["name"]: if definition.Name == name:
return definition return definition
kwargs = {} kwargs = {}
kwargs["ProfileDefinition"] = settings["product"] kwargs["ProfileDefinition"] = product
if file.schema != "IFC2X3": if file.schema != "IFC2X3":
kwargs["Name"] = settings["name"] kwargs["Name"] = name
if is_ifc2x3: if is_ifc2x3:
ifc_class = ifc2x3_subclass or "IfcGeneralProfileProperties" ifc_class = ifc2x3_subclass or "IfcGeneralProfileProperties"
@@ -164,4 +164,4 @@ def add_pset(
ifc_class = "IfcProfileProperties" ifc_class = "IfcProfileProperties"
return file.create_entity(ifc_class, **kwargs) return file.create_entity(ifc_class, **kwargs)
raise TypeError(f"Class '{settings['product'].is_a(True)}' doesn't support adding a property set.") raise TypeError(f"Class '{product.is_a(True)}' doesn't support adding a property set.")
@@ -19,6 +19,7 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api.owner import ifcopenshell.api.owner
import ifcopenshell.guid import ifcopenshell.guid
from typing import Any
def add_qto(file: ifcopenshell.file, product: ifcopenshell.entity_instance, name: str) -> ifcopenshell.entity_instance: def add_qto(file: ifcopenshell.file, product: ifcopenshell.entity_instance, name: str) -> ifcopenshell.entity_instance:
@@ -83,9 +84,15 @@ def add_qto(file: ifcopenshell.file, product: ifcopenshell.entity_instance, name
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
if self.settings["product"].is_a("IfcObject") or self.settings["product"].is_a("IfcContext"): product: ifcopenshell.entity_instance = self.settings["product"]
for rel in self.settings["product"].IsDefinedBy or []: name: str = self.settings["name"]
if product.is_a("IfcObject") or product.is_a("IfcContext"):
for rel in product.IsDefinedBy or []:
if ( if (
rel.is_a("IfcRelDefinesByProperties") rel.is_a("IfcRelDefinesByProperties")
and rel.RelatingPropertyDefinition.Name == self.settings["name"] and rel.RelatingPropertyDefinition.Name == self.settings["name"]
@@ -103,14 +110,14 @@ class Usecase:
} }
) )
return qto return qto
elif self.settings["product"].is_a("IfcTypeObject"): elif product.is_a("IfcTypeObject"):
for definition in self.settings["product"].HasPropertySets or []: for definition in product.HasPropertySets or []:
if definition.Name == self.settings["name"]: if definition.Name == name:
return definition return definition
qto = self.create_qto() qto = self.create_qto()
has_property_sets = list(self.settings["product"].HasPropertySets or []) has_property_sets = list(product.HasPropertySets or [])
has_property_sets.append(qto) has_property_sets.append(qto)
self.settings["product"].HasPropertySets = has_property_sets product.HasPropertySets = has_property_sets
return qto return qto
def create_qto(self): def create_qto(self):
@@ -28,11 +28,8 @@ def edit_prop_template(
IfcSimplePropertyTemplate, consult the IFC documentation. IfcSimplePropertyTemplate, consult the IFC documentation.
:param prop_template: The IfcSimplePropertyTemplate entity you want to edit :param prop_template: The IfcSimplePropertyTemplate entity you want to edit
:type prop_template: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -28,11 +28,8 @@ def edit_pset_template(
IfcPropertySetTemplate, consult the IFC documentation. IfcPropertySetTemplate, consult the IFC documentation.
:param pset_template: The IfcPropertySetTemplate entity you want to edit :param pset_template: The IfcPropertySetTemplate entity you want to edit
:type pset_template: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -45,7 +42,5 @@ def edit_pset_template(
ifcopenshell.api.pset_template.edit_pset_template(model, ifcopenshell.api.pset_template.edit_pset_template(model,
pset_template=template, attributes={"Name": "ABC_RiskFactors"}) pset_template=template, attributes={"Name": "ABC_RiskFactors"})
""" """
settings = {"pset_template": pset_template, "attributes": attributes} for name, value in attributes.items():
setattr(pset_template, name, value)
for name, value in settings["attributes"].items():
setattr(settings["pset_template"], name, value)
@@ -36,15 +36,12 @@ def add_resource_quantity(
This base quantity is then used in other calculations. This base quantity is then used in other calculations.
:param resource: The IfcConstructionResource to add a quantity to. :param resource: The IfcConstructionResource to add a quantity to.
:type resource: ifcopenshell.entity_instance
:param ifc_class: The type of quantity to add, chosen from :param ifc_class: The type of quantity to add, chosen from
IfcQuantityArea (for material), IfcQuantityCount (for products), IfcQuantityArea (for material), IfcQuantityCount (for products),
IfcQuantityLength (for material), IfcQuantityTime (for equipment or IfcQuantityLength (for material), IfcQuantityTime (for equipment or
labour), IfcQuantityVolume (for material), and IfcQuantityWeight labour), IfcQuantityVolume (for material), and IfcQuantityWeight
(for material). (for material).
:type ifc_class: str,optional
:return: The newly created quantity depending on the IFC class :return: The newly created quantity depending on the IFC class
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -65,8 +62,6 @@ def add_resource_quantity(
ifcopenshell.api.resource.edit_resource_quantity(model, ifcopenshell.api.resource.edit_resource_quantity(model,
physical_quantity=quantity, attributes={"TimeValue": 8.0}) physical_quantity=quantity, attributes={"TimeValue": 8.0})
""" """
settings = {"resource": resource, "ifc_class": ifc_class}
resource_type = resource.is_a() resource_type = resource.is_a()
supported_quantities = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES[resource_type] supported_quantities = ifcopenshell.util.resource.RESOURCES_TO_QUANTITIES[resource_type]
if ifc_class not in supported_quantities: if ifc_class not in supported_quantities:
@@ -75,14 +70,14 @@ def add_resource_quantity(
f"Supported quantities: {','.join(supported_quantities)}" f"Supported quantities: {','.join(supported_quantities)}"
) )
quantity = file.create_entity(settings["ifc_class"], Name="Unnamed") quantity = file.create_entity(ifc_class, Name="Unnamed")
# 3 IfcPhysicalSimpleQuantity Value # 3 IfcPhysicalSimpleQuantity Value
if settings["ifc_class"] == "IfcQuantityCount": if ifc_class == "IfcQuantityCount":
quantity[3] = 0 quantity[3] = 0
else: else:
quantity[3] = 0.0 quantity[3] = 0.0
old_quantity = settings["resource"].BaseQuantity old_quantity = resource.BaseQuantity
settings["resource"].BaseQuantity = quantity resource.BaseQuantity = quantity
if old_quantity: if old_quantity:
ifcopenshell.util.element.remove_deep(file, old_quantity) ifcopenshell.util.element.remove_deep(file, old_quantity)
return quantity return quantity
@@ -26,11 +26,8 @@ def edit_resource(file: ifcopenshell.file, resource: ifcopenshell.entity_instanc
IfcResource, consult the IFC documentation. IfcResource, consult the IFC documentation.
:param resource: The IfcResource entity you want to edit :param resource: The IfcResource entity you want to edit
:type resource: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -42,7 +39,5 @@ def edit_resource(file: ifcopenshell.file, resource: ifcopenshell.entity_instanc
# Change the name of the resource to "Zone A Crew" # Change the name of the resource to "Zone A Crew"
ifcopenshell.api.resource.edit_resource(model, resource=resource, attributes={"Name": "Foo"}) ifcopenshell.api.resource.edit_resource(model, resource=resource, attributes={"Name": "Foo"})
""" """
settings = {"resource": resource, "attributes": attributes} for name, value in attributes.items():
setattr(resource, name, value)
for name, value in settings["attributes"].items():
setattr(settings["resource"], name, value)
@@ -28,11 +28,8 @@ def edit_resource_quantity(
IfC quantity, consult the IFC documentation. IfC quantity, consult the IFC documentation.
:param physical_quantity: The IfC quantity entity you want to edit :param physical_quantity: The IfC quantity entity you want to edit
:type physical_quantity: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -53,10 +50,5 @@ def edit_resource_quantity(
ifcopenshell.api.resource.edit_resource_quantity(model, ifcopenshell.api.resource.edit_resource_quantity(model,
physical_quantity=time, attributes={"TimeValue": 8.0}) physical_quantity=time, attributes={"TimeValue": 8.0})
""" """
settings = { for name, value in attributes.items():
"physical_quantity": physical_quantity, setattr(physical_quantity, name, value)
"attributes": attributes,
}
for name, value in settings["attributes"].items():
setattr(settings["physical_quantity"], name, value)
@@ -18,6 +18,9 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api.sequence import ifcopenshell.api.sequence
import ifcopenshell.util.constraint
import ifcopenshell.util.date
import ifcopenshell.util.resource
from typing import Any from typing import Any
@@ -30,11 +33,8 @@ def edit_resource_time(
IfcResourceTime, consult the IFC documentation. IfcResourceTime, consult the IFC documentation.
:param resource_time: The IfcResourceTime entity you want to edit :param resource_time: The IfcResourceTime entity you want to edit
:type resource_time: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -62,25 +62,23 @@ def edit_resource_time(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"resource_time": resource_time, "attributes": attributes} return usecase.execute(resource_time, attributes)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
self.resource = self.get_resource()
def execute(self, resource_time: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
resource = self.get_resource(resource_time)
# If the user specifies both an end date and a duration, the duration takes priority # If the user specifies both an end date and a duration, the duration takes priority
if ( if attributes.get("ScheduleWork", None) and "ScheduleFinish" in attributes.keys():
self.settings["attributes"].get("ScheduleWork", None) del attributes["ScheduleFinish"]
and "ScheduleFinish" in self.settings["attributes"].keys() if attributes.get("ActualWork", None) and "ActualFinish" in attributes.keys():
): del attributes["ActualFinish"]
del self.settings["attributes"]["ScheduleFinish"]
if self.settings["attributes"].get("ActualWork", None) and "ActualFinish" in self.settings["attributes"].keys():
del self.settings["attributes"]["ActualFinish"]
for name, value in self.settings["attributes"].items(): for name, value in attributes.items():
metrics = ifcopenshell.util.constraint.get_metric_constraints(self.resource, "Usage." + name) metrics = ifcopenshell.util.constraint.get_metric_constraints(resource, "Usage." + name)
if metrics and ifcopenshell.util.constraint.is_hard_constraint(metrics[0]): if metrics and ifcopenshell.util.constraint.is_hard_constraint(metrics[0]):
continue continue
if value: if value:
@@ -88,13 +86,13 @@ class Usecase:
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
elif name == "ScheduleWork" or name == "ActualWork" or name == "RemainingTime": elif name == "ScheduleWork" or name == "ActualWork" or name == "RemainingTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
setattr(self.settings["resource_time"], name, value) setattr(resource_time, name, value)
if name == "ScheduleUsage" and ifcopenshell.util.constraint.get_metric_constraints( if name == "ScheduleUsage" and ifcopenshell.util.constraint.get_metric_constraints(
self.resource, "Usage.ScheduleWork" resource, "Usage.ScheduleWork"
): ):
task = ifcopenshell.util.resource.get_task_assignments(self.resource) task = ifcopenshell.util.resource.get_task_assignments(resource)
if task: if task:
ifcopenshell.api.sequence.calculate_task_duration(self.file, task=task) ifcopenshell.api.sequence.calculate_task_duration(self.file, task=task)
def get_resource(self): def get_resource(self, resource_time: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
return [e for e in self.file.get_inverse(self.settings["resource_time"]) if e.is_a("IfcResource")][0] return next(e for e in self.file.get_inverse(resource_time) if e.is_a("IfcResource"))
@@ -23,6 +23,7 @@ import ifcopenshell.api.geometry
import ifcopenshell.util.system import ifcopenshell.util.system
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
from typing import Any
def copy_class(file: ifcopenshell.file, product: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: def copy_class(file: ifcopenshell.file, product: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
@@ -74,6 +75,9 @@ def copy_class(file: ifcopenshell.file, product: ifcopenshell.entity_instance) -
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
result = ifcopenshell.util.element.copy(self.file, self.settings["product"]) result = ifcopenshell.util.element.copy(self.file, self.settings["product"])
self.copy_direct_attributes(result) self.copy_direct_attributes(result)
@@ -19,7 +19,7 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api.owner import ifcopenshell.api.owner
import ifcopenshell.guid import ifcopenshell.guid
from typing import Optional from typing import Optional, Any
def create_entity( def create_entity(
@@ -80,6 +80,9 @@ def create_entity(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
element = self.file.create_entity( element = self.file.create_entity(
self.settings["ifc_class"], self.settings["ifc_class"],
@@ -27,7 +27,7 @@ import ifcopenshell.util.representation
import ifcopenshell.util.type import ifcopenshell.util.type
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.element import ifcopenshell.util.element
from typing import Optional, Union, Literal from typing import Optional, Union, Literal, Any
def reassign_class( def reassign_class(
@@ -87,6 +87,7 @@ def reassign_class(
class Usecase: class Usecase:
file: ifcopenshell.file file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
ifc_class: str = self.settings["ifc_class"] ifc_class: str = self.settings["ifc_class"]
@@ -29,11 +29,8 @@ def add_task_time(
(especially for maintenance tasks). (especially for maintenance tasks).
:param task: The task to add time data to. :param task: The task to add time data to.
:type task: ifcopenshell.entity_instance
:param is_recurring: Whether or not the time should recur. :param is_recurring: Whether or not the time should recur.
:type is_recurring: bool
:return: The newly created IfcTaskTime. :return: The newly created IfcTaskTime.
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -61,11 +58,9 @@ def add_task_time(
ifcopenshell.api.sequence.edit_task_time(model, ifcopenshell.api.sequence.edit_task_time(model,
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"}) task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
""" """
settings = {"task": task, "is_recurring": is_recurring} if is_recurring:
if settings["is_recurring"]:
task_time = file.create_entity("IfcTaskTimeRecurring") task_time = file.create_entity("IfcTaskTimeRecurring")
else: else:
task_time = file.create_entity("IfcTaskTime") task_time = file.create_entity("IfcTaskTime")
settings["task"].TaskTime = task_time task.TaskTime = task_time
return task_time return task_time
@@ -34,17 +34,13 @@ def assign_lag_time(
are allowed. are allowed.
:param rel_sequence: The IfcRelSequence to assign the lag time to. :param rel_sequence: The IfcRelSequence to assign the lag time to.
:type rel_sequence: ifcopenshell.entity_instance
:param lag_value: An ISO standardised duration string. :param lag_value: An ISO standardised duration string.
:type lag_value: str
:param duration_type: Choose from WORKTIME for the associated :param duration_type: Choose from WORKTIME for the associated
calendar-based lag times (this is the most common scenario and is calendar-based lag times (this is the most common scenario and is
recommended as a default), or ELAPSEDTIME to not follow the recommended as a default), or ELAPSEDTIME to not follow the
calendar. You may also choose NOTDEFINED but the behaviour of this calendar. You may also choose NOTDEFINED but the behaviour of this
is unclear. is unclear.
:type duration_type: str
:return: The newly created IfcLagTime :return: The newly created IfcLagTime
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -84,16 +80,10 @@ def assign_lag_time(
# for whatever reason. # for whatever reason.
ifcopenshell.api.sequence.assign_lag_time(model, rel_sequence=sequence, lag_value="P1D") ifcopenshell.api.sequence.assign_lag_time(model, rel_sequence=sequence, lag_value="P1D")
""" """
settings = { lag_value = file.create_entity("IfcDuration", ifcopenshell.util.date.datetime2ifc(lag_value, "IfcDuration"))
"rel_sequence": rel_sequence, lag_time = file.create_entity("IfcLagTime", DurationType=duration_type, LagValue=lag_value)
"lag_value": lag_value, if rel_sequence.is_a("IfcRelSequence"):
"duration_type": duration_type, if rel_sequence.TimeLag and len(file.get_inverse(rel_sequence.TimeLag)) == 1:
} file.remove(rel_sequence.TimeLag)
rel_sequence.TimeLag = lag_time
lag_value = file.createIfcDuration(ifcopenshell.util.date.datetime2ifc(settings["lag_value"], "IfcDuration"))
lag_time = file.create_entity("IfcLagTime", DurationType=settings["duration_type"], LagValue=lag_value)
if settings["rel_sequence"].is_a("IfcRelSequence"):
if settings["rel_sequence"].TimeLag and len(file.get_inverse(settings["rel_sequence"].TimeLag)) == 1:
file.remove(settings["rel_sequence"].TimeLag)
settings["rel_sequence"].TimeLag = lag_time
return lag_time return lag_time
@@ -65,11 +65,8 @@ def assign_recurrence_pattern(
:param parent: Either an IfcTaskTimeRecurring if you are defining a :param parent: Either an IfcTaskTimeRecurring if you are defining a
recurring schedule for a task, or IfcWorkTime if you are defining a recurring schedule for a task, or IfcWorkTime if you are defining a
recurring pattern for a workdays or holidays in a calendar. recurring pattern for a workdays or holidays in a calendar.
:type parent: ifcopenshell.entity_instance
:param recurrence_type: One of the types of recurrences. :param recurrence_type: One of the types of recurrences.
:type recurrence_type: str
:return: The newly created IfcRecurrencePattern :return: The newly created IfcRecurrencePattern
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -108,16 +105,14 @@ def assign_recurrence_pattern(
ifcopenshell.api.sequence.edit_recurrence_pattern(model, ifcopenshell.api.sequence.edit_recurrence_pattern(model,
recurrence_pattern=pattern, attributes={"DayComponent": [1], "Interval": 6}) recurrence_pattern=pattern, attributes={"DayComponent": [1], "Interval": 6})
""" """
settings = {"parent": parent, "recurrence_type": recurrence_type} recurrence = file.create_entity("IfcRecurrencePattern", recurrence_type)
recurrence = file.createIfcRecurrencePattern(settings["recurrence_type"]) if parent.is_a("IfcWorkTime"):
if parent.RecurrencePattern and len(file.get_inverse(parent.RecurrencePattern)) == 1:
if settings["parent"].is_a("IfcWorkTime"): file.remove(parent.RecurrencePattern)
if settings["parent"].RecurrencePattern and len(file.get_inverse(settings["parent"].RecurrencePattern)) == 1: parent.RecurrencePattern = recurrence
file.remove(settings["parent"].RecurrencePattern) elif parent.is_a("IfcTaskTimeRecurring"):
settings["parent"].RecurrencePattern = recurrence if (recurrence_old := parent.Recurrence) and len(file.get_inverse(recurrence_old)) == 1:
elif settings["parent"].is_a("IfcTaskTimeRecurring"):
if recurrence_old := settings["parent"].Recurrence and len(file.get_inverse(recurrence_old)) == 1:
file.remove(recurrence_old) file.remove(recurrence_old)
settings["parent"].Recurrence = recurrence parent.Recurrence = recurrence
return recurrence return recurrence
@@ -51,13 +51,10 @@ def assign_sequence(
predecessor and successor tasks in the planning profession. predecessor and successor tasks in the planning profession.
:param relating_process: The previous / predecessor task. :param relating_process: The previous / predecessor task.
:type relating_process: ifcopenshell.entity_instance
:param related_process: The next / successor task. :param related_process: The next / successor task.
:type related_process: ifcopenshell.entity_instance
:param sequence_type: Choose from FINISH_START, FINISH_FINISH, :param sequence_type: Choose from FINISH_START, FINISH_FINISH,
START_START, or START_FINISH. START_START, or START_FINISH.
:return: The newly created IfcRelSequence :return: The newly created IfcRelSequence
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -109,24 +106,18 @@ def assign_sequence(
# to be 2000-01-05. # to be 2000-01-05.
ifcopenshell.api.sequence.cascade_schedule(model, task=formwork) ifcopenshell.api.sequence.cascade_schedule(model, task=formwork)
""" """
settings = { for rel in related_process.IsSuccessorFrom or []:
"relating_process": relating_process, if rel.RelatingProcess == relating_process:
"related_process": related_process,
"sequence_type": sequence_type,
}
for rel in settings["related_process"].IsSuccessorFrom or []:
if rel.RelatingProcess == settings["relating_process"]:
return rel return rel
rel = file.create_entity( rel = file.create_entity(
"IfcRelSequence", "IfcRelSequence",
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file), "OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatingProcess": settings["relating_process"], "RelatingProcess": relating_process,
"RelatedProcess": settings["related_process"], "RelatedProcess": related_process,
"SequenceType": settings["sequence_type"], "SequenceType": sequence_type,
} }
) )
ifcopenshell.api.sequence.cascade_schedule(file, task=settings["relating_process"]) ifcopenshell.api.sequence.cascade_schedule(file, task=relating_process)
return rel return rel
@@ -20,6 +20,7 @@ import math
import ifcopenshell.api.sequence import ifcopenshell.api.sequence
import ifcopenshell.util.date import ifcopenshell.util.date
import ifcopenshell.util.element import ifcopenshell.util.element
from typing import Union
def calculate_task_duration(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> None: def calculate_task_duration(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> None:
@@ -35,9 +36,7 @@ def calculate_task_duration(file: ifcopenshell.file, task: ifcopenshell.entity_i
then nothing happens. then nothing happens.
:param task: The IfcTask to calculate the duration for. :param task: The IfcTask to calculate the duration for.
:type task: ifcopenshell.entity_instance
:return: None :return: None
:rtype: None
Example: Example:
@@ -82,18 +81,20 @@ def calculate_task_duration(file: ifcopenshell.file, task: ifcopenshell.entity_i
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"task": task} return usecase.execute(task)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
def execute(self, task: ifcopenshell.entity_instance) -> None:
self.task = task
self.seconds_per_workday = self.calculate_seconds_per_workday() self.seconds_per_workday = self.calculate_seconds_per_workday()
duration = self.calculate_max_resource_usage_duration() duration = self.calculate_max_resource_usage_duration()
if duration: if duration:
self.set_task_duration(duration) self.set_task_duration(duration)
def calculate_seconds_per_workday(self): def calculate_seconds_per_workday(self) -> float:
def get_work_schedule(task): def get_work_schedule(task):
for rel in task.HasAssignments or []: for rel in task.HasAssignments or []:
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"): if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"):
@@ -102,7 +103,7 @@ class Usecase:
return get_work_schedule(rel.RelatingObject) return get_work_schedule(rel.RelatingObject)
default_seconds_per_workday = 8 * 60 * 60 default_seconds_per_workday = 8 * 60 * 60
work_schedule = get_work_schedule(self.settings["task"]) work_schedule = get_work_schedule(self.task)
if not work_schedule: if not work_schedule:
return default_seconds_per_workday return default_seconds_per_workday
psets = ifcopenshell.util.element.get_psets(work_schedule) psets = ifcopenshell.util.element.get_psets(work_schedule)
@@ -115,9 +116,9 @@ class Usecase:
work_day_duration = ifcopenshell.util.date.ifc2datetime(psets["Pset_WorkControlCommon"]["WorkDayDuration"]) work_day_duration = ifcopenshell.util.date.ifc2datetime(psets["Pset_WorkControlCommon"]["WorkDayDuration"])
return work_day_duration.seconds return work_day_duration.seconds
def calculate_max_resource_usage_duration(self): def calculate_max_resource_usage_duration(self) -> float:
max_duration = 0 max_duration = 0
for rel in self.settings["task"].OperatesOn or []: for rel in self.task.OperatesOn or []:
for related_object in rel.RelatedObjects: for related_object in rel.RelatedObjects:
if related_object.is_a("IfcConstructionResource"): if related_object.is_a("IfcConstructionResource"):
duration = self.calculate_duration_in_days(related_object) duration = self.calculate_duration_in_days(related_object)
@@ -125,7 +126,7 @@ class Usecase:
max_duration = duration max_duration = duration
return max_duration return max_duration
def calculate_duration_in_days(self, resource): def calculate_duration_in_days(self, resource: ifcopenshell.entity_instance) -> Union[float, None]:
def is_hourly_work(schedule_work): def is_hourly_work(schedule_work):
return "T" in schedule_work return "T" in schedule_work
@@ -140,7 +141,7 @@ class Usecase:
schedule_seconds = (schedule_duration.days + partial_days) * self.seconds_per_workday schedule_seconds = (schedule_duration.days + partial_days) * self.seconds_per_workday
return math.ceil((schedule_seconds / self.seconds_per_workday) / schedule_usage) return math.ceil((schedule_seconds / self.seconds_per_workday) / schedule_usage)
def set_task_duration(self, duration): def set_task_duration(self, duration: float) -> None:
if not self.settings["task"].TaskTime: if not (task_time := self.task.TaskTime):
ifcopenshell.api.sequence.add_task_time(self.file, task=self.settings["task"]) ifcopenshell.api.sequence.add_task_time(self.file, task=self.task)
self.settings["task"].TaskTime.ScheduleDuration = f"P{duration}D" task_time.ScheduleDuration = f"P{duration}D"
@@ -24,7 +24,7 @@ import ifcopenshell.guid
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.sequence import ifcopenshell.util.sequence
import ifcopenshell.util.system import ifcopenshell.util.system
from typing import Optional from typing import Optional, Union
def create_baseline( def create_baseline(
@@ -44,11 +44,8 @@ def create_baseline(
* Same Resource Relationships * Same Resource Relationships
:param work_schedule: The planned work_schedule to baseline :param work_schedule: The planned work_schedule to baseline
:type work_schedule: ifcopenshell.entity_instance
:param name: baseline work schedule name :param name: baseline work schedule name
:type name: str, optional
:return: The baseline work_schedule :return: The baseline work_schedule
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -62,23 +59,20 @@ def create_baseline(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"work_schedule": work_schedule, "name": name} return usecase.execute(work_schedule, name)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
result = self.create_baseline_work_schedule(self.settings["work_schedule"])
return result
def create_baseline_work_schedule(self, work_schedule): def execute(self, work_schedule: ifcopenshell.entity_instance, name: Union[str, None]) -> None:
# create work schedule # create work schedule
if not work_schedule.PredefinedType == "PLANNED": if not work_schedule.PredefinedType == "PLANNED":
return return
baseline_work_schedule = ifcopenshell.api.sequence.add_work_schedule( baseline_work_schedule = ifcopenshell.api.sequence.add_work_schedule(
self.file, name=work_schedule.Name, predefined_type="BASELINE" self.file, name=work_schedule.Name, predefined_type="BASELINE"
) )
baseline_work_schedule.Name = self.settings["name"] baseline_work_schedule.Name = name
self.create_baseline_reference(work_schedule, baseline_work_schedule) self.create_baseline_reference(work_schedule, baseline_work_schedule)
for summary_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule): for summary_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
current, duplicate = ifcopenshell.api.sequence.duplicate_task(self.file, task=summary_task) current, duplicate = ifcopenshell.api.sequence.duplicate_task(self.file, task=summary_task)
@@ -88,7 +82,9 @@ class Usecase:
for i, task in enumerate(current): for i, task in enumerate(current):
self.create_baseline_reference(task, duplicate[i]) self.create_baseline_reference(task, duplicate[i])
def create_baseline_reference(self, relating_object, related_object): def create_baseline_reference(
self, relating_object: ifcopenshell.entity_instance, related_object: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
referenced_by = None referenced_by = None
if relating_object.Declares: if relating_object.Declares:
referenced_by = relating_object.Declares[0] referenced_by = relating_object.Declares[0]
@@ -23,9 +23,12 @@ import ifcopenshell.api.owner
import ifcopenshell.api.sequence import ifcopenshell.api.sequence
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.sequence import ifcopenshell.util.sequence
from typing import Union, Any
def duplicate_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: def duplicate_task(
file: ifcopenshell.file, task: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
"""Duplicates a task in the project """Duplicates a task in the project
The following relationships are also duplicated: The following relationships are also duplicated:
@@ -35,9 +38,7 @@ def duplicate_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance)
* The copy will have duplicated nested tasks * The copy will have duplicated nested tasks
:param task: The task to be duplicated :param task: The task to be duplicated
:type task: ifcopenshell.entity_instance
:return: The duplicated task or the list of duplicated tasks if the latter has children :return: The duplicated task or the list of duplicated tasks if the latter has children
:rtype: ifcopenshell.entity_instance or list of ifcopenshell.entity_instance
Example: Example:
.. code:: python .. code:: python
@@ -55,6 +56,9 @@ def duplicate_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance)
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
self.tracker = {"current": [], "duplicate": []} self.tracker = {"current": [], "duplicate": []}
self.duplicate_task(self.settings["task"]) self.duplicate_task(self.settings["task"])
@@ -28,11 +28,8 @@ def edit_lag_time(file: ifcopenshell.file, lag_time: ifcopenshell.entity_instanc
IfcLagTime, consult the IFC documentation. IfcLagTime, consult the IFC documentation.
:param lag_time: The IfcLagTime entity you want to edit :param lag_time: The IfcLagTime entity you want to edit
:type lag_time: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -75,14 +72,12 @@ def edit_lag_time(file: ifcopenshell.file, lag_time: ifcopenshell.entity_instanc
# Or, let's make it 2 days instead. # Or, let's make it 2 days instead.
ifcopenshell.api.sequence.edit_lag_time(model, lag_time=lag, attributes={"LagValue": "P2D"}) ifcopenshell.api.sequence.edit_lag_time(model, lag_time=lag, attributes={"LagValue": "P2D"})
""" """
settings = {"lag_time": lag_time, "attributes": attributes} for name, value in attributes.items():
for name, value in settings["attributes"].items():
if name == "LagValue" and value is not None: if name == "LagValue" and value is not None:
if isinstance(value, float): if isinstance(value, float):
value = file.createIfcRatioMeasure(value) value = file.createIfcRatioMeasure(value)
else: else:
value = file.createIfcDuration(ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")) value = file.createIfcDuration(ifcopenshell.util.date.datetime2ifc(value, "IfcDuration"))
setattr(settings["lag_time"], name, value) setattr(lag_time, name, value)
for rel in [r for r in file.get_inverse(settings["lag_time"]) if r.is_a("IfcRelSequence")]: for rel in [r for r in file.get_inverse(lag_time) if r.is_a("IfcRelSequence")]:
ifcopenshell.api.sequence.cascade_schedule(file, task=rel.RelatedProcess) ifcopenshell.api.sequence.cascade_schedule(file, task=rel.RelatedProcess)
@@ -30,11 +30,8 @@ def edit_recurrence_pattern(
IfcRecurrencePattern, consult the IFC documentation. IfcRecurrencePattern, consult the IFC documentation.
:param recurrence_pattern: The IfcRecurrencePattern entity you want to edit :param recurrence_pattern: The IfcRecurrencePattern entity you want to edit
:type recurrence_pattern: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -55,13 +52,8 @@ def edit_recurrence_pattern(
ifcopenshell.api.sequence.edit_recurrence_pattern(model, ifcopenshell.api.sequence.edit_recurrence_pattern(model,
recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]}) recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
""" """
settings = { for name, value in attributes.items():
"recurrence_pattern": recurrence_pattern, setattr(recurrence_pattern, name, value)
"attributes": attributes,
}
for name, value in settings["attributes"].items():
setattr(settings["recurrence_pattern"], name, value)
ifcopenshell.util.sequence.is_working_day.cache_clear() ifcopenshell.util.sequence.is_working_day.cache_clear()
ifcopenshell.util.sequence.is_calendar_applicable.cache_clear() ifcopenshell.util.sequence.is_calendar_applicable.cache_clear()
@@ -30,11 +30,8 @@ def edit_sequence(
IfcRelSequence, consult the IFC documentation. IfcRelSequence, consult the IFC documentation.
:param rel_sequence: The IfcRelSequence entity you want to edit :param rel_sequence: The IfcRelSequence entity you want to edit
:type rel_sequence: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -62,9 +59,7 @@ def edit_sequence(
ifcopenshell.api.sequence.edit_sequence(model, ifcopenshell.api.sequence.edit_sequence(model,
rel_sequence=sequence, attributes={"SequenceType": "START_START"}) rel_sequence=sequence, attributes={"SequenceType": "START_START"})
""" """
settings = {"rel_sequence": rel_sequence, "attributes": attributes} for name, value in attributes.items():
setattr(rel_sequence, name, value)
for name, value in settings["attributes"].items(): if "SequenceType" in attributes.keys():
setattr(settings["rel_sequence"], name, value) ifcopenshell.api.sequence.cascade_schedule(file, task=rel_sequence.RelatedProcess)
if "SequenceType" in settings["attributes"].keys():
ifcopenshell.api.sequence.cascade_schedule(file, task=settings["rel_sequence"].RelatedProcess)
@@ -26,11 +26,8 @@ def edit_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance, attri
IfcTask, consult the IFC documentation. IfcTask, consult the IFC documentation.
:param task: The IfcTask entity you want to edit :param task: The IfcTask entity you want to edit
:type task: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -48,7 +45,5 @@ def edit_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance, attri
# Change the identification # Change the identification
ifcopenshell.api.sequence.edit_task(model, task=task, attributes={"Identification": "M"}) ifcopenshell.api.sequence.edit_task(model, task=task, attributes={"Identification": "M"})
""" """
settings = {"task": task, "attributes": attributes or {}} for name, value in attributes.items():
setattr(task, name, value)
for name, value in settings["attributes"].items():
setattr(settings["task"], name, value)
@@ -36,11 +36,8 @@ def edit_task_time(
IfcTaskTime, consult the IFC documentation. IfcTaskTime, consult the IFC documentation.
:param task_time: The IfcTaskTime entity you want to edit :param task_time: The IfcTaskTime entity you want to edit
:type task_time: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -61,94 +58,89 @@ def edit_task_time(
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"task_time": task_time, "attributes": attributes} return usecase.execute(task_time, attributes)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
def execute(self, task_time: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
self.task_time = task_time
self.task = self.get_task() self.task = self.get_task()
self.calendar = ifcopenshell.util.sequence.derive_calendar(self.task) self.calendar = ifcopenshell.util.sequence.derive_calendar(self.task)
# If the user specifies both an end date and a duration, the duration takes priority # If the user specifies both an end date and a duration, the duration takes priority
if ( if attributes.get("ScheduleDuration", None) and "ScheduleFinish" in attributes.keys():
self.settings["attributes"].get("ScheduleDuration", None) del attributes["ScheduleFinish"]
and "ScheduleFinish" in self.settings["attributes"].keys()
):
del self.settings["attributes"]["ScheduleFinish"]
duration_type = self.settings["attributes"].get("DurationType", self.settings["task_time"].DurationType) duration_type = attributes.get("DurationType", self.task_time.DurationType)
finish = self.settings["attributes"].get("ScheduleFinish", None) finish = attributes.get("ScheduleFinish", None)
if finish: if finish:
if isinstance(finish, str): if isinstance(finish, str):
finish = datetime.datetime.fromisoformat(finish) finish = datetime.datetime.fromisoformat(finish)
self.settings["attributes"]["ScheduleFinish"] = datetime.datetime.combine( attributes["ScheduleFinish"] = datetime.datetime.combine(
ifcopenshell.util.sequence.get_soonest_working_day(finish, duration_type, self.calendar), ifcopenshell.util.sequence.get_soonest_working_day(finish, duration_type, self.calendar),
datetime.time(17), datetime.time(17),
) )
start = self.settings["attributes"].get("ScheduleStart", None) start = attributes.get("ScheduleStart", None)
if start: if start:
if isinstance(start, str): if isinstance(start, str):
start = datetime.datetime.fromisoformat(start) start = datetime.datetime.fromisoformat(start)
self.settings["attributes"]["ScheduleStart"] = datetime.datetime.combine( attributes["ScheduleStart"] = datetime.datetime.combine(
ifcopenshell.util.sequence.get_soonest_working_day(start, duration_type, self.calendar), ifcopenshell.util.sequence.get_soonest_working_day(start, duration_type, self.calendar),
datetime.time(9), datetime.time(9),
) )
for name, value in self.settings["attributes"].items(): for name, value in attributes.items():
if value is not None: if value is not None:
if "Start" in name or "Finish" in name or name == "StatusTime": if "Start" in name or "Finish" in name or name == "StatusTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
elif name == "ScheduleDuration" or name == "ActualDuration" or name == "RemainingTime": elif name == "ScheduleDuration" or name == "ActualDuration" or name == "RemainingTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
setattr(self.settings["task_time"], name, value) setattr(self.task_time, name, value)
if ( if "ScheduleDuration" in attributes.keys() and task_time.ScheduleDuration and task_time.ScheduleStart:
"ScheduleDuration" in self.settings["attributes"].keys()
and self.settings["task_time"].ScheduleDuration
and self.settings["task_time"].ScheduleStart
):
self.calculate_finish() self.calculate_finish()
elif self.settings["attributes"].get("ScheduleStart", None) and self.settings["task_time"].ScheduleDuration: elif attributes.get("ScheduleStart", None) and task_time.ScheduleDuration:
self.calculate_finish() self.calculate_finish()
elif self.settings["attributes"].get("ScheduleFinish", None) and self.settings["task_time"].ScheduleStart: elif attributes.get("ScheduleFinish", None) and task_time.ScheduleStart:
self.calculate_duration() self.calculate_duration()
if self.settings["task_time"].ScheduleDuration and ( if task_time.ScheduleDuration and (
"ScheduleStart" in self.settings["attributes"].keys() "ScheduleStart" in attributes.keys()
or "ScheduleFinish" in self.settings["attributes"].keys() or "ScheduleFinish" in attributes.keys()
or "ScheduleDuration" in self.settings["attributes"].keys() or "ScheduleDuration" in attributes.keys()
): ):
ifcopenshell.api.sequence.cascade_schedule(self.file, task=self.task) ifcopenshell.api.sequence.cascade_schedule(self.file, task=self.task)
if self.settings["task_time"].ScheduleDuration: if task_time.ScheduleDuration:
self.handle_resource_calculation() self.handle_resource_calculation()
def calculate_finish(self): def calculate_finish(self):
finish = ifcopenshell.util.sequence.get_start_or_finish_date( finish = ifcopenshell.util.sequence.get_start_or_finish_date(
ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleStart), ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleStart),
ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleDuration), ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleDuration),
self.settings["task_time"].DurationType, self.task_time.DurationType,
self.calendar, self.calendar,
date_type="FINISH", date_type="FINISH",
) )
self.settings["task_time"].ScheduleFinish = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime") self.task_time.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
def calculate_duration(self): def calculate_duration(self):
start = ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleStart) start = ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleStart)
finish = ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleFinish) finish = ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleFinish)
current_date = datetime.date(start.year, start.month, start.day) current_date = datetime.date(start.year, start.month, start.day)
finish_date = datetime.date(finish.year, finish.month, finish.day) finish_date = datetime.date(finish.year, finish.month, finish.day)
duration = datetime.timedelta(days=1) duration = datetime.timedelta(days=1)
while current_date < finish_date: while current_date < finish_date:
if self.settings["task_time"].DurationType == "ELAPSEDTIME" or not self.calendar: if self.task_time.DurationType == "ELAPSEDTIME" or not self.calendar:
duration += datetime.timedelta(days=1) duration += datetime.timedelta(days=1)
elif ifcopenshell.util.sequence.is_working_day(current_date, self.calendar): elif ifcopenshell.util.sequence.is_working_day(current_date, self.calendar):
duration += datetime.timedelta(days=1) duration += datetime.timedelta(days=1)
current_date += datetime.timedelta(days=1) current_date += datetime.timedelta(days=1)
self.settings["task_time"].ScheduleDuration = ifcopenshell.util.date.datetime2ifc(duration, "IfcDuration") self.task_time.ScheduleDuration = ifcopenshell.util.date.datetime2ifc(duration, "IfcDuration")
def get_task(self) -> ifcopenshell.entity_instance: def get_task(self) -> ifcopenshell.entity_instance:
return next(e for e in self.file.get_inverse(self.settings["task_time"]) if e.is_a("IfcTask")) return next(e for e in self.file.get_inverse(self.task_time) if e.is_a("IfcTask"))
def handle_resource_calculation(self): def handle_resource_calculation(self):
resources = ifcopenshell.util.sequence.get_task_resources(self.task, is_deep=False) resources = ifcopenshell.util.sequence.get_task_resources(self.task, is_deep=False)
@@ -28,11 +28,8 @@ def edit_work_calendar(
IfcWorkCalendar, consult the IFC documentation. IfcWorkCalendar, consult the IFC documentation.
:param work_calendar: The IfcWorkCalendar entity you want to edit :param work_calendar: The IfcWorkCalendar entity you want to edit
:type work_calendar: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -45,7 +42,5 @@ def edit_work_calendar(
ifcopenshell.api.sequence.edit_work_calendar(model, ifcopenshell.api.sequence.edit_work_calendar(model,
work_calendar=calendar, attributes={"Description": "Monday to Friday 8 hour days"}) work_calendar=calendar, attributes={"Description": "Monday to Friday 8 hour days"})
""" """
settings = {"work_calendar": work_calendar, "attributes": attributes} for name, value in attributes.items():
setattr(work_calendar, name, value)
for name, value in settings["attributes"].items():
setattr(settings["work_calendar"], name, value)
@@ -29,11 +29,8 @@ def edit_work_plan(
IfcWorkPlan, consult the IFC documentation. IfcWorkPlan, consult the IFC documentation.
:param work_plan: The IfcWorkPlan entity you want to edit :param work_plan: The IfcWorkPlan entity you want to edit
:type work_plan: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -46,12 +43,10 @@ def edit_work_plan(
ifcopenshell.api.sequence.edit_work_plan(model, ifcopenshell.api.sequence.edit_work_plan(model,
work_plan=work_plan, attributes={"Description": "Construction of phase 1"}) work_plan=work_plan, attributes={"Description": "Construction of phase 1"})
""" """
settings = {"work_plan": work_plan, "attributes": attributes} for name, value in attributes.items():
for name, value in settings["attributes"].items():
if value: if value:
if "Date" in name or "Time" in name: if "Date" in name or "Time" in name:
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
elif name == "Duration" or name == "TotalFloat": elif name == "Duration" or name == "TotalFloat":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
setattr(settings["work_plan"], name, value) setattr(work_plan, name, value)
@@ -29,11 +29,8 @@ def edit_work_schedule(
IfcWorkSchedule, consult the IFC documentation. IfcWorkSchedule, consult the IFC documentation.
:param work_schedule: The IfcWorkSchedule entity you want to edit :param work_schedule: The IfcWorkSchedule entity you want to edit
:type work_schedule: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -50,12 +47,10 @@ def edit_work_schedule(
ifcopenshell.api.sequence.edit_work_schedule(model, ifcopenshell.api.sequence.edit_work_schedule(model,
work_schedule=work_schedule, attributes={"Description": "3 crane design option"}) work_schedule=work_schedule, attributes={"Description": "3 crane design option"})
""" """
settings = {"work_schedule": work_schedule, "attributes": attributes} for name, value in attributes.items():
for name, value in settings["attributes"].items():
if value: if value:
if "Date" in name or "Time" in name: if "Date" in name or "Time" in name:
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
elif name == "Duration" or name == "TotalFloat": elif name == "Duration" or name == "TotalFloat":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
setattr(settings["work_schedule"], name, value) setattr(work_schedule, name, value)
@@ -31,11 +31,8 @@ def edit_work_time(
IfcWorkTime, consult the IFC documentation. IfcWorkTime, consult the IFC documentation.
:param work_time: The IfcWorkTime entity you want to edit :param work_time: The IfcWorkTime entity you want to edit
:type work_time: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
Example: Example:
@@ -54,16 +51,14 @@ def edit_work_time(
ifcopenshell.api.sequence.edit_work_time(model, ifcopenshell.api.sequence.edit_work_time(model,
work_time=work_time, attributes={"StartDate": "2000-01-01", "FinishDate": "2000-01-02"}) work_time=work_time, attributes={"StartDate": "2000-01-01", "FinishDate": "2000-01-02"})
""" """
settings = {"work_time": work_time, "attributes": attributes} for name, value in attributes.items():
for name, value in settings["attributes"].items():
if name in ("Start", "StartDate"): if name in ("Start", "StartDate"):
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate")
# 4 IfcWorktime Start # 4 IfcWorktime Start
settings["work_time"][4] = value work_time[4] = value
elif name in ("Finish", "FinishDate"): elif name in ("Finish", "FinishDate"):
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate")
# 5 IfcWorktime Finish # 5 IfcWorktime Finish
settings["work_time"][5] = value work_time[5] = value
else: else:
setattr(settings["work_time"], name, value) setattr(work_time, name, value)
@@ -35,9 +35,7 @@ def recalculate_schedule(file: ifcopenshell.file, work_schedule: ifcopenshell.en
error. error.
:param work_schedule: The IfcWorkSchedule to perform the calculation on. :param work_schedule: The IfcWorkSchedule to perform the calculation on.
:type work_schedule: ifcopenshell.entity_instance
:return: None :return: None
:rtype: None
Example: Example:
@@ -50,12 +48,14 @@ def recalculate_schedule(file: ifcopenshell.file, work_schedule: ifcopenshell.en
""" """
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = {"work_schedule": work_schedule} return usecase.execute(work_schedule)
return usecase.execute()
class Usecase: class Usecase:
def execute(self): file: ifcopenshell.file
def execute(self, work_schedule: ifcopenshell.entity_instance) -> None:
self.work_schedule = work_schedule
# The method implemented is the same as shown here: # The method implemented is the same as shown here:
# https://www.youtube.com/watch?v=qTErIV6OqLg # https://www.youtube.com/watch?v=qTErIV6OqLg
self.start_dates = [] self.start_dates = []
@@ -88,7 +88,6 @@ class Usecase:
if is_cyclic: if is_cyclic:
raise RecursionError("Task graph is cyclic and so critical path method cannot be performed.") raise RecursionError("Task graph is cyclic and so critical path method cannot be performed.")
return
self.pending_nodes = set(self.g.nodes) self.pending_nodes = set(self.g.nodes)
while self.pending_nodes: while self.pending_nodes:
@@ -100,7 +99,7 @@ class Usecase:
self.update_task_times() self.update_task_times()
def build_network_graph(self): def build_network_graph(self) -> None:
self.sequence_type_map = { self.sequence_type_map = {
None: "FS", None: "FS",
"START_START": "SS", "START_START": "SS",
@@ -114,14 +113,14 @@ class Usecase:
self.edges = [] self.edges = []
self.g.add_node("start", duration=0, duration_type="ELAPSEDTIME", calendar=None) self.g.add_node("start", duration=0, duration_type="ELAPSEDTIME", calendar=None)
self.g.add_node("finish", duration=0, duration_type="ELAPSEDTIME", calendar=None) self.g.add_node("finish", duration=0, duration_type="ELAPSEDTIME", calendar=None)
for rel in self.settings["work_schedule"].Controls: for rel in self.work_schedule.Controls:
for related_object in rel.RelatedObjects: for related_object in rel.RelatedObjects:
if not related_object.is_a("IfcTask"): if not related_object.is_a("IfcTask"):
continue continue
self.add_node(related_object) self.add_node(related_object)
self.g.add_edges_from(self.edges) self.g.add_edges_from(self.edges)
def add_node(self, task): def add_node(self, task: ifcopenshell.entity_instance) -> None:
if task.IsNestedBy: if task.IsNestedBy:
for rel in task.IsNestedBy: for rel in task.IsNestedBy:
[self.add_node(o) for o in rel.RelatedObjects] [self.add_node(o) for o in rel.RelatedObjects]
@@ -176,7 +175,7 @@ class Usecase:
if not successor_types: if not successor_types:
self.edges.append((task.id(), "finish", {"lag_time": 0, "type": "FF"})) self.edges.append((task.id(), "finish", {"lag_time": 0, "type": "FF"}))
def update_task_times(self): def update_task_times(self) -> None:
for ifc_definition_id in self.g.nodes: for ifc_definition_id in self.g.nodes:
if ifc_definition_id in ("start", "finish"): if ifc_definition_id in ("start", "finish"):
continue continue
@@ -198,12 +197,12 @@ class Usecase:
}, },
) )
def offset_date(self, date, days, node): def offset_date(self, date: datetime.datetime, days: int, node: dict) -> datetime.datetime:
return ifcopenshell.util.sequence.offset_date( return ifcopenshell.util.sequence.offset_date(
date, datetime.timedelta(days=days), node["duration_type"], node["calendar"] date, datetime.timedelta(days=days), node["duration_type"], node["calendar"]
) )
def forward_pass(self, node): def forward_pass(self, node) -> bool:
successors = self.g.successors(node) successors = self.g.successors(node)
predecessors = list(self.g.predecessors(node)) predecessors = list(self.g.predecessors(node))
data = self.g.nodes[node] data = self.g.nodes[node]
@@ -326,7 +325,7 @@ class Usecase:
return True return True
def backward_pass(self, node): def backward_pass(self, node) -> bool:
successors = list(self.g.successors(node)) successors = list(self.g.successors(node))
predecessors = self.g.predecessors(node) predecessors = self.g.predecessors(node)
data = self.g.nodes[node] data = self.g.nodes[node]
@@ -496,12 +495,12 @@ class Usecase:
def calculate_free_float( def calculate_free_float(
self, self,
predecessor_date, predecessor_date: datetime.datetime,
successor_date, successor_date: datetime.datetime,
lag_time, lag_time: int,
predecessor_data, predecessor_data: dict,
successor_data, successor_data: dict,
): ) -> datetime.timedelta:
if not lag_time: if not lag_time:
min_successor_date = successor_date min_successor_date = successor_date
else: else:
@@ -29,12 +29,10 @@ def dereference_structure(
"""Dereferences a list of products and space """Dereferences a list of products and space
:param products: The list of physical IfcElements that exists in the space. :param products: The list of physical IfcElements that exists in the space.
:type products: list[ifcopenshell.entity_instance]
:param relating_structure: The IfcSpatialStructureElement element, such :param relating_structure: The IfcSpatialStructureElement element, such
as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element
exists in. exists in.
:return: None :return: None
:rtype: None
Example: Example:
@@ -68,14 +66,12 @@ def dereference_structure(
# Actually, it only goes up to storey 2. # Actually, it only goes up to storey 2.
ifcopenshell.api.spatial.dereference_structure(model, products=[column], relating_structure=storey3) ifcopenshell.api.spatial.dereference_structure(model, products=[column], relating_structure=storey3)
""" """
settings = {"products": products, "relating_structure": relating_structure} products_set = set(products)
for rel in relating_structure.ReferencesElements:
products = set(settings["products"])
for rel in settings["relating_structure"].ReferencesElements:
related_elements = set(rel.RelatedElements) related_elements = set(rel.RelatedElements)
if not related_elements.intersection(products): if not related_elements.intersection(products_set):
continue continue
related_elements = related_elements - products related_elements = related_elements - products_set
if related_elements: if related_elements:
rel.RelatedElements = list(related_elements) rel.RelatedElements = list(related_elements)
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel}) ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
@@ -46,14 +46,11 @@ def reference_structure(
spaces simultaneously. spaces simultaneously.
:param products: The list of physical IfcElements that exists in the space. :param products: The list of physical IfcElements that exists in the space.
:type products: list[ifcopenshell.entity_instance]
:param relating_structure: The IfcSpatialStructureElement element, such :param relating_structure: The IfcSpatialStructureElement element, such
as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element
exists in. exists in.
:type relating_structure: ifcopenshell.entity_instance
:return: The IfcRelReferencedInSpatialStructure relationship instance :return: The IfcRelReferencedInSpatialStructure relationship instance
or `None` if `products` was an empty list. or `None` if `products` was an empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example: Example:
@@ -85,19 +82,16 @@ def reference_structure(
model, products=[column], relating_structure=[storey2, storey3] model, products=[column], relating_structure=[storey2, storey3]
) )
""" """
settings = {
"products": products,
"relating_structure": relating_structure,
}
structure = settings["relating_structure"] structure = relating_structure
products = set(settings["products"]) products_set = set(products)
if not products: if not products_set:
return return
referenced = ifcopenshell.util.element.get_structure_referenced_elements(structure) referenced = ifcopenshell.util.element.get_structure_referenced_elements(structure)
products_to_assign = products - referenced products_to_assign = products_set - referenced
rel: Union[ifcopenshell.entity_instance, None]
rel = next(iter(structure.ReferencesElements), None) rel = next(iter(structure.ReferencesElements), None)
if not products_to_assign: if not products_to_assign:
@@ -28,14 +28,8 @@ def edit_structural_analysis_model(
IfcStructuralAnalysisModel, consult the IFC documentation. IfcStructuralAnalysisModel, consult the IFC documentation.
:param structural_analysis_model: The IfcStructuralAnalysisModel entity you want to edit :param structural_analysis_model: The IfcStructuralAnalysisModel entity you want to edit
:type structural_analysis_model: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
""" """
settings = {"structural_analysis_model": structural_analysis_model, "attributes": attributes or {}} for name, value in attributes.items():
setattr(structural_analysis_model, name, value)
for name, value in settings["attributes"].items():
setattr(settings["structural_analysis_model"], name, value)
return settings["structural_analysis_model"]
@@ -28,19 +28,14 @@ def edit_structural_boundary_condition(
IfcBoundaryCondition, consult the IFC documentation. IfcBoundaryCondition, consult the IFC documentation.
:param condition: The IfcBoundaryCondition entity you want to edit :param condition: The IfcBoundaryCondition entity you want to edit
:type condition: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
""" """
settings = {"condition": condition, "attributes": attributes} for name, data in attributes.items():
for name, data in settings["attributes"].items():
if data["type"] == "string" or data["type"] == "null": if data["type"] == "string" or data["type"] == "null":
value = data["value"] value = data["value"]
elif data["type"] == "IfcBoolean": elif data["type"] == "IfcBoolean":
value = file.createIfcBoolean(data["value"]) value = file.createIfcBoolean(data["value"])
else: else:
value = file.create_entity(data["type"], data["value"]) value = file.create_entity(data["type"], data["value"])
setattr(settings["condition"], name, value) setattr(condition, name, value)
@@ -16,25 +16,21 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
from ifcopenshell.util.shape_builder import VectorType, ifc_safe_vector_type
def edit_structural_item_axis( def edit_structural_item_axis(
file: ifcopenshell.file, file: ifcopenshell.file,
structural_item: ifcopenshell.entity_instance, structural_item: ifcopenshell.entity_instance,
axis: tuple[float, float, float] = (0.0, 0.0, 1.0), axis: VectorType = (0.0, 0.0, 1.0),
) -> None: ) -> None:
"""Edits the coordinate system of a structural connection """Edits the coordinate system of a structural connection
:param structural_item: The IfcStructuralItem you want to modify. :param structural_item: The IfcStructuralItem you want to modify.
:type structural_item: ifcopenshell.entity_instance
:param axis: The unit Z axis vector defined as a list of 3 floats. :param axis: The unit Z axis vector defined as a list of 3 floats.
Defaults to (0., 0., 1.). Defaults to (0., 0., 1.).
:type axis: tuple[float, float, float]
:return: None :return: None
:rtype: None
""" """
settings = {"structural_item": structural_item, "axis": axis} if len(file.get_inverse(axis_dir := structural_item.Axis)) == 1:
file.remove(axis_dir)
if len(file.get_inverse(settings["structural_item"].Axis)) == 1: structural_item.Axis = file.create_entity("IfcDirection", ifc_safe_vector_type(axis))
file.remove(settings["structural_item"].Axis)
settings["structural_item"].Axis = file.createIfcDirection(settings["axis"])
@@ -28,13 +28,8 @@ def edit_structural_load(
IfcStructuralLoad, consult the IFC documentation. IfcStructuralLoad, consult the IFC documentation.
:param structural_load: The IfcStructuralLoad entity you want to edit :param structural_load: The IfcStructuralLoad entity you want to edit
:type structural_load: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
""" """
settings = {"structural_load": structural_load, "attributes": attributes or {}} for name, value in attributes.items():
setattr(structural_load, name, value)
for name, value in settings["attributes"].items():
setattr(settings["structural_load"], name, value)
@@ -28,13 +28,8 @@ def edit_structural_load_case(
IfcStructuralLoadCase, consult the IFC documentation. IfcStructuralLoadCase, consult the IFC documentation.
:param load_case: The IfcStructuralLoadCase entity you want to edit :param load_case: The IfcStructuralLoadCase entity you want to edit
:type load_case: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict
:return: None :return: None
:rtype: None
""" """
settings = {"load_case": load_case, "attributes": attributes or {}} for name, value in attributes.items():
setattr(load_case, name, value)
for name, value in settings["attributes"].items():
setattr(settings["load_case"], name, value)
@@ -82,17 +82,13 @@ def add_surface_style(
:param style: The IfcSurfaceStyle you want to add to presentation item :param style: The IfcSurfaceStyle you want to add to presentation item
to. See ifcopenshell.api.style.add_style. to. See ifcopenshell.api.style.add_style.
:type style: ifcopenshell.entity_instance
:param ifc_class: Choose from IfcSurfaceStyleShading, :param ifc_class: Choose from IfcSurfaceStyleShading,
IfcSurfaceStyleRendering, IfcSurfaceStyleWithTextures, IfcSurfaceStyleRendering, IfcSurfaceStyleWithTextures,
IfcSurfaceStyleLighting, IfcSurfaceStyleReflectance, or IfcSurfaceStyleLighting, IfcSurfaceStyleReflectance, or
IfcExternallyDefinedSurfaceStyle. IfcExternallyDefinedSurfaceStyle.
:type ifc_class: str
:param attributes: a dictionary of attribute names and values. :param attributes: a dictionary of attribute names and values.
:type attributes: dict, optional
:return: The newly created presentation item based on the provided :return: The newly created presentation item based on the provided
ifc_class. ifc_class.
:rtype: ifcopenshell.entity_instance
Example: Example:
@@ -19,6 +19,7 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api.style import ifcopenshell.api.style
import ifcopenshell.util.element import ifcopenshell.util.element
from typing import Any
def assign_material_style( def assign_material_style(
@@ -115,6 +116,9 @@ def assign_material_style(
class Usecase: class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self): def execute(self):
self.style = self.settings["style"] self.style = self.settings["style"]
if self.file.schema == "IFC2X3" or self.settings["should_use_presentation_style_assignment"]: if self.file.schema == "IFC2X3" or self.settings["should_use_presentation_style_assignment"]:

Some files were not shown because too many files have changed in this diff Show More