nest api to maintain order of related objects #4698

order of objects in .RelatedObjects is important (e.g. for cost items, it's the order of their appearance), so we should maintain it and cannot use sets for .RelatedObjects
This commit is contained in:
Andrej730
2024-05-20 16:24:35 +05:00
parent fb88499197
commit 678dbaa66a
4 changed files with 49 additions and 12 deletions
@@ -104,20 +104,20 @@ def assign_object(
return
ifc2x3 = file.schema == "IFC2X3"
related_objects_set = set(related_objects)
related_objects = set(settings["related_objects"])
relating_object = settings["relating_object"]
if ifc2x3:
is_nested_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelNests")), None)
else:
is_nested_by = next((i for i in relating_object.IsNestedBy), None)
# NOTE: maintain .RelatedObjects order as it has meaning in IFC
previous_nests_rels: set[ifcopenshell.entity_instance] = set()
objects_without_nests: list[ifcopenshell.entity_instance] = []
objects_with_nests: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for object in related_objects:
for object in related_objects_set:
if ifc2x3:
object_rel = next((i for i in object.Decomposes if i.is_a("IfcRelNests")), None)
else:
@@ -143,7 +143,7 @@ def assign_object(
# unassign elements from previous nests
for nests in previous_nests_rels:
cur_related_objects = set(nests.RelatedObjects) - related_objects
cur_related_objects = [o for o in nests.RelatedObjects if o not in related_objects_set]
if cur_related_objects:
nests.RelatedObjects = list(cur_related_objects)
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": nests})
@@ -155,7 +155,11 @@ def assign_object(
# assign elements to a new nesting
if is_nested_by:
is_nested_by.RelatedObjects = list(set(is_nested_by.RelatedObjects) | related_objects)
cur_related_objects = list(is_nested_by.RelatedObjects)
cur_related_objects_set = set(cur_related_objects)
is_nested_by.RelatedObjects = cur_related_objects + [
o for o in related_objects if o not in cur_related_objects_set
]
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": is_nested_by})
else:
is_nested_by = file.create_entity(
@@ -163,7 +167,7 @@ def assign_object(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
"RelatedObjects": list(related_objects),
"RelatedObjects": related_objects,
"RelatingObject": relating_object,
}
)
@@ -49,23 +49,23 @@ def unassign_object(file: ifcopenshell.file, related_objects: list[ifcopenshell.
# nothing is returned, relationship is removed
ifcopenshell.api.run("nest.unassign_object", model, related_objects=[subtask2])
"""
settings = {"related_objects": related_objects}
related_objects = set(settings["related_objects"])
# NOTE: maintain .RelatedObjects order as it has meaning in IFC
related_objects_set = set(related_objects)
ifc2x3 = file.schema == "IFC2X3"
if ifc2x3:
rels = set(
rel
for object in related_objects
for object in related_objects_set
if (rel := next((rel for rel in object.Decomposes if rel.is_a("IfcRelNests")), None))
)
else:
rels = set(rel for object in related_objects if (rel := next((rel for rel in object.Nests), None)))
for rel in rels:
related_objects = set(rel.RelatedObjects) - related_objects
if related_objects:
rel.RelatedObjects = list(related_objects)
cur_related_objects = [o for o in rel.RelatedObjects if o not in related_objects_set]
if cur_related_objects:
rel.RelatedObjects = cur_related_objects
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
else:
history = rel.OwnerHistory
@@ -66,6 +66,23 @@ class TestAssignObject(test.bootstrap.IFC4):
with pytest.raises(RuntimeError):
self.file.by_id(rel_id)
def test_maintain_assignment_order_in_related_objects(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTask")
subelements = [ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTask") for _ in range(5)]
for i in range(5):
ifcopenshell.api.run(
"nest.assign_object", self.file, related_objects=subelements[: i + 1], relating_object=element
)
rel = self.file.by_type("IfcRelNests")[0]
assert rel.RelatedObjects == tuple(subelements[: i + 1])
# maintain the order in the affected relationships too
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTask")
ifcopenshell.api.run(
"nest.assign_object", self.file, related_objects=subelements[2:3], relating_object=element2
)
assert rel.RelatedObjects == tuple(subelements[:2] + subelements[3:])
class TestAssignObjectIFC2X3(test.bootstrap.IFC2X3, TestAssignObject):
pass
@@ -50,5 +50,21 @@ class TestUnassignObject(test.bootstrap.IFC4):
ifcopenshell.api.run("nest.unassign_object", self.file, related_objects=[subelement])
assert len(self.file.by_type("IfcRelNests")) == 0
def test_maintain_assignment_order_in_related_objects(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTask")
subelements = [ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcTask") for _ in range(5)]
rel = self.file.create_entity("IfcRelNests", RelatingObject=element, RelatedObjects=subelements)
original_order = subelements.copy()
# unassign elements in some random order
# skip 1 element to make sure rel won't get removed
removed_elements = set()
for i in (0, 3, 2, 4):
subelement = subelements[i]
ifcopenshell.api.run("nest.unassign_object", self.file, related_objects=[subelement])
removed_elements.add(subelement)
assert rel.RelatedObjects == tuple([o for o in original_order if o not in removed_elements])
class TestUnassignObjectIFC2X3(test.bootstrap.IFC2X3, TestUnassignObject):
pass