mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user