document.assign_document - support batching #4474

This commit is contained in:
Andrej730
2024-04-15 16:32:54 +05:00
parent 1f605484d9
commit 097f44284f
12 changed files with 91 additions and 51 deletions
@@ -99,6 +99,9 @@ ARGUMENTS_DEPRECATION = {
"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"
),
}
@@ -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
@@ -45,7 +45,7 @@ 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)
@@ -18,22 +18,25 @@
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.element
class TestAssignDocument(test.bootstrap.IFC4):
def test_assigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference)
ifcopenshell.api.run("document.assign_document", self.file, products=[element], document=reference)
assert element.HasAssociations[0].RelatingDocument == reference
assert ifcopenshell.util.element.get_referenced_elements(reference) == {element}
def test_assigning_multiple_documents(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference)
ifcopenshell.api.run("document.assign_document", self.file, product=element2, document=reference)
ifcopenshell.api.run("document.assign_document", self.file, products=[element, element2], document=reference)
assert len(self.file.by_type("IfcRelAssociatesDocument")) == 1
assert element.HasAssociations[0].RelatingDocument == reference
assert element2.HasAssociations[0].RelatingDocument == reference
assert element.HasAssociations[0] == element.HasAssociations[0]
assert ifcopenshell.util.element.get_referenced_elements(reference) == {element, element2}
class TestAssignDocumentIFC2X3(test.bootstrap.IFC2X3, TestAssignDocument):
pass
@@ -35,7 +35,7 @@ class TestRemoveReference(test.bootstrap.IFC4):
wall = self.file.createIfcWall()
information = ifcopenshell.api.run("document.add_information", self.file, parent=None)
reference = ifcopenshell.api.run("document.add_reference", self.file, information=information)
ifcopenshell.api.run("document.assign_document", self.file, product=wall, document=reference)
ifcopenshell.api.run("document.assign_document", self.file, products=[wall], document=reference)
assert len(self.file.by_type("IfcRelAssociatesDocument")) == 2
ifcopenshell.api.run("document.remove_reference", self.file, reference=reference)
assert len(self.file.by_type("IfcDocumentReference")) == 0
@@ -24,7 +24,7 @@ class TestUnassignDocument(test.bootstrap.IFC4):
def test_unassigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference)
ifcopenshell.api.run("document.assign_document", self.file, products=[element], document=reference)
ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference)
assert not element.HasAssociations
assert not len(self.file.by_type("IfcRelAssociatesDocument"))
@@ -33,8 +33,8 @@ class TestUnassignDocument(test.bootstrap.IFC4):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference)
ifcopenshell.api.run("document.assign_document", self.file, product=element2, document=reference)
ifcopenshell.api.run("document.assign_document", self.file, products=[element], document=reference)
ifcopenshell.api.run("document.assign_document", self.file, products=[element2], document=reference)
ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference)
assert not element.HasAssociations
assert element2.HasAssociations[0].RelatingDocument == reference
@@ -254,3 +254,10 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4):
ifcopenshell.api.run("library.assign_reference", self.file, products=[product], reference=reference)
ifcopenshell.api.run("library.unassign_reference", self.file, product=product, reference=reference)
assert len(self.file.by_type("IfcRelAssociatesLibrary")) == 0
@deprecation_check
def test_assigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference)
assert element.HasAssociations[0].RelatingDocument == reference
@@ -787,7 +787,6 @@ class TestGetNestIFC2X3(test.bootstrap.IFC2X3, TestGetNestIFC4):
class TestGetReferencedElements(test.bootstrap.IFC4):
# TODO: test other references:
# IfcDocumentReference
# IfcExternallyDefinedHatchStyle
# IfcExternallyDefinedSurfaceStyle
# IfcExternallyDefinedTextFont
@@ -816,6 +815,15 @@ class TestGetReferencedElements(test.bootstrap.IFC4):
ifcopenshell.api.run("library.assign_reference", self.file, reference=reference, products=elements)
assert subject.get_referenced_elements(reference) == set(elements)
def test_get_elements_referenced_by_document_reference(self):
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
elements = [
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall"),
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall"),
]
ifcopenshell.api.run("document.assign_document", self.file, document=reference, products=elements)
assert subject.get_referenced_elements(reference) == set(elements)
class TestGetReferencedElementsIFC2X3(test.bootstrap.IFC2X3, TestGetReferencedElements):
pass