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
+1 -1
View File
@@ -109,7 +109,7 @@ def remove_document(ifc, document_tool, document=None):
def assign_document(ifc, product=None, document=None): def assign_document(ifc, product=None, document=None):
ifc.run("document.assign_document", product=product, document=document) ifc.run("document.assign_document", products=[product], document=document)
def unassign_document(ifc, product=None, document=None): def unassign_document(ifc, product=None, document=None):
+2 -2
View File
@@ -241,7 +241,7 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"} attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
ifc.run("document.edit_information", information=information, attributes=attributes) ifc.run("document.edit_information", information=information, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri}) ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
ifc.run("document.assign_document", product=element, document=reference) ifc.run("document.assign_document", products=[element], document=reference)
drawing.import_drawings() drawing.import_drawings()
@@ -276,7 +276,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"} attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
ifc.run("document.edit_information", information=information, attributes=attributes) ifc.run("document.edit_information", information=information, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri}) ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
ifc.run("document.assign_document", product=new_drawing, document=reference) ifc.run("document.assign_document", products=[new_drawing], document=reference)
drawing_tool.import_drawings() drawing_tool.import_drawings()
return new_drawing return new_drawing
+1 -1
View File
@@ -133,7 +133,7 @@ class TestRemoveDocument:
class TestAssignDocument: class TestAssignDocument:
def test_run(self, ifc): def test_run(self, ifc):
ifc.run("document.assign_document", product="product", document="document").should_be_called() ifc.run("document.assign_document", products=["product"], document="document").should_be_called()
subject.assign_document(ifc, product="product", document="document") subject.assign_document(ifc, product="product", document="document")
+2 -2
View File
@@ -365,7 +365,7 @@ class TestAddDrawing:
attributes={"Identification": "X", "Name": "name", "Scope": "DRAWING"}, attributes={"Identification": "X", "Name": "name", "Scope": "DRAWING"},
).should_be_called() ).should_be_called()
ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called() ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called()
ifc.run("document.assign_document", product="element", document="reference").should_be_called() ifc.run("document.assign_document", products=["element"], document="reference").should_be_called()
drawing.import_drawings().should_be_called() drawing.import_drawings().should_be_called()
subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint") subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint")
@@ -405,7 +405,7 @@ class TestDuplicateDrawing:
ifc.run( ifc.run(
"document.edit_reference", reference="reference", attributes={"Location": "drawing_path"} "document.edit_reference", reference="reference", attributes={"Location": "drawing_path"}
).should_be_called() ).should_be_called()
ifc.run("document.assign_document", product="new_drawing", document="reference").should_be_called() ifc.run("document.assign_document", products=["new_drawing"], document="reference").should_be_called()
drawing.import_drawings().should_be_called() drawing.import_drawings().should_be_called()
subject.duplicate_drawing(ifc, drawing, drawing="drawing", should_duplicate_annotations=True) subject.duplicate_drawing(ifc, drawing, drawing="drawing", should_duplicate_annotations=True)
@@ -99,6 +99,9 @@ ARGUMENTS_DEPRECATION = {
"library.unassign_reference": partial( "library.unassign_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products" 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/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from typing import Union
class Usecase: class Usecase:
def __init__(self, file, product=None, document=None): def __init__(
"""Assigns a document to a product 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 An object may be assigned to zero, one, or multiple documents. Almost
any object or property may be assigned to a document, though typically 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 consistent with other external relationships (such as classification
systems or libraries). 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. 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 :param document: The IfcDocumentReference to associate to, or
alternatively an IfcDocumentInformation, though this is not alternatively an IfcDocumentInformation, though this is not
recommended. recommended.
:type document: ifcopenshell.entity_instance.entity_instance :type document: ifcopenshell.entity_instance.entity_instance
:return: The IfcRelAssociatesDocument relationship :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 :rtype: ifcopenshell.entity_instance.entity_instance
Example: Example:
@@ -54,42 +64,51 @@ class Usecase:
reference = ifcopenshell.api.run("document.add_reference", model, information=document) reference = ifcopenshell.api.run("document.add_reference", model, information=document)
# Let's imagine storey represents an IfcBuildingStorey for the ground floor # Let's imagine storey represents an IfcBuildingStorey for the ground floor
ifcopenshell.api.run("document.assign_document", model, product=storey, document=reference) ifcopenshell.api.run("document.assign_document", model, products=[storey], document=reference)
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"document": document, "document": document,
} }
def execute(self): def execute(self) -> Union[ifcopenshell.entity_instance, None]:
rel = self.get_document_rel() # TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set() # NOTE: reuses code from `library.assign_reference`
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects) 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": if self.file.schema == "IFC2X3":
for rel in self.file.by_type("IfcRelAssociatesDocument"): rel = next(
if rel.RelatingDocument == self.settings["document"]: (
return rel r
for r in self.file.by_type("IfcRelAssociatesDocument")
if r.RelatingDocument == self.settings["document"]
),
None,
)
else: else:
if ( ifc_class = self.settings["document"].is_a()
hasattr(self.settings["document"], "DocumentRefForObjects") if ifc_class == "IfcDocumentReference":
and self.settings["document"].DocumentRefForObjects rel = next(iter(self.settings["document"].DocumentRefForObjects), None)
): elif ifc_class == "IfcDocumentInformation":
return self.settings["document"].DocumentRefForObjects[0] rel = next(iter(self.settings["document"].DocumentInfoForObjects), None)
elif (
hasattr(self.settings["document"], "DocumentInfoForObjects")
and self.settings["document"].DocumentInfoForObjects
):
return self.settings["document"].DocumentInfoForObjects[0]
return self.file.create_entity( if not rel:
"IfcRelAssociatesDocument", return self.file.create_entity(
**{ "IfcRelAssociatesDocument",
"GlobalId": ifcopenshell.guid.new(), GlobalId=ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingDocument": self.settings["document"], 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) reference = ifcopenshell.api.run("document.add_reference", model, information=document)
# Let's imagine storey represents an IfcBuildingStorey for the ground floor # Let's imagine storey represents an IfcBuildingStorey for the ground floor
ifcopenshell.api.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 # 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, product=storey, document=reference)
@@ -18,22 +18,25 @@
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element
class TestAssignDocument(test.bootstrap.IFC4): class TestAssignDocument(test.bootstrap.IFC4):
def test_assigning_a_document(self): def test_assigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None) 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 element.HasAssociations[0].RelatingDocument == reference
assert ifcopenshell.util.element.get_referenced_elements(reference) == {element}
def test_assigning_multiple_documents(self): def test_assigning_multiple_documents(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = 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) 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, element2], document=reference)
ifcopenshell.api.run("document.assign_document", self.file, product=element2, document=reference)
assert len(self.file.by_type("IfcRelAssociatesDocument")) == 1 assert len(self.file.by_type("IfcRelAssociatesDocument")) == 1
assert element.HasAssociations[0].RelatingDocument == reference assert ifcopenshell.util.element.get_referenced_elements(reference) == {element, element2}
assert element2.HasAssociations[0].RelatingDocument == reference
assert element.HasAssociations[0] == element.HasAssociations[0]
class TestAssignDocumentIFC2X3(test.bootstrap.IFC2X3, TestAssignDocument):
pass
@@ -35,7 +35,7 @@ class TestRemoveReference(test.bootstrap.IFC4):
wall = self.file.createIfcWall() wall = self.file.createIfcWall()
information = ifcopenshell.api.run("document.add_information", self.file, parent=None) information = ifcopenshell.api.run("document.add_information", self.file, parent=None)
reference = ifcopenshell.api.run("document.add_reference", self.file, information=information) 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 assert len(self.file.by_type("IfcRelAssociatesDocument")) == 2
ifcopenshell.api.run("document.remove_reference", self.file, reference=reference) ifcopenshell.api.run("document.remove_reference", self.file, reference=reference)
assert len(self.file.by_type("IfcDocumentReference")) == 0 assert len(self.file.by_type("IfcDocumentReference")) == 0
@@ -24,7 +24,7 @@ class TestUnassignDocument(test.bootstrap.IFC4):
def test_unassigning_a_document(self): def test_unassigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None) 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) ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference)
assert not element.HasAssociations assert not element.HasAssociations
assert not len(self.file.by_type("IfcRelAssociatesDocument")) 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") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = 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) 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.assign_document", self.file, product=element2, 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) ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference)
assert not element.HasAssociations assert not element.HasAssociations
assert element2.HasAssociations[0].RelatingDocument == reference 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.assign_reference", self.file, products=[product], reference=reference)
ifcopenshell.api.run("library.unassign_reference", self.file, product=product, reference=reference) ifcopenshell.api.run("library.unassign_reference", self.file, product=product, reference=reference)
assert len(self.file.by_type("IfcRelAssociatesLibrary")) == 0 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): class TestGetReferencedElements(test.bootstrap.IFC4):
# TODO: test other references: # TODO: test other references:
# IfcDocumentReference
# IfcExternallyDefinedHatchStyle # IfcExternallyDefinedHatchStyle
# IfcExternallyDefinedSurfaceStyle # IfcExternallyDefinedSurfaceStyle
# IfcExternallyDefinedTextFont # IfcExternallyDefinedTextFont
@@ -816,6 +815,15 @@ class TestGetReferencedElements(test.bootstrap.IFC4):
ifcopenshell.api.run("library.assign_reference", self.file, reference=reference, products=elements) ifcopenshell.api.run("library.assign_reference", self.file, reference=reference, products=elements)
assert subject.get_referenced_elements(reference) == set(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): class TestGetReferencedElementsIFC2X3(test.bootstrap.IFC2X3, TestGetReferencedElements):
pass pass