From a61eae5f9837a0a5898cdf440d591d6a456c2a3b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 22 Jul 2023 16:36:19 +1000 Subject: [PATCH] More powerful search interface that uses the new facet selector syntax --- .../blenderbim/bim/module/search/__init__.py | 23 ++-- .../blenderbim/bim/module/search/operator.py | 115 ++++++++++++++++++ .../blenderbim/bim/module/search/prop.py | 28 ++++- .../blenderbim/bim/module/search/ui.py | 54 +++++++- 4 files changed, 210 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index 49e3c796b7..6797a0df44 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -20,24 +20,31 @@ import bpy from . import ui, prop, operator classes = ( - operator.ActivateIfcClassFilter, operator.ActivateIfcBuildingStoreyFilter, + operator.ActivateIfcClassFilter, + operator.AddFilter, + operator.AddFilterGroup, + operator.AddToIfcGroup, operator.ColourByAttribute, operator.ColourByClass, operator.ColourByPset, - operator.ToggleFilterSelection, + operator.FilterModelElements, + operator.IfcSelector, + operator.LoadQuery, + operator.OpenQueryLibrary, + operator.RemoveFilter, + operator.RemoveFilterGroup, operator.ResetObjectColours, + operator.SaveSelectorQuery, + operator.Search, operator.SelectAttribute, operator.SelectGlobalId, operator.SelectIfcClass, operator.SelectPset, operator.ShowAllElements, - operator.FilterModelElements, - operator.IfcSelector, - operator.SaveSelectorQuery, - operator.OpenQueryLibrary, - operator.LoadQuery, - operator.AddToIfcGroup, + operator.ToggleFilterSelection, + prop.BIMFacet, + prop.BIMFilterGroup, prop.BIMFilterClasses, prop.BIMFilterBuildingStoreys, prop.BIMSearchProperties, diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index 2f32988a8b..4e8d123092 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -20,6 +20,7 @@ import re import bpy import ifcopenshell import ifcopenshell.util.element +import ifcopenshell.util.selector from ifcopenshell.util.selector import Selector import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore @@ -69,6 +70,120 @@ def does_keyword_exist(pattern, string, context): return True +class AddFilterGroup(Operator): + bl_idname = "bim.add_filter_group" + bl_label = "Add Filter Group" + + def execute(self, context): + context.scene.BIMSearchProperties.filter_groups.add() + return {"FINISHED"} + + +class RemoveFilterGroup(Operator): + bl_idname = "bim.remove_filter_group" + bl_label = "Remove Filter Group" + index: IntProperty() + + def execute(self, context): + props = context.scene.BIMSearchProperties + props.filter_groups.remove(self.index) + return {"FINISHED"} + + +class RemoveFilter(Operator): + bl_idname = "bim.remove_filter" + bl_label = "Remove Filter Group" + group_index: IntProperty() + index: IntProperty() + + def execute(self, context): + props = context.scene.BIMSearchProperties + props.filter_groups[self.group_index].filters.remove(self.index) + return {"FINISHED"} + + +class AddFilter(Operator): + bl_idname = "bim.add_filter" + bl_label = "Add Filter" + index: IntProperty() + type: StringProperty() + + def execute(self, context): + new = context.scene.BIMSearchProperties.filter_groups[self.index].filters.add() + new.type = self.type + return {"FINISHED"} + + +class Search(Operator): + bl_idname = "bim.search" + bl_label = "Search" + + def execute(self, context): + props = context.scene.BIMSearchProperties + query = [] + for filter_group in props.filter_groups: + filter_group_query = [] + has_instance_or_entity_filter = False + for ifc_filter in filter_group.filters: + if not ifc_filter.value: + continue + if ifc_filter.type == "instance": + has_instance_or_entity_filter = True + filter_group_query.append(ifc_filter.value) + elif ifc_filter.type == "entity": + has_instance_or_entity_filter = True + filter_group_query.append(ifc_filter.value) + elif ifc_filter.type == "attribute": + if not ifc_filter.name: + continue + comparison, value = self.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"{ifc_filter.name}{comparison}{value}") + elif ifc_filter.type == "type": + comparison, value = self.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"type{comparison}{value}") + elif ifc_filter.type == "material": + comparison, value = self.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"material{comparison}{value}") + elif ifc_filter.type == "property": + if not ifc_filter.pset or not ifc_filter.name: + continue + pset = self.wrap_value(ifc_filter, ifc_filter.pset) + name = self.wrap_value(ifc_filter, ifc_filter.name) + comparison, value = self.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"{pset}.{name}{comparison}{value}") + elif ifc_filter.type == "classification": + comparison, value = self.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"classification{comparison}{value}") + elif ifc_filter.type == "location": + comparison, value = self.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"location{comparison}{value}") + if not has_instance_or_entity_filter: + filter_group_query.insert(0, "IfcElement") + query.append(", ".join(filter_group_query)) + query = " + ".join(query) + results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query) + + total_selected = 0 + for element in results: + obj = tool.Ifc.get_object(element) + if obj: + obj.select_set(True) + self.report({"INFO"}, f"{len(results)} Results") + return {"FINISHED"} + + def get_comparison_and_value(self, ifc_filter): + if ifc_filter.value.startswith("!="): + return ("!=", self.wrap_value(ifc_filter, ifc_filter.value[2:].strip())) + return ("=", self.wrap_value(ifc_filter, ifc_filter.value.strip())) + + def wrap_value(self, ifc_filter, value): + if value.startswith("/") and value.endswith("/"): + return value + elif value in ("NULL", "TRUE", "FALSE"): + return value + return '"' + value + '"' + + class SelectGlobalId(Operator): """Click to select the objects that match with the given Global ID""" diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index 8fd8cc8bb7..c071b5284b 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -79,7 +79,33 @@ class BIMFilterBuildingStoreys(PropertyGroup): unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects") +class BIMFacet(PropertyGroup): + name: StringProperty(name="Type") + pset: StringProperty(name="Type") + value: StringProperty(name="Type") + type: StringProperty(name="Type") + comparison: StringProperty(name="Comparison") + + +class BIMFilterGroup(PropertyGroup): + filters: CollectionProperty(type=BIMFacet, name="filters") + + class BIMSearchProperties(PropertyGroup): + filter_query: StringProperty(name="Filter Query") + filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups") + facet: EnumProperty( + items=[ + ("entity", "Class", "", "FILE_3D", 0), + ("attribute", "Attribute", "", "COPY_ID", 1), + ("property", "Property", "", "PROPERTIES", 2), + ("material", "Material", "", "MATERIAL", 3), + ("classification", "Classification", "", "OUTLINER", 4), + ("location", "Location", "", "PACKAGE", 5), + ("type", "Type", "", "FILE_VOLUME", 6), + ("instance", "GlobalId", "", "GRIP", 7), + ], + ) should_use_regex: BoolProperty(name="Search With Regex", default=False) should_ignorecase: BoolProperty(name="Search Ignoring Case", default=True) global_id: StringProperty(name="GlobalId") @@ -133,7 +159,7 @@ def load_selection_options(self, context): elif self.selector == "Attribute": attributes = ["GlobalId", "Name", "Description", "ObjectType", "Tag", "PredefinedType"] for a in attributes: - options.append((a, a, "")) + options.append((a, a, "")) elif load_option == "sub_options": if self.selector in ["IfcSpatialElement", "IfcElementType"]: diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py index fc9b016012..ead9999e24 100644 --- a/src/blenderbim/blenderbim/bim/module/search/ui.py +++ b/src/blenderbim/blenderbim/bim/module/search/ui.py @@ -24,7 +24,6 @@ from blenderbim.bim.ifc import IfcStore class BIM_PT_search(Panel): bl_label = "Search" bl_idname = "BIM_PT_search" - # bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" @@ -33,6 +32,59 @@ class BIM_PT_search(Panel): def draw(self, context): props = context.scene.BIMSearchProperties + row = self.layout.row(align=True) + row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD") + + for i, filter_group in enumerate(props.filter_groups): + box = self.layout.box() + + row = box.row(align=True) + row.prop(props, "facet", text="") + op = row.operator("bim.add_filter", text="Add Filter", icon="ADD") + op.type = props.facet + op.index = i + + for j, ifc_filter in enumerate(filter_group.filters): + if ifc_filter.type == "entity": + row = box.row(align=True) + row.prop(ifc_filter, "value", text="", icon="FILE_3D") + elif ifc_filter.type == "attribute": + row = box.row(align=True) + row.prop(ifc_filter, "name", text="", icon="COPY_ID") + row.prop(ifc_filter, "value", text="") + elif ifc_filter.type == "type": + row = box.row(align=True) + row.prop(ifc_filter, "value", text="", icon="FILE_VOLUME") + elif ifc_filter.type == "material": + row = box.row(align=True) + row.prop(ifc_filter, "value", text="", icon="MATERIAL") + elif ifc_filter.type == "property": + row = box.row(align=True) + row.prop(ifc_filter, "pset", text="", icon="PROPERTIES") + row.prop(ifc_filter, "name", text="") + row.prop(ifc_filter, "value", text="") + elif ifc_filter.type == "classification": + row = box.row(align=True) + row.prop(ifc_filter, "value", text="", icon="OUTLINER") + elif ifc_filter.type == "location": + row = box.row(align=True) + row.prop(ifc_filter, "name", text="", icon="PACKAGE") + elif ifc_filter.type == "instance": + row = box.row(align=True) + row.prop(ifc_filter, "value", text="", icon="GRIP") + op = row.operator("bim.remove_filter", text="", icon="X") + op.group_index = i + op.index = j + + row = box.row() + row.operator("bim.remove_filter_group", icon="X").index = i + + if len(props.filter_groups): + row = self.layout.row(align=True) + row.operator("bim.search", text="Search", icon="VIEWZOOM") + + return # Temporary for now whilst searching is being upgraded. + row = self.layout.row() row.prop(props, "should_use_regex") row = self.layout.row()