From a4a2497f33862fd113ae93e6906df52233da97e4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 23 Aug 2024 13:49:45 +1000 Subject: [PATCH] You can now filter and search in the spatial decomposition panel --- .../bonsai/bim/module/spatial/operator.py | 21 +++++----- src/bonsai/bonsai/bim/module/spatial/prop.py | 2 + src/bonsai/bonsai/bim/module/spatial/ui.py | 24 +++++++++--- src/bonsai/bonsai/core/spatial.py | 8 +--- src/bonsai/bonsai/tool/spatial.py | 39 +++++++++++++------ 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/spatial/operator.py b/src/bonsai/bonsai/bim/module/spatial/operator.py index 248145b409..9ba7831940 100644 --- a/src/bonsai/bonsai/bim/module/spatial/operator.py +++ b/src/bonsai/bonsai/bim/module/spatial/operator.py @@ -249,8 +249,6 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator): 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): @@ -262,20 +260,25 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator): return self.execute(context) def _execute(self, context): + ifc_class = relating_type = is_untyped = None 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 + active_element = context.scene.BIMSpatialDecompositionProperties.active_element + if active_element.is_class: + ifc_class = active_element.name + elif relating_type := active_element.ifc_definition_id: + ifc_class = active_element.ifc_class + relating_type = tool.Ifc.get().by_id(relating_type) + else: + ifc_class = active_element.ifc_class + is_untyped = True + element_filter = context.scene.BIMSpatialDecompositionProperties.element_filter 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, + element_filter = element_filter ) diff --git a/src/bonsai/bonsai/bim/module/spatial/prop.py b/src/bonsai/bonsai/bim/module/spatial/prop.py index 22c8ec4c1c..a95aef86ef 100644 --- a/src/bonsai/bonsai/bim/module/spatial/prop.py +++ b/src/bonsai/bonsai/bim/module/spatial/prop.py @@ -107,6 +107,7 @@ class BIMContainer(PropertyGroup): class Element(PropertyGroup): name: StringProperty(name="Name") + ifc_class: 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") @@ -118,6 +119,7 @@ class BIMSpatialDecompositionProperties(PropertyGroup): contracted_containers: StringProperty(name="Contracted containers", default="[]") expanded_containers: StringProperty(name="Expanded containers", default="[]") active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index) + element_filter: StringProperty(name="Element Filter", default="", options={"TEXTEDIT_UPDATE"}) elements: CollectionProperty(name="Elements", type=Element) active_element_index: IntProperty(name="Active Element Index") total_elements: IntProperty(name="Total Elements") diff --git a/src/bonsai/bonsai/bim/module/spatial/ui.py b/src/bonsai/bonsai/bim/module/spatial/ui.py index 213b48dfe0..1476914b9d 100644 --- a/src/bonsai/bonsai/bim/module/spatial/ui.py +++ b/src/bonsai/bonsai/bim/module/spatial/ui.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +import bpy from bpy.types import Panel, UIList from bonsai.bim.module.spatial.data import SpatialData, SpatialDecompositionData import bonsai.tool as tool @@ -169,12 +170,6 @@ class BIM_PT_spatial_decomposition(Panel): ) row.prop(self.props, "should_include_children", text="", icon="OUTLINER") op = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="") - 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( @@ -184,6 +179,7 @@ class BIM_PT_spatial_decomposition(Panel): "elements", self.props, "active_element_index", + sort_lock=True, ) @@ -229,6 +225,9 @@ class BIM_UL_containers_manager(UIList): class BIM_UL_elements(UIList): + def __init__(self): + self.use_filter_show = True + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: row = layout.row(align=True) @@ -244,3 +243,16 @@ class BIM_UL_elements(UIList): col = row.column() col.alignment = "RIGHT" col.label(text=str(item.total)) + + def draw_filter(self, context, layout): + row = layout.row() + row.prop(context.scene.BIMSpatialDecompositionProperties, "element_filter", text="", icon="VIEWZOOM") + + def filter_items(self, context, data, propname): + items = getattr(data, propname) + filter_name = context.scene.BIMSpatialDecompositionProperties.element_filter + filtered = bpy.types.UI_UL_list.filter_items_by_name(filter_name, self.bitflag_filter_item, items, "name") + return filtered, [] + + def get_filter_name(self): + return self.filter_name diff --git a/src/bonsai/bonsai/core/spatial.py b/src/bonsai/bonsai/core/spatial.py index 955711d45c..4bad423e30 100644 --- a/src/bonsai/bonsai/core/spatial.py +++ b/src/bonsai/bonsai/core/spatial.py @@ -147,14 +147,10 @@ def select_decomposed_elements( ifc_class: str | None = None, relating_type: ifcopenshell.entity_instance | None = None, is_untyped: bool = False, + element_filter: str | None = None, ) -> 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) + elements = spatial.filter_elements(elements, ifc_class, relating_type, is_untyped, element_filter) spatial.select_products(elements) diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index ebc3a364e8..499508e9bd 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -233,6 +233,7 @@ class Spatial(bonsai.core.tool.Spatial): new2 = props.elements.add() new2.is_type = True new2.name = results[ifc_class][ifc_definition_id]["type_name"] + new2.ifc_class = ifc_class new2.total = results[ifc_class][ifc_definition_id]["total"] new2.ifc_definition_id = ifc_definition_id total += new2.total @@ -242,18 +243,32 @@ class Spatial(bonsai.core.tool.Spatial): 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)] + def filter_elements( + cls, + elements: List[ifcopenshell.entity_instance], + ifc_class: str | None, + relating_type: ifcopenshell.entity_instance | None, + is_untyped: bool | None, + keyword: str | None, + ) -> filter[ifcopenshell.entity_instance]: + keyword = keyword.lower() if keyword else keyword + def filter_element(element): + if ifc_class: + if not element.is_a(ifc_class): + return False + element_type = ifcopenshell.util.element.get_type(element) + if relating_type: + if relating_type != element_type: + return False + if is_untyped: + if element_type is not None: + return False + if keyword: + type_name = getattr(element_type, "Name", "") or "" + if keyword not in f"{element.is_a()} {type_name}".lower(): + return False + return True + return filter(filter_element, elements) @classmethod def import_spatial_decomposition(cls) -> None: