diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index 77d2652f65..cbe069f513 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -237,15 +237,36 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.select_decomposed_elements" bl_label = "Select Children" bl_options = {"REGISTER", "UNDO"} + should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"}) container: bpy.props.IntProperty() ifc_class: bpy.props.StringProperty() + relating_type: bpy.props.IntProperty() @classmethod def description(cls, context, operator): - return f"Select all elements contained in this {operator.ifc_class}" + return "Select all contained elements filtered by this type" + "\nALT+CLICK to select all contained elements" + + def invoke(self, context, event): + if event.type == "LEFTMOUSE" and event.alt: + self.should_filter = False + return self.execute(context) def _execute(self, context): - core.select_decomposed_elements(tool.Spatial, container=tool.Ifc.get().by_id(self.container)) + if self.should_filter: + ifc_class = self.ifc_class + relating_type = tool.Ifc.get().by_id(self.relating_type) if self.relating_type else None + is_untyped = self.relating_type == 0 + else: + ifc_class = "" + relating_type = None + is_untyped = False + core.select_decomposed_elements( + tool.Spatial, + container=tool.Ifc.get().by_id(self.container), + ifc_class=ifc_class, + relating_type=relating_type, + is_untyped=is_untyped, + ) class SetDefaultContainer(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index 2afee7fa23..f56ba3be2f 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -123,6 +123,7 @@ class Element(PropertyGroup): name: StringProperty(name="Name") is_class: BoolProperty(name="Is Class", default=False) is_type: BoolProperty(name="Is Type", default=False) + ifc_definition_id: IntProperty(name="IFC Definition ID") total: IntProperty(name="Total") @@ -144,3 +145,8 @@ class BIMSpatialDecompositionProperties(PropertyGroup): def active_container(self): if self.containers and self.active_container_index < len(self.containers): return self.containers[self.active_container_index] + + @property + def active_element(self): + if self.elements and self.active_element_index < len(self.elements): + return self.elements[self.active_element_index] diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index 0d8451bafa..211aa64cee 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -161,15 +161,20 @@ class BIM_PT_spatial_decomposition(Panel): if not self.props.total_elements: row = self.layout.row(align=True) - row.label(text="No Contained Elements", icon="FILE_3D") + row.label(text=f"{self.props.active_container.ifc_class} > No Contained Elements", icon="FILE_3D") row.prop(self.props, "should_include_children", text="", icon="OUTLINER") return row = self.layout.row(align=True) - row.label(text=f"{self.props.total_elements} Contained Elements", icon="FILE_3D") + row.label(text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Contained Elements", icon="FILE_3D") row.prop(self.props, "should_include_children", text="", icon="OUTLINER") op = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="") - op.ifc_class = self.props.active_container.ifc_class + if self.props.active_element.is_class: + op.ifc_class = self.props.active_element.name + op.relating_type = 0 + elif self.props.active_element.is_type: + op.ifc_class = "" + op.relating_type = self.props.active_element.ifc_definition_id op.container = ifc_definition_id self.layout.template_list( @@ -187,7 +192,20 @@ class BIM_UL_containers_manager(UIList): if item: row = layout.row(align=True) self.draw_hierarchy(row, item) - row.prop(item, "name", emboss=False, text="") + icon = { + "IfcProject": "FILE", + "IfcSite": "WORLD", + "IfcBuilding": "HOME", + "IfcBuildingStorey": "LINENUMBERS_OFF", + "IfcSpace": "ANTIALIASED", + "IfcFacilityPart": "MOD_FLUID", + "IfcBridgePart": "MOD_FLUID", + "IfcFacilityPartCommon": "MOD_FLUID", + "IfcMarinePart": "MOD_FLUID", + "IfcRailwayPart": "MOD_FLUID", + "IfcRoadPart": "MOD_FLUID", + }.get(item.ifc_class, "META_PLANE") + row.prop(item, "name", emboss=False, text="", icon=icon) if item.long_name: row.prop(item, "long_name", emboss=False, text="") col = row.column() @@ -208,24 +226,6 @@ class BIM_UL_containers_manager(UIList): ) else: row.label(text="", icon="BLANK1") - if item.ifc_class == "IfcProject": - row.label(text="", icon="FILE") - else: - icon = { - "IfcSite": "WORLD", - "IfcBuilding": "HOME", - "IfcBuildingStorey": "LINENUMBERS_OFF", - "IfcSpace": "ANTIALIASED", - "IfcFacilityPart": "MOD_FLUID", - "IfcBridgePart": "MOD_FLUID", - "IfcFacilityPartCommon": "MOD_FLUID", - "IfcMarinePart": "MOD_FLUID", - "IfcRailwayPart": "MOD_FLUID", - "IfcRoadPart": "MOD_FLUID", - }.get(item.ifc_class, "META_PLANE") - op = row.operator("bim.select_decomposed_elements", icon=icon, text="", emboss=False) - op.ifc_class = item.ifc_class - op.container = item.ifc_definition_id class BIM_UL_elements(UIList): @@ -240,7 +240,6 @@ class BIM_UL_elements(UIList): col.label(text=str(item.total)) elif item.is_type: row.label(text="", icon="BLANK1") - row.label(text="", icon="DISCLOSURE_TRI_DOWN") row.label(text=item.name) col = row.column() col.alignment = "RIGHT" diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index ed2ff20dff..a256a9d9de 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -149,8 +149,21 @@ def delete_container( spatial.import_spatial_decomposition() -def select_decomposed_elements(spatial: tool.Spatial, container: ifcopenshell.entity_instance) -> None: - spatial.select_products(spatial.get_decomposed_elements(container)) +def select_decomposed_elements( + spatial: tool.Spatial, + container: ifcopenshell.entity_instance, + ifc_class: str | None = None, + relating_type: ifcopenshell.entity_instance | None = None, + is_untyped: bool = False, +) -> None: + elements = spatial.get_decomposed_elements(container) + if ifc_class: + elements = spatial.filter_elements_by_class(elements, ifc_class) + elif relating_type: + elements = spatial.filter_elements_by_relating_type(elements, relating_type) + elif is_untyped: + elements = spatial.filter_elements_by_untyped(elements) + spatial.select_products(elements) def generate_space(ifc: tool.Ifc, model: tool.Model, root: tool.Root, spatial: tool.Spatial, type: tool.Type) -> None: diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index 5102b1f27b..642c9b4962 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -39,7 +39,7 @@ import json from math import pi from mathutils import Vector, Matrix from shapely import Polygon -from typing import Generator, Optional, Union, Literal, Any +from typing import Generator, Optional, Union, Literal, Any, List class Spatial(blenderbim.core.tool.Spatial): @@ -244,9 +244,10 @@ class Spatial(blenderbim.core.tool.Spatial): continue element_type = ifcopenshell.util.element.get_type(element) ifc_class = element.is_a() + ifc_definition_id = element_type.id() if element_type else 0 type_name = element_type.Name or "Unnamed" if element_type else "Untyped" - results.setdefault(ifc_class, {}).setdefault(type_name, 0) - results[ifc_class][type_name] += 1 + results.setdefault(ifc_class, {}).setdefault(ifc_definition_id, {"total": 0, "type_name": type_name}) + results[ifc_class][ifc_definition_id]["total"] += 1 total_elements = 0 for ifc_class in sorted(results.keys()): @@ -254,17 +255,34 @@ class Spatial(blenderbim.core.tool.Spatial): new.name = ifc_class new.is_class = True total = 0 - for type_name in sorted(results[ifc_class].keys()): + for ifc_definition_id in sorted( + results[ifc_class].keys(), key=lambda x: results[ifc_class][x]["type_name"] + ): new2 = props.elements.add() - new2.name = type_name new2.is_type = True - new2.total = results[ifc_class][type_name] + new2.name = results[ifc_class][ifc_definition_id]["type_name"] + new2.total = results[ifc_class][ifc_definition_id]["total"] + new2.ifc_definition_id = ifc_definition_id total += new2.total new.total = total total_elements += total props.total_elements = total_elements + @classmethod + def filter_elements_by_class(cls, elements: List[ifcopenshell.entity_instance], ifc_class: str): + return [e for e in elements if e.is_a(ifc_class)] + + @classmethod + def filter_elements_by_relating_type( + cls, elements: List[ifcopenshell.entity_instance], relating_type: ifcopenshell.entity_instance + ): + return [e for e in elements if ifcopenshell.util.element.get_type(e) == relating_type] + + @classmethod + def filter_elements_by_untyped(cls, elements: List[ifcopenshell.entity_instance]): + return [e for e in elements if not ifcopenshell.util.element.get_type(e)] + @classmethod def import_spatial_decomposition(cls) -> None: props = bpy.context.scene.BIMSpatialDecompositionProperties