diff --git a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py index 0ab51ed853..aa83b82d63 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py @@ -27,6 +27,7 @@ classes = ( operator.EnableEditingContainer, operator.RemoveContainer, operator.SelectContainer, + operator.SelectSimilarContainer, prop.SpatialElement, prop.BIMSpatialProperties, prop.BIMObjectSpatialProperties, diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index 2a40024cff..185a46b6ca 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -112,3 +112,12 @@ class SelectContainer(bpy.types.Operator, Operator): def _execute(self, context): core.select_container(tool.Ifc, tool.Spatial, obj=context.active_object) + + +class SelectSimilarContainer(bpy.types.Operator, Operator): + bl_idname = "bim.select_similar_container" + bl_label = "Select Similar Container" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.select_similar_container(tool.Ifc, tool.Spatial, obj=context.active_object) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index 09cd98a199..42f22c0bc7 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -63,6 +63,7 @@ class BIM_PT_spatial(Panel): if SpatialData.data["is_contained"]: row.label(text=SpatialData.data["label"]) row.operator("bim.select_container", icon="TRACKER", text="") + row.operator("bim.select_similar_container", icon="RESTRICT_SELECT_OFF", text="") row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") if SpatialData.data["is_directly_contained"]: row.operator("bim.remove_container", icon="X", text="") diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index 67259a5ae2..e256eb894f 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -73,3 +73,13 @@ def select_container(ifc, spatial, obj=None): if not element: return spatial.set_active_object(ifc.get_object(spatial.get_container(element))) + + +def select_similar_container(ifc, spatial, obj=None): + element = ifc.get_entity(obj) + if not element: + return + for subelement in spatial.get_decomposed_elements(spatial.get_container(element)): + obj = ifc.get_object(subelement) + if obj: + spatial.select_object(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 6d6a9bbf6e..698629b3a9 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -191,11 +191,13 @@ class Spatial: def duplicate_object_and_data(cls, obj): pass def enable_editing(cls, obj): pass def get_container(cls, element): pass + def get_decomposed_elements(cls, container): pass def get_object_matrix(cls, obj): pass def get_relative_object_matrix(cls, target_obj, relative_to_obj): pass def import_containers(cls, parent=None): pass def run_root_copy_class(cls, obj=None): pass def run_spatial_assign_container(cls, structure_obj=None, element_obj=None): pass + def select_object(cls, obj): pass def set_active_object(cls, obj): pass def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index 8e495f9209..f521463861 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -60,6 +60,10 @@ class Spatial(blenderbim.core.tool.Spatial): def get_container(cls, element): return ifcopenshell.util.element.get_container(element) + @classmethod + def get_decomposed_elements(cls, container): + return ifcopenshell.util.element.get_decomposition(container) + @classmethod def get_object_matrix(cls, obj): return obj.matrix_world @@ -101,6 +105,10 @@ class Spatial(blenderbim.core.tool.Spatial): tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj ) + @classmethod + def select_object(cls, obj): + obj.select_set(True) + @classmethod def set_active_object(cls, obj): bpy.context.view_layer.objects.active = obj diff --git a/src/blenderbim/test/bim/feature/spatial.feature b/src/blenderbim/test/bim/feature/spatial.feature index dee421803f..20d51badfd 100644 --- a/src/blenderbim/test/bim/feature/spatial.feature +++ b/src/blenderbim/test/bim/feature/spatial.feature @@ -53,3 +53,16 @@ Scenario: Select container And I press "bim.assign_container(structure={site})" When I press "bim.select_container" Then nothing happens + +Scenario: Select similar container + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.enable_editing_container" + And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()" + And I press "bim.assign_container(structure={site})" + When I press "bim.select_similar_container" + Then nothing happens diff --git a/src/blenderbim/test/core/test_spatial.py b/src/blenderbim/test/core/test_spatial.py index 7ac5a16248..5bec7131a7 100644 --- a/src/blenderbim/test/core/test_spatial.py +++ b/src/blenderbim/test/core/test_spatial.py @@ -103,3 +103,13 @@ class TestSelectContainer: ifc.get_object("container").should_be_called().will_return("container_obj") spatial.set_active_object("container_obj").should_be_called() subject.select_container(ifc, spatial, obj="obj") + + +class TestSelectSimilarContainer: + def test_run(self, ifc, spatial): + ifc.get_entity("obj").should_be_called().will_return("element") + spatial.get_container("element").should_be_called().will_return("container") + spatial.get_decomposed_elements("container").should_be_called().will_return(["contained_element"]) + ifc.get_object("contained_element").should_be_called().will_return("contained_obj") + spatial.select_object("contained_obj").should_be_called() + subject.select_similar_container(ifc, spatial, obj="obj") diff --git a/src/blenderbim/test/tool/test_spatial.py b/src/blenderbim/test/tool/test_spatial.py index cba51c6ed8..d3f097fcb7 100644 --- a/src/blenderbim/test/tool/test_spatial.py +++ b/src/blenderbim/test/tool/test_spatial.py @@ -128,6 +128,15 @@ class TestGetContainer(NewFile): assert subject.get_container(wall) == site +class TestGetDecomposedElements(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + site = ifc.createIfcSite() + wall = ifc.createIfcWall() + ifcopenshell.api.run("spatial.assign_container", ifc, product=wall, relating_structure=site) + assert subject.get_decomposed_elements(site) == [wall] + + class TestGetObjectMatrix(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) @@ -192,6 +201,14 @@ class TestRunSpatialAssignContainer(NewFile): pass +class TestSelectObject(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + bpy.context.scene.collection.objects.link(obj) + subject.select_object(obj) + assert obj in bpy.context.selected_objects + + class TestSetActiveObject(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None)