diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index 163a3ea3c2..df87d493c7 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -80,10 +80,7 @@ class Spatial(bonsai.core.tool.Spatial): def get_root_element(cls, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: while True: if parent := ( - ifcopenshell.util.element.get_aggregate(element) - or ifcopenshell.util.element.get_nest(element) - or ifcopenshell.util.element.get_filled_void(element) - or ifcopenshell.util.element.get_voided_element(element) + ifcopenshell.util.element.get_aggregate(element) or ifcopenshell.util.element.get_nest(element) ): element = parent else: diff --git a/src/bonsai/test/core/test_spatial.py b/src/bonsai/test/core/test_spatial.py index ddc6116fdf..e5a03d2048 100644 --- a/src/bonsai/test/core/test_spatial.py +++ b/src/bonsai/test/core/test_spatial.py @@ -52,6 +52,35 @@ class TestAssignContainer: collector.assign("obj2").should_be_called() subject.assign_container(ifc, collector, spatial, container="container", objs=["obj"]) + def test_root_resolves_to_self_for_a_filling(self, ifc, collector, spatial): + ifc.get_entity("door_obj").should_be_called().will_return("door") + spatial.get_root_element("door").should_be_called().will_return("door") + spatial.disable_editing("door_obj").should_be_called() + spatial.get_decomposition("door").should_be_called().will_return(["door"]) + spatial.can_contain("container", "door").should_be_called().will_return(True) + ifc.run("spatial.assign_container", products=["door"], relating_structure="container").should_be_called() + ifc.get_object("door").should_be_called().will_return("door_obj") + collector.assign("door_obj").should_be_called() + subject.assign_container(ifc, collector, spatial, container="container", objs=["door_obj"]) + + def test_can_contain_is_evaluated_per_root_element(self, ifc, collector, spatial): + ifc.get_entity("door_obj").should_be_called().will_return("door") + spatial.get_root_element("door").should_be_called().will_return("door") + spatial.disable_editing("door_obj").should_be_called() + spatial.get_decomposition("door").should_be_called().will_return(["door"]) + ifc.get_entity("opening_obj").should_be_called().will_return("opening") + spatial.get_root_element("opening").should_be_called().will_return("opening") + spatial.disable_editing("opening_obj").should_be_called() + spatial.get_decomposition("opening").should_be_called().will_return(["opening"]) + spatial.can_contain("container", "door").should_be_called().will_return(True) + spatial.can_contain("container", "opening").should_be_called().will_return(False) + ifc.run("spatial.assign_container", products=["door"], relating_structure="container").should_be_called() + ifc.get_object("door").should_be_called().will_return("door_obj") + ifc.get_object("opening").should_be_called().will_return("opening_obj") + collector.assign("door_obj").should_be_called() + collector.assign("opening_obj").should_be_called() + subject.assign_container(ifc, collector, spatial, container="container", objs=["door_obj", "opening_obj"]) + class TestEnableEditingContainer: def test_run(self, spatial): diff --git a/src/bonsai/test/tool/test_spatial.py b/src/bonsai/test/tool/test_spatial.py index 005f370b01..ee2991e1c3 100644 --- a/src/bonsai/test/tool/test_spatial.py +++ b/src/bonsai/test/tool/test_spatial.py @@ -19,6 +19,9 @@ import bpy import ifcopenshell import ifcopenshell.api +import ifcopenshell.api.aggregate +import ifcopenshell.api.feature +import ifcopenshell.api.nest import ifcopenshell.api.root import ifcopenshell.api.spatial import numpy as np @@ -148,6 +151,40 @@ class TestGetContainer(NewFile): assert subject.get_container(wall) == site +class TestGetRootElement(NewFile): + def test_a_door_filling_a_wall_is_its_own_root_element(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + wall = ifc.createIfcWall() + opening = ifc.createIfcOpeningElement() + door = ifc.createIfcDoor() + ifcopenshell.api.feature.add_feature(ifc, feature=opening, element=wall) + ifcopenshell.api.feature.add_filling(ifc, opening=opening, element=door) + assert subject.get_root_element(door) == door + + def test_an_aggregated_element_walks_to_its_aggregate_root(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + assembly = ifc.createIfcElementAssembly() + beam = ifc.createIfcBeam() + ifcopenshell.api.aggregate.assign_object(ifc, products=[beam], relating_object=assembly) + assert subject.get_root_element(beam) == assembly + + def test_a_nested_element_walks_to_its_nest_root(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + parent_task = ifc.createIfcTask() + child_task = ifc.createIfcTask() + ifcopenshell.api.nest.assign_object(ifc, related_objects=[child_task], relating_object=parent_task) + assert subject.get_root_element(child_task) == parent_task + + def test_a_loose_element_is_its_own_root(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + wall = ifc.createIfcWall() + assert subject.get_root_element(wall) == wall + + class TestGetDecomposedElements(NewFile): def test_run(self): ifc = ifcopenshell.file()