Merge remote-tracking branch 'origin/v0.7.0' into v08attempt1

This commit is contained in:
Thomas Krijnen
2024-04-18 21:39:33 +02:00
86 changed files with 1817 additions and 631 deletions
@@ -87,6 +87,36 @@ ARGUMENTS_DEPRECATION = {
"material.unassign_material": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"classification.add_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"classification.remove_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"library.assign_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"library.unassign_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"document.assign_document": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"document.unassign_document": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"spatial.reference_structure": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"spatial.dereference_structure": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"constraint.assign_constraint": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"constraint.unassign_constraint": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
}
@@ -64,7 +64,8 @@ class Usecase:
self.settings["product"].PredefinedType = "USERDEFINED"
elif hasattr(self.settings["product"], "ObjectType"):
relating_type = ifcopenshell.util.element.get_type(self.settings["product"])
if relating_type and relating_type.PredefinedType != "NOTDEFINED":
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
if relating_type and relating_type.PredefinedType not in ("NOTDEFINED", None):
self.settings["product"].ObjectType = None
self.settings["product"].PredefinedType = None
elif (
@@ -19,10 +19,11 @@
import ifcopenshell
import ifcopenshell.util.schema
import ifcopenshell.util.date
from typing import Union
class Usecase:
def __init__(self, file, classification=None):
def __init__(self, file: ifcopenshell.file, classification: Union[str, ifcopenshell.entity_instance]):
"""Adds a new classification system to the project
External classification systems such as Uniclass or Omniclass are
@@ -81,7 +82,7 @@ class Usecase:
"classification": classification,
}
def execute(self):
def execute(self) -> ifcopenshell.entity_instance:
if isinstance(self.settings["classification"], str):
classification = self.file.createIfcClassification(Name=self.settings["classification"])
self.relate_to_project(classification)
@@ -17,12 +17,24 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.schema
from typing import Optional, Union
class Usecase:
def __init__(self, file, product=None, reference=None, identification=None, name=None, classification=None, is_lightweight=True):
"""Adds a new classification reference and assigns it to a product
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
reference: Optional[ifcopenshell.entity_instance] = None,
identification: Optional[str] = None,
name: Optional[str] = None,
classification: Optional[ifcopenshell.entity_instance] = None,
is_lightweight=True,
):
"""Adds a new classification reference and assigns it to the list of products
A classification reference is a single entry such as "Pr_12_23_34" that
is part of an external classification system (such as Uniclass or
@@ -33,7 +45,7 @@ class Usecase:
resources such as profiles, documents, libraries, and so on.
Classification references can be added in two ways. Option 1) specify a
custom arbitrary reference, where you have the manually specify the
custom arbitrary reference, where you have to manually specify the
identification (e.g. "Pr_12_23_45") and name (e.g. "Door Products").
Option 2) add a reference from an IFC classification library. The latter
is preferred if you are using a common classification system such as
@@ -52,13 +64,13 @@ class Usecase:
assigned to both the type and an occurrence, then the assignment at the
occurrence will override the type classification.
:param product: The IFC object, property, or resource you want to
:param product: The list of IFC objects, properties, or resources you want to
associate the classification reference to.
:type product: ifcopenshell.entity_instance.entity_instance
:type product: list[ifcopenshell.entity_instance.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 product: ifcopenshell.entity_instance.entity_instance, optional
:type reference: ifcopenshell.entity_instance.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
@@ -70,7 +82,7 @@ class Usecase:
: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 product: ifcopenshell.entity_instance.entity_instance
:type classification: ifcopenshell.entity_instance.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
@@ -81,8 +93,12 @@ class Usecase:
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
:rtype: ifcopenshell.entity_instance.entity_instance
or `None` if `products` was empty list.
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
Example:
@@ -93,7 +109,7 @@ class Usecase:
classification = ifcopenshell.api.run("classification.add_classification",
model, classification="MyCustomClassification")
ifcopenshell.api.run("classification.add_reference", model,
product=wall_type, classification=classification,
products=[wall_type], classification=classification,
identification="W_01", name="Interior Walls")
# Option 2: adding a popular classification from a library
@@ -104,12 +120,12 @@ class Usecase:
reference = [r for r in library.by_type("IfcClassificationReference")
if r.Identification == "XYZ"][0]
ifcopenshell.api.run("classification.add_reference", model,
product=wall_type, classification=classification,
products=[wall_type], classification=classification,
reference=reference)
"""
self.file = file
self.settings = {
"product": product,
"products": products,
"reference": reference,
"identification": identification,
"name": name,
@@ -117,8 +133,27 @@ class Usecase:
"is_lightweight": is_lightweight,
}
def execute(self):
self.is_rooted = self.settings["product"].is_a("IfcRoot")
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
if not self.settings["products"]:
return
if self.settings["reference"]:
referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
if set(self.settings["products"]).issubset(referenced):
# nothing to do, all elements already have this reference assigned
return self.settings["reference"]
self.rooted_products: set[ifcopenshell.entity_instance] = set()
self.non_rooted_products: set[ifcopenshell.entity_instance] = set()
for product in self.settings["products"]:
if product.is_a("IfcRoot"):
self.rooted_products.add(product)
else:
self.non_rooted_products.add(product)
if self.non_rooted_products and self.file.schema == "IFC2X3":
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {self.non_rooted_products}.")
if self.settings["reference"]:
return self.add_from_library()
return self.add_from_identification()
@@ -134,14 +169,10 @@ class Usecase:
else:
reference.Identification = self.settings["identification"]
relationship = self.get_existing_relationship(reference)
if relationship:
self.add_to_existing_relationship(relationship)
else:
self.add_new_relationship(reference)
self.update_relationships(reference)
return reference
def add_from_library(self):
def add_from_library(self) -> ifcopenshell.entity_instance:
if hasattr(self.settings["reference"], "ItemReference"):
identification = self.settings["reference"].ItemReference # IFC2X3
else:
@@ -155,8 +186,9 @@ class Usecase:
old_referenced_source = self.settings["reference"].ReferencedSource
self.settings["reference"].ReferencedSource = None
else:
classification_name = self.settings["classification"].Name
existing_classification = [
c for c in self.file.by_type("IfcClassification") if c.Name == self.settings["classification"].Name
c for c in self.file.by_type("IfcClassification") if c.Name == classification_name
]
reference = migrator.migrate(self.settings["reference"], self.file)
@@ -174,15 +206,10 @@ class Usecase:
for element in to_delete:
self.file.remove(element)
relationship = self.get_existing_relationship(reference)
if relationship:
self.add_to_existing_relationship(relationship)
else:
self.add_new_relationship(reference)
self.update_relationships(reference)
return reference
def get_existing_reference(self, identification):
def get_existing_reference(self, identification: Optional[str] = None) -> Union[ifcopenshell.entity_instance, None]:
for reference in self.file.by_type("IfcClassificationReference"):
if self.file.schema == "IFC2X3":
if reference.ItemReference == identification:
@@ -191,39 +218,39 @@ class Usecase:
if reference.Identification == identification:
return reference
def add_new_relationship(self, reference):
if self.is_rooted:
self.file.create_entity(
"IfcRelAssociatesClassification",
GlobalId=ifcopenshell.guid.new(),
RelatedObjects=[self.settings["product"]],
RelatingClassification=reference,
)
else:
self.file.create_entity(
"IfcExternalReferenceRelationship",
RelatingReference=reference,
RelatedResourceObjects=[self.settings["product"]],
)
def add_to_existing_relationship(self, rel):
if self.is_rooted:
related_objects = set(rel.RelatedObjects)
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
else:
related_objects = set(rel.RelatedResourceObjects)
related_objects.add(self.settings["product"])
rel.RelatedResourceObjects = list(related_objects)
def get_existing_relationship(self, reference):
if self.is_rooted:
def update_relationships(self, reference: ifcopenshell.entity_instance) -> None:
root_rel, non_root_rel = None, None
if self.rooted_products:
if self.file.schema == "IFC2X3":
for rel in self.file.by_type("IfcRelAssociatesClassification"):
if rel.RelatingClassification == reference:
return rel
elif reference.ClassificationRefForObjects:
return reference.ClassificationRefForObjects[0]
elif self.file.schema != "IFC2X3":
if reference.ExternalReferenceForResources:
return reference.ExternalReferenceForResources[0]
root_rel = rel
break
else:
root_rel = next(iter(reference.ClassificationRefForObjects), None)
if root_rel:
related_objects = set(root_rel.RelatedObjects) | self.rooted_products
root_rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": root_rel})
else:
self.file.create_entity(
"IfcRelAssociatesClassification",
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
GlobalId=ifcopenshell.guid.new(),
RelatedObjects=list(self.rooted_products),
RelatingClassification=reference,
)
if self.non_rooted_products:
# NOTE: Only Ifc4+. Ifc2x3 is already handled by raising TypeError
non_root_rel = next(iter(reference.ExternalReferenceForResources), None)
if non_root_rel:
related_objects = set(non_root_rel.RelatedResourceObjects) | self.non_rooted_products
non_root_rel.RelatedResourceObjects = list(related_objects)
else:
self.file.create_entity(
"IfcExternalReferenceRelationship",
RelatingReference=reference,
RelatedResourceObjects=list(self.non_rooted_products),
)
@@ -17,12 +17,18 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, reference=None, product=None):
"""Removes a classification reference from a product
def __init__(
self,
file: ifcopenshell.file,
reference: ifcopenshell.entity_instance,
products: list[ifcopenshell.entity_instance],
):
"""Removes a classification reference from the list of products
If the classification reference is no longer associated to any products,
the classification reference itself is also removed.
@@ -30,9 +36,12 @@ class Usecase:
:param reference: The IfcClassificationReference entity of the
relationship you want to remove.
:type reference: ifcopenshell.entity_instance.entity_instance
:param product: The object entity of the relationship you want to
:param product: The list fo object entities of the relationship you want to
remove.
:type reference: ifcopenshell.entity_instance.entity_instance
:type product: list[ifcopenshell.entity_instance.entity_instance]
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
:return: None
:rtype: None
@@ -44,42 +53,75 @@ class Usecase:
classification = ifcopenshell.api.run("classification.add_classification",
model, classification="MyCustomClassification")
reference = ifcopenshell.api.run("classification.add_reference", model,
product=wall_type, classification=classification,
products=[wall_type], classification=classification,
identification="W_01", name="Interior Walls")
ifcopenshell.api.run("classification.remove_reference", model,
reference=reference, product=wall_type)
reference=reference, products=[wall_type])
"""
self.file = file
self.settings = {"reference": reference, "product": product}
self.settings = {"reference": reference, "products": products}
def execute(self):
if self.settings["product"].is_a("IfcRoot"):
for rel in self.file.by_type("IfcRelAssociatesClassification"):
if rel.RelatingClassification == self.settings["reference"] and rel.RelatedObjects:
if self.settings["product"] in rel.RelatedObjects:
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["product"])
if len(related_objects):
rel.RelatedObjects = related_objects
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
else:
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
if rel.RelatingReference == self.settings["reference"] and rel.RelatedResourceObjects:
if self.settings["product"] in rel.RelatedResourceObjects:
related_objects = list(rel.RelatedResourceObjects)
related_objects.remove(self.settings["product"])
if len(related_objects):
rel.RelatedResourceObjects = related_objects
else:
self.file.remove(rel)
def execute(self) -> None:
is_ifc2x3 = self.file.schema == "IFC2X3"
products = set(self.settings["products"])
referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
products -= products.difference(referenced)
# all products are already unassigned from a reference
if not products:
return
rooted_products: set[ifcopenshell.entity_instance] = set()
non_rooted_products: set[ifcopenshell.entity_instance] = set()
for product in self.settings["products"]:
if product.is_a("IfcRoot"):
rooted_products.add(product)
else:
non_rooted_products.add(product)
if non_rooted_products and is_ifc2x3:
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
if rooted_products:
reference_rels: set[ifcopenshell.entity_instance] = set()
for product in rooted_products:
reference_rels.update(product.HasAssociations)
reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesClassification")
and rel.RelatingClassification == self.settings["reference"]
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - rooted_products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
if non_rooted_products:
reference_rels: set[ifcopenshell.entity_instance] = set()
for product in non_rooted_products:
rels = getattr(product, "HasExternalReferences", None)
if rels is None:
rels = getattr(product, "HasExternalReference", [])
reference_rels.update(rels)
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == self.settings["reference"]}
for rel in reference_rels:
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
if related_objects:
rel.RelatedResourceObjects = list(related_objects)
else:
self.file.remove(rel)
# TODO: we only handle lightweight classifications here
if (
not self.settings["reference"].ClassificationRefForObjects
and not self.settings["reference"].ExternalReferenceForResources
):
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
if not referenced_elements:
self.file.remove(self.settings["reference"])
@@ -17,11 +17,18 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
from typing import Union
class Usecase:
def __init__(self, file, product=None, constraint=None):
"""Assigns a constraint to a product
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
constraint: ifcopenshell.entity_instance,
):
"""Assigns a constraint to a list of products
This assigns a relationship between a product and a constraint, so that
when a product's properties and quantities do not match the requirements
@@ -31,36 +38,58 @@ class Usecase:
constraints are inherited from the type. This way, it is not necessary
to create lots of constraint assignments.
:param product: The product the constraint applies to. This is anything
:param products: The list of products the constraint applies to. This is anything
which can have properties or quantities.
:type product: ifcopenshell.entity_instance.entity_instance
:type products: list[ifcopenshell.entity_instance.entity_instance]
:param constraint: The IfcObjective constraint
:type constraint: ifcopenshell.entity_instance.entity_instance
:return: The new or updated IfcRelAssociatesConstraint relationship
or `None` if `products` was an empty list.
:rtype: ifcopenshell.entity_instance.entity_instance
"""
self.file = file
self.settings = {
"product": product,
"products": products,
"constraint": constraint,
}
def execute(self):
rel = self.get_constraint_rel()
related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set()
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
return rel
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
products = set(self.settings["products"])
if not products:
return
self.constraint = self.settings["constraint"]
rels = self.get_constraint_rels()
related_objects = set()
for rel in rels:
related_objects.update(rel.RelatedObjects)
products_to_assign = products - related_objects
if not products_to_assign:
return rels[0]
rel = next(iter(rels), None)
if rel:
related_objects = set(rel.RelatedObjects) | products_to_assign
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
def get_constraint_rel(self):
for rel in self.file.by_type("IfcRelAssociatesConstraint"):
if rel.RelatingConstraint == self.settings["constraint"]:
return rel
return self.file.create_entity(
"IfcRelAssociatesConstraint",
**{
"GlobalId": ifcopenshell.guid.new(),
# TODO: owner history
"RelatingConstraint": self.settings["constraint"],
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingConstraint": self.constraint,
"RelatedObjects": list(products_to_assign),
}
)
def get_constraint_rels(self) -> list[ifcopenshell.entity_instance]:
rels = []
for rel in self.file.get_inverse(self.constraint):
if rel.is_a("IfcRelAssociatesConstraint"):
rels.append(rel)
return rels
@@ -17,18 +17,24 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, product=None, constraint=None):
"""Unassigns a constraint to a product
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
constraint: ifcopenshell.entity_instance,
):
"""Unassigns a constraint from a list of products
The constraint will not be deleted and is available to be assigned to
other products.
:param product: The product the constraint applies to.
:type product: ifcopenshell.entity_instance.entity_instance
:param products: The list of products the constraint applies to.
:type products: list[ifcopenshell.entity_instance.entity_instance]
:param constraint: The IfcObjective constraint
:type constraint: ifcopenshell.entity_instance.entity_instance
:return: None
@@ -36,14 +42,42 @@ class Usecase:
"""
self.file = file
self.settings = {
"product": product,
"products": products,
"constraint": constraint,
}
def execute(self):
for rel in self.settings["product"].HasAssociations:
if rel.is_a("IfcRelAssociatesConstraint") and rel.RelatingConstraint == self.settings["constraint"]:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
products = set(self.settings["products"])
if not products:
return
self.constraint = self.settings["constraint"]
rels = self.get_constraint_rels()
related_objects = set()
for rel in rels:
related_objects.update(rel.RelatedObjects)
if not related_objects.intersection(products):
return
for rel in rels:
related_objects = set(rel.RelatedObjects)
if not related_objects.intersection(products):
continue
related_objects -= products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
continue
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
def get_constraint_rels(self) -> list[ifcopenshell.entity_instance]:
rels = []
for rel in self.file.get_inverse(self.constraint):
if rel.is_a("IfcRelAssociatesConstraint"):
rels.append(rel)
return rels
@@ -69,10 +69,12 @@ class Usecase:
def execute(self) -> ifcopenshell.entity_instance:
if self.file.schema == "IFC2X3":
reference = self.file.create_entity("IfcDocumentReference")
reference = self.file.create_entity("IfcDocumentReference", ItemReference="X")
if self.settings["information"]:
references = list(self.settings["information"].DocumentReferences or [])
references.append(reference)
self.settings["information"].DocumentReferences = references
return reference
return self.file.create_entity("IfcDocumentReference", ReferencedDocument=self.settings["information"])
return self.file.create_entity(
"IfcDocumentReference", ReferencedDocument=self.settings["information"], Identification="X"
)
@@ -17,11 +17,19 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from typing import Union
class Usecase:
def __init__(self, file, product=None, document=None):
"""Assigns a document to a product
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
document: ifcopenshell.entity_instance,
):
"""Assigns a document to a list of products
An object may be assigned to zero, one, or multiple documents. Almost
any object or property may be assigned to a document, though typically
@@ -32,14 +40,16 @@ class Usecase:
consistent with other external relationships (such as classification
systems or libraries).
:param product: The object to associate the document to. This could be
:param product: The list of objects to associate the document to. This could be
almost any sensible object in IFC.
:type product: ifcopenshell.entity_instance.entity_instance
:type product: list[ifcopenshell.entity_instance.entity_instance]
:param document: The IfcDocumentReference to associate to, or
alternatively an IfcDocumentInformation, though this is not
recommended.
:type document: ifcopenshell.entity_instance.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.entity_instance
Example:
@@ -54,42 +64,51 @@ class Usecase:
reference = ifcopenshell.api.run("document.add_reference", model, information=document)
# Let's imagine storey represents an IfcBuildingStorey for the ground floor
ifcopenshell.api.run("document.assign_document", model, product=storey, document=reference)
ifcopenshell.api.run("document.assign_document", model, products=[storey], document=reference)
"""
self.file = file
self.settings = {
"product": product,
"products": products,
"document": document,
}
def execute(self):
rel = self.get_document_rel()
related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set()
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
# 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(self.settings["document"])
products: set[ifcopenshell.entity_instance] = set(self.settings["products"])
products = products - referenced_elements
if not products:
return
def get_document_rel(self):
if self.file.schema == "IFC2X3":
for rel in self.file.by_type("IfcRelAssociatesDocument"):
if rel.RelatingDocument == self.settings["document"]:
return rel
rel = next(
(
r
for r in self.file.by_type("IfcRelAssociatesDocument")
if r.RelatingDocument == self.settings["document"]
),
None,
)
else:
if (
hasattr(self.settings["document"], "DocumentRefForObjects")
and self.settings["document"].DocumentRefForObjects
):
return self.settings["document"].DocumentRefForObjects[0]
elif (
hasattr(self.settings["document"], "DocumentInfoForObjects")
and self.settings["document"].DocumentInfoForObjects
):
return self.settings["document"].DocumentInfoForObjects[0]
ifc_class = self.settings["document"].is_a()
if ifc_class == "IfcDocumentReference":
rel = next(iter(self.settings["document"].DocumentRefForObjects), None)
elif ifc_class == "IfcDocumentInformation":
rel = next(iter(self.settings["document"].DocumentInfoForObjects), None)
return self.file.create_entity(
"IfcRelAssociatesDocument",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingDocument": self.settings["document"],
}
)
if not rel:
return self.file.create_entity(
"IfcRelAssociatesDocument",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
RelatedObjects=list(products),
RelatingDocument=self.settings["document"],
)
related_objects = set(rel.RelatedObjects) | products
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel)
return rel
@@ -17,16 +17,22 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, product=None, document=None):
"""Unassigns a document and a product association
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
document: ifcopenshell.entity_instance,
):
"""Unassigns a document and an association to the list of products
:param product: The object that the document reference or information is
:param product: The list of objects that the document reference or information is
related to.
:type product: ifcopenshell.entity_instance.entity_instance
:type product: list[ifcopenshell.entity_instance.entity_instance]
:param document: The IfcDocumentReference (typically) or in rare cases
the IfcDocumentInformation that is associated with the product
:type document: ifcopenshell.entity_instance.entity_instance
@@ -45,24 +51,39 @@ class Usecase:
reference = ifcopenshell.api.run("document.add_reference", model, information=document)
# Let's imagine storey represents an IfcBuildingStorey for the ground floor
ifcopenshell.api.run("document.assign_document", model, product=storey, document=reference)
ifcopenshell.api.run("document.assign_document", model, products=[storey], document=reference)
# Now let's change our mind and remove the association
ifcopenshell.api.run("document.unassign_document", model, product=storey, document=reference)
ifcopenshell.api.run("document.unassign_document", model, products=[storey], document=reference)
"""
self.file = file
self.settings = {
"product": product,
"products": products,
"document": document,
}
def execute(self):
for rel in self.settings["product"].HasAssociations:
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == self.settings["document"]:
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
else:
rel.RelatedObjects = [o for o in rel.RelatedObjects if o != self.settings["product"]]
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
# NOTE: reuses code from `library.un assign_reference`
reference_rels: set[ifcopenshell.entity_instance] = set()
products = set(self.settings["products"])
for product in products:
reference_rels.update(product.HasAssociations)
reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == self.settings["document"]
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
@@ -26,7 +26,7 @@ class Usecase:
surveyor, and a third-party digital engineer with expertise in IFC to
moderate. For more information, read the BlenderBIM Add-on documentation
for Georeferencing:
https://blenderbim.org/docs/users/georeferencing.html
https://docs.blenderbim.org/users/georeferencing.html
For more information about the attributes and data types of an
IfcMapConversion, consult the IFC documentation.
@@ -17,23 +17,30 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from typing import Union
class Usecase:
def __init__(self, file, product=None, reference=None):
"""Associates a product with a library reference
def __init__(
self, file: ifcopenshell.file, products: ifcopenshell.entity_instance, reference: ifcopenshell.entity_instance
):
"""Associates a list products with a library reference
A product may be associated with zero, one, or many references across
multiple libraries. See ifcopenshell.api.library.add_reference for more
detail about how references work.
:param product: The IfcProduct you want to associate with the reference
:type product: ifcopenshell.entity_instance.entity_instance
:param products: The list of IfcProducts you want to associate with the reference
:type products: list[ifcopenshell.entity_instance.entity_instance]
:param reference: The IfcLibraryReference you want the product to be
associated with.
:type reference: ifcopenshell.entity_instance.entity_instance
:return: The IfcRelAssociatesLibrary relationship entity
:rtype: ifcopenshell.entity_instance.entity_instance
or `None` if `products` was an empty list or all products were
already assigned to the `reference`.
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
Example:
@@ -51,40 +58,46 @@ class Usecase:
ifc_class="IfcUnitaryEquipment", predefined_type="AIRHANDLER")
# And now assign the IFC model's AHU with its Brickschema counterpart
ifcopenshell.api.run("library.assign_reference", model, reference=reference, product=ahu)
ifcopenshell.api.run("library.assign_reference", model, reference=reference, products=[ahu])
"""
self.file = file
self.settings = {
"product": product,
"products": products,
"reference": reference,
}
def execute(self):
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
products: set[ifcopenshell.entity_instance] = set(self.settings["products"])
products = products - referenced_elements
if not products:
return
if self.file.schema == "IFC2X3":
rels = self.get_ifc2x3_rels()
rel = next(
(
r
for r in self.file.by_type("IfcRelAssociatesLibrary")
if r.RelatingLibrary == self.settings["reference"]
),
None,
)
else:
rels = self.settings["reference"].LibraryRefForObjects
if not rels:
rel = next(iter(self.settings["reference"].LibraryRefForObjects), None)
if not rel:
return self.file.create_entity(
"IfcRelAssociatesLibrary",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
RelatedObjects=[self.settings["product"]],
RelatedObjects=list(products),
RelatingLibrary=self.settings["reference"],
)
for rel in rels:
if self.settings["product"] in rel.RelatedObjects:
return rel
rel = rels[0]
related_objects = list(rel.RelatedObjects)
related_objects.append(self.settings["product"])
rel.RelatedObjects = related_objects
related_objects = set(rel.RelatedObjects) | products
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel)
return rel
def get_ifc2x3_rels(self):
return [
r for r in self.file.by_type("IfcRelAssociatesLibrary") if r.RelatingLibrary == self.settings["reference"]
]
@@ -18,18 +18,24 @@
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.api
class Usecase:
def __init__(self, file, reference=None, product=None):
"""Unassigns a product from a reference
def __init__(
self,
file: ifcopenshell.file,
reference: ifcopenshell.entity_instance,
products: list[ifcopenshell.entity_instance],
):
"""Unassigns a product of products from a reference
If the product isn't assigned to the reference, nothing will happen.
:param reference: The IfcLibraryReference to unassign from
:type reference: ifcopenshell.entity_instance.entity_instance
:param product: A IfcProduct element to unassign from the reference
:type product: ifcopenshell.entity_instance.entity_instance
:param products: A list of IfcProduct elements to unassign from the reference
:type products: list[ifcopenshell.entity_instance.entity_instance]
:return: None
:rtype: None
@@ -49,27 +55,36 @@ class Usecase:
ifc_class="IfcUnitaryEquipment", predefined_type="AIRHANDLER")
# And now assign the IFC model's AHU with its Brickschema counterpart
ifcopenshell.api.run("library.assign_reference", model, reference=reference, product=ahu)
ifcopenshell.api.run("library.assign_reference", model, reference=reference, products=[ahu])
# Let's change our mind and unassign it.
ifcopenshell.api.run("library.unassign_reference", model, reference=reference, product=ahu)
ifcopenshell.api.run("library.unassign_reference", model, reference=reference, products=[ahu])
"""
self.file = file
self.settings = {"reference": reference, "product": product}
self.settings = {"reference": reference, "products": products}
def execute(self):
rels = self.settings["reference"].LibraryRefForObjects
if not rels:
return
for rel in rels:
if self.settings["product"] in rel.RelatedObjects:
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
continue
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["product"])
rel.RelatedObjects = related_objects
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
reference_rels: set[ifcopenshell.entity_instance] = set()
products = set(self.settings["products"])
for product in products:
reference_rels.update(product.HasAssociations)
reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesLibrary") and rel.RelatingLibrary == self.settings["reference"]
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
@@ -38,7 +38,7 @@ def get_application(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instanc
if not app and ifc.schema == "IFC2X3":
raise Exception(
"Please create an application to continue. See the owner.create_owner_history docs for more info."
"https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
"https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
)
return (app or [None])[0]
@@ -58,7 +58,7 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
if not pao and ifc.schema == "IFC2X3":
raise Exception(
"Please create a user to continue. See the owner.create_owner_history docs for more info."
"https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
"https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
)
return (pao or [None])[0]
@@ -22,11 +22,16 @@ import ifcopenshell.util.element
class Usecase:
def __init__(self, file, product=None, relating_structure=None):
"""Dereferences the a product and space
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
relating_structure: ifcopenshell.entity_instance,
):
"""Dereferences a list of products and space
:param product: The physical IfcElement that exists in the space.
:type product: ifcopenshell.entity_instance.entity_instance
:param products: The list of physical IfcElements that exists in the space.
:type products: list[ifcopenshell.entity_instance.entity_instance]
:param relating_structure: The IfcSpatialStructureElement element, such
as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element
exists in.
@@ -59,23 +64,24 @@ class Usecase:
ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1)
# And referenced in the others
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey2)
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey3)
ifcopenshell.api.run("spatial.reference_structure", model, products=[column], relating_structure=storey2)
ifcopenshell.api.run("spatial.reference_structure", model, products=[column], relating_structure=storey3)
# Actually, it only goes up to storey 2.
ifcopenshell.api.run("spatial.dereference_structure", model, product=column, relating_structure=storey3)
ifcopenshell.api.run("spatial.dereference_structure", model, products=[column], relating_structure=storey3)
"""
self.file = file
self.settings = {"product": product, "relating_structure": relating_structure}
self.settings = {"products": products, "relating_structure": relating_structure}
def execute(self):
for rel in self.settings["product"].ReferencedInStructures:
if rel.RelatingStructure != self.settings["relating_structure"]:
def execute(self) -> None:
products = set(self.settings["products"])
for rel in self.settings["relating_structure"].ReferencesElements:
related_elements = set(rel.RelatedElements)
if not related_elements.intersection(products):
continue
related_elements = list(rel.RelatedElements)
related_elements.remove(self.settings["product"])
related_elements = related_elements - products
if related_elements:
rel.RelatedElements = related_elements
rel.RelatedElements = list(related_elements)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
@@ -18,11 +18,18 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from typing import Union
class Usecase:
def __init__(self, file, product=None, relating_structure=None):
"""Denote that a product is related to a spatial structure
def __init__(
self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
relating_structure: ifcopenshell.entity_instance,
):
"""Denote that a list products is related to a list of spatial structures
This is similar to ifcopenshell.api.spatial.assign_container, except
that containment can only occur between a product and a single spatial
@@ -39,13 +46,15 @@ class Usecase:
Referencing is non-hierarchical, so a door may be referenced in multiple
spaces simultaneously.
:param product: The physical IfcElement that exists in the space.
:type product: ifcopenshell.entity_instance.entity_instance
:param products: The list of physical IfcElements that exists in the space.
:type products: list[ifcopenshell.entity_instance.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.entity_instance
:return: The IfcRelReferencedInSpatialStructure relationship instance
:rtype: ifcopenshell.entity_instance.entity_instance
or `None` if `products` was an empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
@@ -73,37 +82,43 @@ class Usecase:
ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1)
# And referenced in the others
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey2)
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey3)
ifcopenshell.api.run(
"spatial.reference_structure", model, products=[column], relating_structure=[storey2, storey3]
)
"""
self.file = file
self.settings = {
"product": product,
"products": products,
"relating_structure": relating_structure,
}
def execute(self):
referenced_in_structures = self.settings["product"].ReferencedInStructures
references_elements = self.settings["relating_structure"].ReferencesElements
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
structure = self.settings["relating_structure"]
products = set(self.settings["products"])
for rel in referenced_in_structures:
if rel.RelatingStructure == self.settings["relating_structure"]:
return
if not products:
return
if references_elements:
related_elements = list(references_elements[0].RelatedElements)
related_elements.append(self.settings["product"])
references_elements[0].RelatedElements = related_elements
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": references_elements[0]})
else:
references_elements = self.file.create_entity(
referenced = ifcopenshell.util.element.get_structure_referenced_elements(structure)
products_to_assign = products - referenced
rel = next(iter(structure.ReferencesElements), None)
if not products_to_assign:
return rel
if rel is None:
rel = self.file.create_entity(
"IfcRelReferencedInSpatialStructure",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedElements": [self.settings["product"]],
"RelatingStructure": self.settings["relating_structure"],
"RelatedElements": list(products_to_assign),
"RelatingStructure": structure,
}
)
else:
related_elements = set(rel.RelatedElements) | products_to_assign
rel.RelatedElements = list(related_elements)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return references_elements
return rel
@@ -30,7 +30,7 @@ import functools
import subprocess
import sys
import time
from typing import Union, Any, Callable, TypeVar
from typing import Union, Any, Callable, TypeVar, overload
from . import ifcopenshell_wrapper
from . import settings
@@ -317,15 +317,25 @@ class entity_instance(object):
return self.wrapped_data.to_string(valid_spf)
def is_a(self, *args) -> Union[str, bool]:
@overload
def is_a(self) -> str: ...
@overload
def is_a(self, ifc_class: str) -> bool: ...
@overload
def is_a(self, with_schema: bool) -> str: ...
def is_a(self, *args: Union[str, bool]) -> Union[str, bool]:
"""Return the IFC class name of an instance, or checks if an instance belongs to a class.
The check will also return true if a parent class name is provided.
:param args: If specified, is a case insensitive IFC class name to check
:type args: string
or if specified as a boolean then will define whether
returned IFC class name should include schema name
(e.g. "IFC4.IfcWall" if `True` and "IfcWall" if `False`).
If omitted will act as `False`.
:type args: Union[str, bool]
:returns: Either the name of the class, or a boolean if it passes the check
:rtype: string|bool
:rtype: Union[str, bool]
Example:
@@ -23,10 +23,10 @@ from typing import Optional
def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]:
results = set()
if not element.is_a("IfcRoot"):
if hasattr(element, "HasExternalReferences"):
return {r.RelatingReference for r in element.HasExternalReferences or []}
elif hasattr(element, "HasExternalReference"): # Seriously, IFC?
return {r.RelatingReference for r in element.HasExternalReference or []}
if (references := getattr(element, "HasExternalReferences", None)) is not None or (
references := getattr(element, "HasExternalReference", None)
) is not None:
return {r.RelatingReference for r in references}
if should_inherit:
element_type = ifcopenshell.util.element.get_type(element)
if element_type and element_type != element:
@@ -18,20 +18,59 @@
#
#
def get_constraints(product):
import ifcopenshell
from typing import Union
def get_constraints(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
"""
Retrieves the constraints assigned to the `product`.
:param product: The IFC element.
:type product: ifcopenshell.entity_instance.entity_instance
:return: List of assigned constraints.
:rtype: list[ifcopenshell.entity_instance.entity_instance]
"""
constraints = []
for rel in product.HasAssociations or []:
if rel.is_a("IfcRelAssociatesConstraint"):
constraints.append(rel.RelatingConstraint)
return constraints
def get_metrics(constraint):
def get_constrained_elements(constraint: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""
Retrieves the elements constrained by a `constraint`.
:param product: The IFC element.
:type product: ifcopenshell.entity_instance.entity_instance
:return: Set of elements constrained by a `constrant`.
:rtype: set[ifcopenshell.entity_instance.entity_instance]
"""
elements = set()
for rel in constraint.file.get_inverse(constraint):
if rel.is_a("IfcRelAssociatesConstraint"):
elements.update(rel.RelatedObjects)
return elements
def get_metrics(constraint: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
"""
Retrieves the list of nested constraints for a IfcObjective `constraint`.
:param product: IfcObjective constraint.
:type product: ifcopenshell.entity_instance.entity_instance
:return: List of nested constraints.
:rtype: list[ifcopenshell.entity_instance.entity_instance]
"""
metrics = []
for metric in constraint.BenchmarkValues or []:
metrics.append(metric)
return metrics
def get_metric_reference(metric, is_deep=True):
def get_metric_reference(metric: ifcopenshell.entity_instance, is_deep=True):
def get_reference_Attribute(ref, path):
if ref:
if is_deep:
@@ -47,7 +86,10 @@ def get_metric_reference(metric, is_deep=True):
reference = metric.ReferencePath
return get_reference_Attribute(reference, "")
def get_metric_constraints(resource, attribute):
def get_metric_constraints(
resource: ifcopenshell.entity_instance, attribute
) -> Union[list[ifcopenshell.entity_instance], None]:
metrics = []
for constraint in get_constraints(resource) or []:
for metric in get_metrics(constraint) or []:
@@ -60,15 +102,14 @@ def get_metric_constraints(resource, attribute):
return metrics
return None
def is_hard_constraint(metric):
if metric.ConstraintGrade == "HARD" and metric.Benchmark == "EQUALTO":
return True
def is_attribute_locked(product, attribute):
def is_hard_constraint(metric: ifcopenshell.entity_instance) -> bool:
return metric.ConstraintGrade == "HARD" and metric.Benchmark == "EQUALTO"
def is_attribute_locked(product: ifcopenshell.entity_instance, attribute) -> bool:
is_locked = False
metrics = get_metric_constraints(
product, attribute
)
metrics = get_metric_constraints(product, attribute)
for metric in metrics or []:
if is_hard_constraint(metric):
is_locked = True
@@ -20,16 +20,17 @@ from __future__ import annotations
import ifcopenshell
import ifcopenshell.util.element
from typing import Any, Callable, Optional, Union, Literal, overload
from collections import namedtuple
def get_pset(
element: ifcopenshell.entity_instance,
name: str,
prop: Optional[str] = None,
psets_only=False,
qtos_only=False,
should_inherit=True,
verbose=False,
psets_only: bool = False,
qtos_only: bool = False,
should_inherit: bool = True,
verbose: bool = False,
) -> Union[Any, dict[str, Any]]:
"""Retrieve a single property set or single property
@@ -429,8 +430,7 @@ def get_predefined_type(element: ifcopenshell.entity_instance) -> str:
element = ifcopenshell.by_type("IfcWall")[0]
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
"""
element_type = get_type(element)
if element_type:
if element_type := get_type(element):
predefined_type = getattr(element_type, "PredefinedType", None)
if predefined_type == "USERDEFINED" or not predefined_type:
predefined_type = getattr(element_type, "ElementType", ...)
@@ -563,7 +563,9 @@ def get_material(
return get_material(relating_type, should_skip_usage)
def get_materials(element: ifcopenshell.entity_instance, should_inherit=True) -> list[ifcopenshell.entity_instance]:
def get_materials(
element: ifcopenshell.entity_instance, should_inherit: bool = True
) -> list[ifcopenshell.entity_instance]:
"""Gets individual materials of an element
If the element has a material set, the individual materials of that set are
@@ -826,7 +828,7 @@ def get_layers(
def get_container(
element: ifcopenshell.entity_instance, should_get_direct=False, ifc_class: Optional[str] = None
element: ifcopenshell.entity_instance, should_get_direct: bool = False, ifc_class: Optional[str] = None
) -> ifcopenshell.entity_instance:
"""
Retrieves the spatial structure container of an element.
@@ -902,6 +904,27 @@ def get_referenced_structures(element: ifcopenshell.entity_instance) -> list[ifc
return [r.RelatingStructure for r in getattr(element, "ReferencedInStructures", [])]
def get_structure_referenced_elements(structure: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""Retreives a set of elements referenced by a structure
:param structure: IfcSpatialElement
:type element: ifcopenshell.entity_instance.entity_instance
:return: A set of referenced elements, IfcSpatialReferenceSelect
:rtype: set[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
element = file.by_type("IfcBuildingStorey")[0]
print(ifcopenshell.util.element.get_structure_referenced_elements(element))
"""
referenced = set()
for rel in structure.ReferencesElements:
referenced.update(rel.RelatedElements)
return referenced
def get_decomposition(element: ifcopenshell.entity_instance, is_recursive=True) -> list[ifcopenshell.entity_instance]:
"""
Retrieves all subelements of an element based on the spatial decomposition
@@ -1092,6 +1115,66 @@ def get_components(element: ifcopenshell.entity_instance, include_ports=False) -
return is_decomposed_by[0].RelatedObjects
ReferenceData = namedtuple("ReferenceData", "inverse_attribute, rel_class, relating_element_attribute")
# References below are omitted because they do not introduce
# any additional referenced objects besides the objects
# from their supertype IfcExternalReference
# - IfcExternallyDefinedHatchStyle
# - IfcExternallyDefinedSurfaceStyle
# - IfcExternallyDefinedTextFont
REFERENCE_TYPES: dict[str, ReferenceData] = {
"IfcClassificationReference": ReferenceData(
"ClassificationRefForObjects",
"IfcRelAssociatesClassification",
"RelatingClassification",
),
"IfcDocumentReference": ReferenceData("DocumentRefForObjects", "IfcRelAssociatesDocument", "RelatingDocument"),
"IfcLibraryReference": ReferenceData("LibraryRefForObjects", "IfcRelAssociatesLibrary", "RelatingLibrary"),
}
def get_referenced_elements(reference: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""Get all elements with assigned `reference`
:param reference: IfcExternalReference subtype reference
:type reference: ifcopenshell.entity_instance.entity_instance
:return: The elements with assigned `reference`
:rtype: set[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
reference = file.by_type("IfcClassificationReference")[0]
elements = ifcopenshell.util.element.get_referenced_elements(reference)
"""
related_objects: set[ifcopenshell.entity_instance] = set()
ifc_file = reference.file
ifc_class = reference.is_a()
if ifc_file.schema == "IFC2X3":
reference_data = REFERENCE_TYPES.get(ifc_class)
if reference_data:
for rel in ifc_file.by_type(reference_data.rel_class):
if getattr(rel, reference_data.relating_element_attribute) == reference:
related_objects.update(rel.RelatedObjects)
else:
# IfcExternalReference
for external_rel in reference.ExternalReferenceForResources:
related_objects.update(external_rel.RelatedResourceObjects)
reference_data = REFERENCE_TYPES.get(ifc_class)
if reference_data:
for rel in getattr(reference, reference_data.inverse_attribute):
related_objects.update(rel.RelatedObjects)
return related_objects
def replace_attribute(element: ifcopenshell.entity_instance, old: Any, new: Any) -> None:
for i, attribute_value in enumerate(element):
if has_element_reference(attribute_value, old):
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import shapely
import shapely.ops
import numpy as np
import ifcopenshell.util.element
import ifcopenshell.util.placement