mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
nest.assign_object - support batching #4474
This commit is contained in:
@@ -29,7 +29,9 @@ def assign_object(ifc, nest, collector, relating_obj=None, related_obj=None):
|
||||
if not nest.can_nest(relating_obj, related_obj):
|
||||
return
|
||||
rel = ifc.run(
|
||||
"nest.assign_object", related_object=ifc.get_entity(related_obj), relating_object=ifc.get_entity(relating_obj)
|
||||
"nest.assign_object",
|
||||
related_objects=[ifc.get_entity(related_obj)],
|
||||
relating_object=ifc.get_entity(relating_obj),
|
||||
)
|
||||
collector.assign(relating_obj)
|
||||
collector.assign(related_obj)
|
||||
|
||||
@@ -38,7 +38,7 @@ class TestAssignObject:
|
||||
ifc.get_entity("relating_obj").should_be_called().will_return("relating_object")
|
||||
ifc.get_entity("related_obj").should_be_called().will_return("related_object")
|
||||
ifc.run(
|
||||
"nest.assign_object", related_object="related_object", relating_object="relating_object"
|
||||
"nest.assign_object", related_objects=["related_object"], relating_object="relating_object"
|
||||
).should_be_called().will_return("rel")
|
||||
nest.disable_editing("related_obj").should_be_called()
|
||||
collector.assign("relating_obj").should_be_called()
|
||||
|
||||
@@ -67,11 +67,15 @@ ARGUMENTS_DEPRECATION = {
|
||||
new_argument="products",
|
||||
replace_usecase="spatial.unassign_container",
|
||||
),
|
||||
"nest.assign_object": partial(
|
||||
batching_argument_deprecation, prev_argument="related_object", new_argument="related_objects"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
CACHED_USECASE_CLASSES = dict()
|
||||
|
||||
|
||||
def run(
|
||||
usecase_path: str,
|
||||
ifc_file: Optional[ifcopenshell.file] = None,
|
||||
|
||||
@@ -68,6 +68,6 @@ class Usecase:
|
||||
)
|
||||
elif self.settings["cost_item"]:
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object", self.file, related_object=cost_item, relating_object=self.settings["cost_item"]
|
||||
"nest.assign_object", self.file, related_objects=[cost_item], relating_object=self.settings["cost_item"]
|
||||
)
|
||||
return cost_item
|
||||
|
||||
@@ -82,7 +82,7 @@ class Usecase:
|
||||
rel = ifcopenshell.api.run(
|
||||
"nest.assign_object",
|
||||
self.file,
|
||||
related_object=cost_item,
|
||||
related_objects=[cost_item],
|
||||
relating_object=to_element,
|
||||
)
|
||||
# elif inverse.is_a("IfcRelAssignsToProduct"):
|
||||
@@ -104,4 +104,4 @@ class Usecase:
|
||||
elif isinstance(value, (tuple, list)) and from_element in value:
|
||||
new_value = list(value)
|
||||
new_value.append(to_element)
|
||||
inverse[i] = new_value
|
||||
inverse[i] = new_value
|
||||
|
||||
@@ -19,11 +19,17 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, related_object=None, relating_object=None):
|
||||
"""Assigns an object as a nested child to a parent host
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
related_objects: list[ifcopenshell.entity_instance],
|
||||
relating_object: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Assigns objects as nested children to a parent host
|
||||
|
||||
All physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", where large things are made up of
|
||||
@@ -72,14 +78,15 @@ class Usecase:
|
||||
ifcopenshell.api.cost.add_cost_item or
|
||||
ifcopenshell.api.sequence.add_task.
|
||||
|
||||
:param related_object: The child of the nesting relationship, typically
|
||||
an IfcElement.
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_objects: The list of children of the nesting relationship,
|
||||
typically IfcElements.
|
||||
:type related_objects: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param relating_object: The host parent of the nesting relationship,
|
||||
typically an IfcElement.
|
||||
:type relating_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelNests relationship instance
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
or `None` if `related_objects` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
@@ -90,30 +97,50 @@ class Usecase:
|
||||
ifc_class="IfcSanitaryTerminal", predefined_type="SINK")
|
||||
faucet = ifcopenshell.api.run("root.create_entity", model,
|
||||
ifc_class="IfcValve", predefined_type="FAUCET")
|
||||
ifcopenshell.api.run("nest.assign_object", model, related_object=faucet, relating_object=sink)
|
||||
ifcopenshell.api.run("nest.assign_object", model, related_objects=[faucet], relating_object=sink)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"related_object": related_object, "relating_object": relating_object}
|
||||
self.settings = {"related_objects": related_objects, "relating_object": relating_object}
|
||||
|
||||
def execute(self):
|
||||
nests = None
|
||||
if self.settings["related_object"].Nests:
|
||||
nests = self.settings["related_object"].Nests[0]
|
||||
|
||||
is_nested_by = None
|
||||
for rel in self.settings["relating_object"].IsNestedBy:
|
||||
if rel.is_a("IfcRelNests"):
|
||||
is_nested_by = rel
|
||||
break
|
||||
|
||||
if nests and nests == is_nested_by:
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if not self.settings["related_objects"]:
|
||||
return
|
||||
|
||||
if nests:
|
||||
related_objects = list(nests.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
related_objects = set(self.settings["related_objects"])
|
||||
relating_object = self.settings["relating_object"]
|
||||
is_nested_by = next((i for i in relating_object.IsNestedBy), None)
|
||||
|
||||
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:
|
||||
object_rel = next(iter(object.Nests), None)
|
||||
|
||||
if object_rel is None:
|
||||
objects_without_nests.append(object)
|
||||
continue
|
||||
|
||||
# either is_nested_by is None or product is part of different rel
|
||||
if object_rel != is_nested_by:
|
||||
previous_nests_rels.add(object_rel)
|
||||
objects_with_nests.append(object)
|
||||
|
||||
# products with already assigned nestings will be skipped
|
||||
|
||||
objects_to_change = objects_without_nests + objects_with_nests
|
||||
# nothing to change
|
||||
if not objects_to_change:
|
||||
return is_nested_by
|
||||
|
||||
# NOTE: An object can both be nested and assigned to a container or aggregate.
|
||||
|
||||
# unassign elements from previous nests
|
||||
for nests in previous_nests_rels:
|
||||
related_objects = set(nests.RelatedObjects) - related_objects
|
||||
if related_objects:
|
||||
nests.RelatedObjects = related_objects
|
||||
nests.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": nests})
|
||||
else:
|
||||
history = nests.OwnerHistory
|
||||
@@ -121,10 +148,9 @@ class Usecase:
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
|
||||
# assign elements to a new nesting
|
||||
if is_nested_by:
|
||||
related_objects = list(is_nested_by.RelatedObjects)
|
||||
related_objects.append(self.settings["related_object"])
|
||||
is_nested_by.RelatedObjects = related_objects
|
||||
is_nested_by.RelatedObjects = list(set(is_nested_by.RelatedObjects) | related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_nested_by})
|
||||
else:
|
||||
is_nested_by = self.file.create_entity(
|
||||
@@ -132,8 +158,12 @@ class Usecase:
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingObject": self.settings["relating_object"],
|
||||
"RelatedObjects": list(related_objects),
|
||||
"RelatingObject": relating_object,
|
||||
}
|
||||
)
|
||||
|
||||
# NOTE: Creating a nesting relationship doesn't localize the object's placement,
|
||||
# unlike assigning it to an aggregate or a container.
|
||||
|
||||
return is_nested_by
|
||||
|
||||
@@ -44,6 +44,6 @@ class Usecase:
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object",
|
||||
self.file,
|
||||
related_object=self.settings["item"],
|
||||
related_objects=[self.settings["item"]],
|
||||
relating_object=self.settings["new_parent"],
|
||||
)
|
||||
|
||||
@@ -42,8 +42,8 @@ class Usecase:
|
||||
task = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subtask1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
subtask2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
ifcopenshell.api.run("nest.assign_object", model, related_object=subtask1, relating_object=task)
|
||||
ifcopenshell.api.run("nest.assign_object", model, related_object=subtask2, relating_object=task)
|
||||
ifcopenshell.api.run("nest.assign_object", model, related_objects=[subtask1], relating_object=task)
|
||||
ifcopenshell.api.run("nest.assign_object", model, related_objects=[subtask2], relating_object=task)
|
||||
# The relationship is returned as task still has subtask2
|
||||
rel = ifcopenshell.api.run("nest.unassign_object", model, related_object=subtask1)
|
||||
# Nothing is returned, as the relationship has no related objects
|
||||
|
||||
@@ -97,7 +97,7 @@ class Usecase:
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object",
|
||||
self.file,
|
||||
related_object=resource,
|
||||
related_objects=[resource],
|
||||
relating_object=self.settings["parent_resource"],
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -176,7 +176,7 @@ class Usecase:
|
||||
rel = ifcopenshell.api.run(
|
||||
"nest.assign_object",
|
||||
self.file,
|
||||
related_object=task,
|
||||
related_objects=[task],
|
||||
relating_object=self.settings["parent_task"],
|
||||
)
|
||||
if self.settings["parent_task"].Identification:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,67 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
|
||||
|
||||
class TestAssignObject(test.bootstrap.IFC4):
|
||||
def test_assigning_a_nesting(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSanitaryTerminal")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcValve")
|
||||
subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcValve")
|
||||
rel = ifcopenshell.api.run(
|
||||
"nest.assign_object", self.file, related_objects=[subelement1, subelement2], relating_object=element
|
||||
)
|
||||
assert ifcopenshell.util.element.get_nest(subelement1) == element
|
||||
assert ifcopenshell.util.element.get_nest(subelement2) == element
|
||||
assert rel.is_a("IfcRelNests")
|
||||
|
||||
def test_doing_nothing_if_the_nesting_is_already_assigned(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSanitaryTerminal")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcValve")
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_objects=[subelement], relating_object=element)
|
||||
total_elements = len([e for e in self.file])
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_objects=[subelement], relating_object=element)
|
||||
assert len([e for e in self.file]) == total_elements
|
||||
|
||||
def test_that_old_nesting_relationships_are_updated_if_they_still_have_elements(self):
|
||||
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSanitaryTerminal")
|
||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSanitaryTerminal")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcValve")
|
||||
subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcValve")
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object", self.file, related_objects=[subelement1, subelement2], relating_object=element1
|
||||
)
|
||||
rel = subelement1.Nests[0]
|
||||
assert len(rel.RelatedObjects) == 2
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_objects=[subelement1], relating_object=element2)
|
||||
assert len(rel.RelatedObjects) == 1
|
||||
|
||||
def test_that_old_nesting_relationships_are_purged_if_no_more_elements_are_nested(self):
|
||||
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSanitaryTerminal")
|
||||
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSanitaryTerminal")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcValve")
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_objects=[subelement1], relating_object=element1)
|
||||
rel_id = subelement1.Nests[0].id()
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_objects=[subelement1], relating_object=element2)
|
||||
with pytest.raises(RuntimeError):
|
||||
self.file.by_id(rel_id)
|
||||
@@ -190,7 +190,7 @@ class TestRemoveProduct(test.bootstrap.IFC4):
|
||||
def test_removing_all_nesting_relationships_of_a_whole(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam")
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_object=subelement, relating_object=element)
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_objects=[subelement], relating_object=element)
|
||||
total_entities = len(list(self.file))
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=element)
|
||||
assert len(list(self.file)) == total_entities - 2
|
||||
@@ -201,7 +201,7 @@ class TestRemoveProduct(test.bootstrap.IFC4):
|
||||
def test_removing_all_nesting_relationships_of_a_part(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam")
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_object=subelement, relating_object=element)
|
||||
ifcopenshell.api.run("nest.assign_object", self.file, related_objects=[subelement], relating_object=element)
|
||||
total_entities = len(list(self.file))
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=subelement)
|
||||
assert len(list(self.file)) == total_entities - 2
|
||||
|
||||
@@ -107,3 +107,11 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4):
|
||||
ifcopenshell.api.run("spatial.assign_container", self.file, products=[subelement], relating_structure=element)
|
||||
ifcopenshell.api.run("spatial.remove_container", self.file, product=subelement)
|
||||
assert ifcopenshell.util.element.get_container(subelement) is None
|
||||
|
||||
@deprecation_check
|
||||
def test_assigning_a_nesting(self):
|
||||
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSanitaryTerminal")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcValve")
|
||||
rel = ifcopenshell.api.run("nest.assign_object", self.file, related_object=subelement, relating_object=element)
|
||||
assert ifcopenshell.util.element.get_nest(subelement) == element
|
||||
assert rel.is_a("IfcRelNests")
|
||||
|
||||
@@ -1526,7 +1526,7 @@ class TestPartOf:
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory")
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element)
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_objects=[subelement], relating_object=element)
|
||||
facet = PartOf(name="IFCFURNITURE", relation="IFCRELNESTS")
|
||||
run("Any nested part passes a nest relationship", facet=facet, inst=subelement, expected=True)
|
||||
run("Any nested whole fails a nest relationship", facet=facet, inst=element, expected=False)
|
||||
@@ -1534,7 +1534,7 @@ class TestPartOf:
|
||||
ifc = ifcopenshell.file()
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory")
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element)
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_objects=[subelement], relating_object=element)
|
||||
facet = PartOf(relation="IFCRELNESTS", name="IFCBEAM")
|
||||
run("The nest entity must match exactly 1/2", facet=facet, inst=subelement, expected=False)
|
||||
facet = PartOf(relation="IFCRELNESTS", name="IFCFURNITURE")
|
||||
@@ -1551,8 +1551,8 @@ class TestPartOf:
|
||||
element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory")
|
||||
subsubelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcMechanicalFastener")
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element)
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_object=subsubelement, relating_object=subelement)
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_objects=[subelement], relating_object=element)
|
||||
ifcopenshell.api.run("nest.assign_object", ifc, related_objects=[subsubelement], relating_object=subelement)
|
||||
facet = PartOf(relation="IFCRELNESTS", name="IFCFURNITURE")
|
||||
run("Nesting may be indirect", facet=facet, inst=subsubelement, expected=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user