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