diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 60d8572ece..0b699440cc 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -265,10 +265,12 @@ class BimTool(WorkSpaceTool): row.label(text="Mode") row = layout.row(align=True) row.label(text="", icon="EVENT_ALT") - row.label(text="Opening", icon="EVENT_O") + row.label(text="", icon="EVENT_O") + row.operator("bim.hotkey", text="Void").hotkey = "A_O" row = layout.row(align=True) row.label(text="", icon="EVENT_ALT") - row.label(text="Decomposition", icon="EVENT_D") + row.label(text="", icon="EVENT_D") + row.operator("bim.hotkey", text="Decomposition").hotkey = "A_D" class Hotkey(bpy.types.Operator, tool.Ifc.Operator): @@ -401,7 +403,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): self.props.z = self.z def hotkey_A_D(self): - bpy.ops.bim.toggle_decomposition_parenting() + bpy.ops.bim.select_decomposition() def hotkey_A_O(self): if AuthoringData.data["has_visible_openings"]: diff --git a/src/blenderbim/blenderbim/bim/module/void/__init__.py b/src/blenderbim/blenderbim/bim/module/void/__init__.py index 93ccc6b148..0d1bdfcad2 100644 --- a/src/blenderbim/blenderbim/bim/module/void/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/void/__init__.py @@ -20,11 +20,11 @@ import bpy from . import ui, prop, operator classes = ( - operator.AddOpening, - operator.RemoveOpening, operator.AddFilling, + operator.AddOpening, operator.RemoveFilling, - operator.ToggleDecompositionParenting, + operator.RemoveOpening, + operator.SelectDecomposition, prop.VoidProperties, ui.BIM_PT_voids, ui.BIM_PT_booleans, diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 74319f7707..533146cd7b 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -182,55 +182,18 @@ class RemoveFilling(bpy.types.Operator): return {"FINISHED"} -class ToggleDecompositionParenting(bpy.types.Operator): - bl_idname = "bim.toggle_decomposition_parenting" - bl_label = "Toggle Decomposition Parenting" +class SelectDecomposition(bpy.types.Operator): + bl_idname = "bim.select_decomposition" + bl_label = "Select Decomposition" bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty() def execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - self.file = IfcStore.get_file() - is_parenting = None - - self.decompositions = {} - self.load_decompositions(obj) - - for parent, children in self.decompositions.items(): - bpy.ops.bim.dynamically_void_product(obj=parent.name) - for child in children: - bpy.ops.bim.dynamically_void_product(obj=child.name) - if is_parenting is None: - is_parenting = not bool(child.parent) - - if is_parenting: - child.parent = parent - child.matrix_parent_inverse = parent.matrix_world.inverted() - else: - parent_matrix_world = child.matrix_world.copy() - child.parent = None - child.matrix_world = parent_matrix_world - + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + continue + for subelement in ifcopenshell.util.element.get_decomposition(element): + subobj = tool.Ifc.get_object(subelement) + if subobj: + subobj.select_set(True) return {"FINISHED"} - - def load_decompositions(self, parent_obj): - element = self.file.by_id(parent_obj.BIMObjectProperties.ifc_definition_id) - for rel in self.file.get_inverse(element): - if rel.is_a("IfcRelDecomposes"): - if rel[4] != element: - continue - if isinstance(rel[5], tuple): - for related_object in rel[5]: - self.add_decomposition(parent_obj, related_object) - else: - self.add_decomposition(parent_obj, rel[5]) - elif rel.is_a("IfcRelFillsElement"): - if rel.RelatingOpeningElement != element: - continue - self.add_decomposition(parent_obj, rel.RelatedBuildingElement) - - def add_decomposition(self, parent_obj, child_element): - child_obj = IfcStore.get_element(child_element.id()) - if child_obj: - self.decompositions.setdefault(parent_obj, []).append(child_obj) - self.load_decompositions(child_obj) diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 269b425312..b0b341aac8 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -323,7 +323,9 @@ def get_container(element, should_get_direct=False): def get_referenced_structures(element): """ - Retreives a list of referenced structural elements + Retreives a list of referenced spatial elements, typically useful for + multistorey elements or elements that span multiple spaces or in-between + spaces. :param element: The IFC element :type element: ifcopenshell.entity_instance.entity_instance @@ -340,7 +342,9 @@ def get_referenced_structures(element): def get_decomposition(element): """ - Retrieves the decomposition of an element. + Retrieves all subelements of an element based on the spatial decomposition + hierarchy. This includes all subspaces and elements contained in subspaces, + parts of an aggreate, all openings, and all fills of any openings. :param element: The IFC element :return: The decomposition of the element @@ -360,6 +364,12 @@ def get_decomposition(element): for rel in getattr(element, "IsDecomposedBy", []): queue.extend(rel.RelatedObjects) results.extend(rel.RelatedObjects) + for rel in getattr(element, "HasOpenings", []): + queue.append(rel.RelatedOpeningElement) + results.append(rel.RelatedOpeningElement) + for rel in getattr(element, "HasFillings", []): + queue.append(rel.RelatedBuildingElement) + results.append(rel.RelatedBuildingElement) return results diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 8eb80df8f0..33a3e3cc39 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -561,6 +561,16 @@ class TestGetDecompositionIFC4(test.bootstrap.IFC4): assert element in results assert subelement in results + def test_getting_openings_and_fills_of_an_element(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + subsubelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWindow") + ifcopenshell.api.run("void.add_opening", self.file, element=element, opening=subelement) + ifcopenshell.api.run("void.add_filling", self.file, element=subsubelement, opening=subelement) + results = subject.get_decomposition(element) + assert subelement in results + assert subsubelement in results + class TestGetAggregateIFC4(test.bootstrap.IFC4): def test_getting_the_containing_aggregate_of_a_subelement(self):