From e3d9dd747d0f618acce1e2a6e8ac6703734b9147 Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Mon, 11 Jul 2022 16:33:24 +0200 Subject: [PATCH 1/8] draft endofday 1207 240pm 14/07 draft version cleanup --- .../blenderbim/bim/module/search/__init__.py | 15 +- .../blenderbim/bim/module/search/operator.py | 173 ++++++++++++++++-- .../blenderbim/bim/module/search/prop.py | 153 +++++++++++++++- .../blenderbim/bim/module/search/ui.py | 133 ++++++++++++++ .../ifcopenshell/util/selector.py | 13 +- 5 files changed, 458 insertions(+), 29 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index df128b9a9e..79630de378 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -20,6 +20,7 @@ import bpy from . import ui, prop, operator classes = ( + operator.EditBlenderCollection, operator.ActivateIfcClassFilter, operator.ActivateIfcBuildingStoreyFilter, operator.ColourByAttribute, @@ -31,18 +32,30 @@ classes = ( operator.SelectGlobalId, operator.SelectIfcClass, operator.SelectPset, + operator.Reset3dView, + operator.FilterModelElements, prop.BIMFilterClasses, prop.BIMFilterBuildingStoreys, prop.BIMSearchProperties, + prop.SearchCollection, + prop.SearchQueryFilter, + prop.SearchQuery, + prop.SearchQueryGroup, + prop.IfcSelectorProperties, ui.BIM_PT_search, ui.BIM_UL_ifc_class_filter, ui.BIM_UL_ifc_building_storey_filter, + ui.BIM_PT_IFCSelector ) def register(): - bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties) + bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty( + type=prop.BIMSearchProperties) + bpy.types.Scene.IfcSelectorProperties = bpy.props.PointerProperty( + type=prop.IfcSelectorProperties) def unregister(): del bpy.types.Scene.BIMSearchProperties + del bpy.types.Scene.IfcSelectorProperties diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index 5004f5c7fa..cb0f635511 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 +from ifcopenshell.util.selector import Selector import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from itertools import cycle @@ -54,6 +55,23 @@ def does_keyword_exist(pattern, string, context): return True +class EditBlenderCollection(bpy.types.Operator): + bl_idname = "bim.edit_blender_collection" + bl_label = "Add or Remove blender collection item" + bl_options = {"REGISTER", "UNDO"} + option: bpy.props.StringProperty() + collection: bpy.props.StringProperty() + index: bpy.props.IntProperty() + + def execute(self, context): + if self.option == "add": + getattr(context.bim_prop_group, self.collection).add() + else: + getattr(context.bim_prop_group, self.collection).remove(self.index) + + return {"FINISHED"} + + class SelectGlobalId(bpy.types.Operator): """Click to select the objects that match with the given Global ID""" @@ -86,7 +104,8 @@ class SelectIfcClass(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id or obj.is_library_indirect: continue - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id( + obj.BIMObjectProperties.ifc_definition_id) if does_keyword_exist(self.ifc_class, element.is_a(), context): obj.select_set(True) return {"FINISHED"} @@ -107,10 +126,12 @@ class SelectAttribute(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id( + obj.BIMObjectProperties.ifc_definition_id) if context.scene.BIMSearchProperties.should_ignorecase: data = element.get_info() - value = next((v for k, v in data.items() if k.lower() == attribute_name.lower()), None) + value = next((v for k, v in data.items() + if k.lower() == attribute_name.lower()), None) else: value = getattr(element, attribute_name, None) if does_keyword_exist(pattern, value, context): @@ -134,7 +155,8 @@ class SelectPset(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id( + obj.BIMObjectProperties.ifc_definition_id) psets = ifcopenshell.util.element.get_psets(element) if search_pset_name == "": props = {} @@ -142,8 +164,10 @@ class SelectPset(bpy.types.Operator): else: props = None if context.scene.BIMSearchProperties.should_ignorecase: - props = props or next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) - value = str(next((v for k, v in props.items() if k.lower() == search_prop_name.lower()), None)) + props = props or next( + (v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) + value = str(next((v for k, v in props.items() + if k.lower() == search_prop_name.lower()), None)) else: props = props or psets.get(search_pset_name, {}) value = props.get(search_prop_name, None) @@ -175,10 +199,12 @@ class ColourByAttribute(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id( + obj.BIMObjectProperties.ifc_definition_id) if context.scene.BIMSearchProperties.should_ignorecase: data = element.get_info() - value = next((v for k, v in data.items() if k.lower() == attribute_name.lower()), None) + value = next((v for k, v in data.items() + if k.lower() == attribute_name.lower()), None) else: value = getattr(element, attribute_name, None) if value not in values: @@ -192,7 +218,8 @@ class ColourByAttribute(bpy.types.Operator): def store_state(self, context): areas = [a for a in context.screen.areas if a.type == "VIEW_3D"] if areas: - self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} + self.transaction_data = { + "area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} def rollback(self, data): if data: @@ -227,7 +254,8 @@ class ColourByPset(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id( + obj.BIMObjectProperties.ifc_definition_id) psets = ifcopenshell.util.element.get_psets(element) if search_pset_name == "": props = {} @@ -235,8 +263,10 @@ class ColourByPset(bpy.types.Operator): else: props = None if context.scene.BIMSearchProperties.should_ignorecase: - props = props or next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) - value = str(next((v for k, v in props.items() if k.lower() == search_prop_name.lower()), None)) + props = props or next( + (v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) + value = str(next((v for k, v in props.items() + if k.lower() == search_prop_name.lower()), None)) else: props = props or psets.get(search_pset_name, {}) value = str(props.get(search_prop_name, None)) @@ -251,7 +281,8 @@ class ColourByPset(bpy.types.Operator): def store_state(self, context): areas = [a for a in context.screen.areas if a.type == "VIEW_3D"] if areas: - self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} + self.transaction_data = { + "area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} def rollback(self, data): if data: @@ -284,7 +315,8 @@ class ColourByClass(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id( + obj.BIMObjectProperties.ifc_definition_id) ifc_class = element.is_a() if ifc_class not in ifc_classes: ifc_classes[ifc_class] = next(colours) @@ -297,7 +329,8 @@ class ColourByClass(bpy.types.Operator): def store_state(self, context): areas = [a for a in context.screen.areas if a.type == "VIEW_3D"] if areas: - self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} + self.transaction_data = { + "area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} def rollback(self, data): if data: @@ -324,7 +357,8 @@ class ToggleFilterSelection(bpy.types.Operator): "Click to select/deselect current selection" bl_idname = "bim.toggle_filter_selection" bl_label = "Toggle Filter Selection" - action: bpy.props.EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", ""))) + action: bpy.props.EnumProperty( + items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", ""))) def execute(self, context): props = bpy.context.scene.BIMSearchProperties @@ -383,8 +417,10 @@ class ActivateIfcClassFilter(bpy.types.Operator): else len(bpy.context.scene.BIMSearchProperties.filter_classes), ) row = self.layout.row(align=True) - row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" - row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" + row.operator("bim.toggle_filter_selection", + text="Select All").action = "SELECT" + row.operator("bim.toggle_filter_selection", + text="Deselect All").action = "DESELECT" class ActivateIfcBuildingStoreyFilter(bpy.types.Operator): @@ -431,5 +467,102 @@ class ActivateIfcBuildingStoreyFilter(bpy.types.Operator): else len(bpy.context.scene.BIMSearchProperties.filter_building_storeys), ) row = self.layout.row(align=True) - row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" - row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" + row.operator("bim.toggle_filter_selection", + text="Select All").action = "SELECT" + row.operator("bim.toggle_filter_selection", + text="Deselect All").action = "DESELECT" + + +class Reset3dView(bpy.types.Operator): + """Filter model elements based on selection""" + bl_idname = "bim.reset_3d_view" + bl_label = "Reset 3D View" + + def execute(self, context): + for obj in bpy.data.scenes["Scene"].objects: + obj.hide_set(False) + return {"FINISHED"} + + +class FilterModelElements(bpy.types.Operator): + """Filter model elements based on selection""" + bl_idname = "bim.filter_model_elements" + bl_label = "Filter Model Elements" + option: bpy.props.StringProperty("select|isolate|hide") + + def execute(self, context): + selector = context.scene.IfcSelectorProperties + selection = self.add_groups(selector) + selector.selector_query_syntax = selection + self.update_model_view(context, selection) + return {"FINISHED"} + + def add_groups(self, selector): + selection = '' + for group_index, group in enumerate(selector.groups): + if group_index != 0: + selection += " | " + selection += "(" if len(selector.groups) >1 else "" + selection = self.add_queries(selection, group) + + selection += ")" if len(selector.groups) >1 else "" + return selection + + def add_queries(self, selection, group): + for query_index, query in enumerate(group.queries): + if query_index != 0: + selection += " & " if query.and_or == "and" else " | " + + if query.selector == "IFC Class": + active_option = query.active_option.split(": ")[1] + selection += f".{active_option}" + selection = self.add_filters(selection, query) + + elif query.selector == "GlobalId": + selection += f"#{query.global_id}" + + elif query.selector == "IfcElementType": + index = int(query.active_sub_option.split(":")[0]) + selection += f"* #{query.sub_options[index].global_id}" + + elif query.selector == "IfcSpatialElement": + index = int(query.active_sub_option.split(":")[0]) + selection += f"@ #{query.sub_options[index].global_id}" + return selection + + def add_filters(self, selection, query): + for f_index, f in enumerate(query.filters): + + if f_index !=0: + selection += " & " if f.and_or == "and" else " | " + selection += f".{query.active_option}" + + selection += "[" + + if f.selector == "Pset-Property": + selection += f'{f.active_option}.{f.active_sub_option} {"!" if f.negation else ""} ="{f.value}"' + elif f.selector == "Attribute": + selection += f'{f.attribute} {"!" if f.negation else ""}= "{f.value}"' + + selection += "]" + return selection + + def update_model_view(self, context, selection): + query = Selector.parse(IfcStore.file, selection) + sel_element_ids = [e.id() for e in query] + bpy.ops.object.select_all(action='DESELECT') + + + for obj in bpy.data.scenes["Scene"].objects: + obj.hide_set(False) # reset 3d view + + if self.option == "select": + if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids: + obj.select_set(True) + elif self.option == "isolate": + if obj.BIMObjectProperties.ifc_definition_id not in sel_element_ids: + obj.hide_set(True) + elif self.option == "hide": + if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids: + obj.hide_set(True) + diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index cf97d4f01a..a060514c81 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -16,10 +16,13 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import bpy +from ifcopenshell import util +from ifcopenshell.util.selector import Selector import blenderbim.tool as tool -from blenderbim.bim.prop import ObjProperty +from blenderbim.bim.prop import ObjProperty, StrProperty +from blenderbim.bim.ifc import IfcStore from bpy.types import PropertyGroup +from blenderbim.tool.ifc import Ifc from . import ui, prop, operator from bpy.props import ( PointerProperty, @@ -90,3 +93,149 @@ class BIMSearchProperties(PropertyGroup): filter_classes_index: IntProperty(name="Filter Classes Index") filter_building_storeys: CollectionProperty(type=BIMFilterBuildingStoreys, name="Filter Level") filter_building_storeys_index: IntProperty(name="Filter Level Index") + + +def get_classes(self, ifc_product): + declaration = tool.Ifc.schema().declaration_by_name(ifc_product) + declarations = util.schema.get_subtypes(declaration) + names = [d.name() for d in declarations] + return [(c, c, "") for c in sorted(names)] + + +def load_sub_options(self, context): + if self.selector not in ["IfcClass"]: + self.load_option = "sub_options" + load_selection_options(self, context) + +def load_selection_options(self, context): + ifc = IfcStore.file + load_option = self.load_option + op = getattr(self, load_option) + op.clear() + + options = [] + + if load_option == "options": + self.sub_options.clear() + if self.selector == "IFC Class": + options = get_classes(self, "IfcElement") + options.append(("IfcSpace", "IfcSpace","")) + elif self.selector == "IfcSpatialElement": + options = get_classes(self, "IfcSpatialElement") + elif self.selector == "IfcElementType": + options = get_classes(self, "IfcElementType") + elif self.selector == "GlobalId": + return + elif self.selector == "IfcPropertySet": + psets = Selector.parse(ifc, ".IfcPropertySet") + options = set([o.Name for o in psets]) + + elif load_option == "sub_options": + if self.selector in ["IfcSpatialElement", "IfcElementType"]: + active_option = self.active_option.split(": ")[1] + options = Selector.parse(ifc, f".{active_option}") + + elif self.selector == "IfcPropertySet": + options = set() + active_pset = self.active_option.split(": ")[1] + psets_in_file = Selector.parse(ifc, f'.IfcPropertySet[Name="{active_pset}"]') + for pset in psets_in_file: + for prop in pset.HasProperties: + options.add(prop.Name) + + + for index, option in enumerate(options): + new = op.add() + if self.selector in ["IfcSpatialElement", "IfcElementType"]: + if self.load_option == "sub_options": + new.name = f"{index}: {option.Name}" + new.global_id = option.GlobalId + else: + new.name = f"{index}: {option[0]}" + elif self.selector == "IfcPropertySet": + new.name = f"{index}: {option}" + else: + new.name = f"{index}: {option[0]}" + + self.load_option = "options" + + +def load_spatial_elements(self, context): + ifc = IfcStore.file + col_items = Selector.parse(ifc, f".{self.selected_spatial_element}") + collection = getattr(self, "sub_spatial_elements", None) + collection.clear() + for index, c in enumerate(col_items): + new = collection.add() + new.name = f"{index}-{c.Name}" + + +class SearchCollection(PropertyGroup): + name: StringProperty() + long_name: StringProperty() + global_id: StringProperty() + +class IfcSelector: + and_or: EnumProperty( + items=[(i, i, i) for i in ["and", "or"]], + ) + negation: BoolProperty(name="not") + comparison: EnumProperty( + items=[ + ("=", "equal to", ""), + ("*=", "contains", ""), + (">=", "greater than or equal to", ""), + ("<=", "lesser than or equal to", ""), + (">", "greater than", ""), + ("<", "less than", ""), + ], + ) + load_option: StringProperty(default="options") + + options: CollectionProperty(type=SearchCollection) + active_option: StringProperty(update=load_sub_options) + + sub_options: CollectionProperty(type=SearchCollection) + active_sub_option: StringProperty() + + value: StringProperty() + + +class SearchQueryFilter(PropertyGroup, IfcSelector): + selector: EnumProperty( + items=[(i, i, i) for i in ["-", "IfcPropertySet", "Attribute"]], + name="Filter selection by", + update=load_selection_options, + default="-", + ) + attribute: EnumProperty( + items=[(i, i, i) for i in ["GlobalId", "Name", "Description", "ObjectType", "Tag", "PredefinedType"]], + name="Filter selection by", + update=load_selection_options, + ) + + # property_sets: CollectionProperty(type=StrProperty) + # selected_property_set: StringProperty(update=load_selection_options) + + # prop_names: CollectionProperty(type=StrProperty) + # selected_prop: StringProperty() + # prop_value: StringProperty() + +class SearchQuery(PropertyGroup, IfcSelector): + filters: CollectionProperty(type=SearchQueryFilter) + selector: EnumProperty( + items=[(i, i, i) for i in ["-", "IFC Class", "IfcSpatialElement", "IfcElementType", "GlobalId"]], + name="Selector type", + update=load_selection_options, + default="-", + ) + +class SearchQueryGroup(PropertyGroup, IfcSelector): + queries: CollectionProperty(type=SearchQuery) + + +class IfcSelectorProperties(PropertyGroup, IfcSelector): + groups: CollectionProperty(type=SearchQueryGroup) + selector_query_syntax: StringProperty() + + diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py index 44dce8431a..7609b9c86e 100644 --- a/src/blenderbim/blenderbim/bim/module/search/ui.py +++ b/src/blenderbim/blenderbim/bim/module/search/ui.py @@ -102,3 +102,136 @@ class BIM_UL_ifc_building_storey_filter(bpy.types.UIList): split = split.column() split.scale_x = 0.5 split.label(text=str(item.total)) + + +class BIM_PT_IFCSelector(Panel): + bl_label = "IFC Selector" + bl_idname = "BIM_PT_ifc_selector" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_collaboration" + + @classmethod + def poll(cls, context): + return IfcStore.get_file() + + def draw(self, context): + ifc_selector = context.scene.IfcSelectorProperties + layout = self.layout + row = layout.row() + + row.context_pointer_set(name="bim_prop_group", data=ifc_selector) + op = row.operator("bim.edit_blender_collection", text="Add selection group") + op.option = "add" + op.collection = "groups" + layout.separator() + + self.draw_query_group_ui(ifc_selector, layout) + + if len(ifc_selector.groups) != 0: + row = layout.row() + row.alignment = "CENTER" + select = row.operator("bim.filter_model_elements", text="select") + select.option = "select" + isolate = row.operator("bim.filter_model_elements", text="isolate") + isolate.option = "isolate" + hide = row.operator("bim.filter_model_elements", text="hide") + hide.option = "hide" + reset = row.operator("bim.reset_3d_view", text="reset 3d view") + + row = layout.row() + row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax") + + def draw_query_group_ui(self, ifc_selector, layout): + for index, group in enumerate(ifc_selector.groups): + row = layout.row() + row.alignment = "CENTER" + row.label(text="or") if index !=0 else None + + box = layout.box() + row = box.row(align=True) + row.alignment = "CENTER" + row.label(text=f"Group #{str(index+1)}") + row.context_pointer_set(name="bim_prop_group", data=group) + op = row.operator("bim.edit_blender_collection", text="Add query", icon="PLUS") + op.option = "add" + op.collection = "queries" + + row.context_pointer_set(name="bim_prop_group", data=ifc_selector) + op = row.operator("bim.edit_blender_collection", text="Remove selection group") + op.option = "remove" + op.collection = "groups" + op.index = index + + self.draw_query_ui(group, box) + + def draw_query_ui(self, group, box): + for index, query in enumerate(group.queries): + row = box.row() + row.alignment = "LEFT" + + row.prop(query, "and_or", text="") if index != 0 else None + if query.and_or == "or": + row=box.row() + row.alignment = "LEFT" + row.prop(query, "selector", text="") + + if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]: + row.label(text="Equals") + row.prop_search(query, "active_option", query, "options", text="") + row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len(query.sub_options) != 0 else None + self.draw_filter_ui(index, box, query) + + elif query.selector in ["GlobalId", "Attribute"]: + row.prop(query, "negation", text="") + row.prop(query, "comparison", text="") + row.prop(query, "value", text="") + + row.context_pointer_set(name="bim_prop_group", data=group) + op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") + op.option = "remove" + op.collection = "queries" + op.index = index + + row.context_pointer_set(name="bim_prop_group", data=query) + op = ( + row.operator("bim.edit_blender_collection", text="Add filter") + if query.selector == "IFC Class" + else None + ) + if op: + op.option = "add" + op.collection = "filters" + + def draw_filter_ui(self, index, box, query): + for filter_index, f in enumerate(query.filters): + row = box.row() + row.alignment = "LEFT" + row.label(text=" ↪") + row.prop(f, "and_or", text="") if filter_index != 0 else None + if f.and_or == "or": + row=box.row() + row.alignment = "LEFT" + row.label(text=" ↪") + row.prop(f, "selector", text="") + + if f.selector == "Attribute": + row.prop(f, "attribute", text="") + row.prop(f, "negation",) + row.prop(f, "comparison", text="") + row.prop(f, "value", text="") + + elif f.selector == "IfcPropertySet": + row.prop_search(f, "active_option", f, "options", text="") + row.prop_search(f, "active_sub_option", f, "sub_options", text="") + row.prop(f, "negation") + row.prop(f, "comparison", text="") + row.prop(f, "value", text="") + + row.context_pointer_set(name="bim_prop_group", data=query) + op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") + op.option = "remove" + op.collection = "filters" + op.index = filter_index diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index c8b7a2eb91..a26c4be1dd 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -21,13 +21,11 @@ import ifcopenshell.util.fm import ifcopenshell.util.element import lark - class Selector: @classmethod def parse(cls, ifc_file, query, elements=None): cls.file = ifc_file cls.elements = elements - l = lark.Lark( """start: query (lfunction query)* query: selector | group @@ -38,7 +36,7 @@ class Selector: filter: "[" filter_key (comparison filter_value)? "]" filter_key: WORD | pset_or_qto filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL - pset_or_qto: /[A-Za-z0-9_]+/ "." /[A-Za-z0-9_]+/ + pset_or_qto: /[^.= ][^.=]*[^.= ]/ "." /[^.= ][^.=]*[^.= ](?= +\W+")/ lfunction: and | or inverse_relationship: types | decomposed_by | bounded_by types: "*" @@ -143,7 +141,8 @@ class Selector: elif hasattr(element, "ObjectTypeOf") and element.ObjectTypeOf: results.extend(element.ObjectTypeOf[0].RelatedObjects) elif inverse_relationship == "decomposed_by": - results.extend(ifcopenshell.util.element.get_decomposition(element)) + results.extend( + ifcopenshell.util.element.get_decomposition(element)) elif inverse_relationship == "bounded_by" and hasattr(element, "BoundedBy"): for relationship in element.BoundedBy: results.append(relationship.RelatedBuildingElement) @@ -161,7 +160,8 @@ class Selector: if cls.elements is None: elements = cls.file.by_type(class_selector.children[0]) else: - elements = [e for e in cls.elements if e.is_a(class_selector.children[0])] + elements = [e for e in cls.elements if e.is_a( + class_selector.children[0])] if len(class_selector.children) > 1 and class_selector.children[1].data == "filter": return cls.filter_elements(elements, class_selector.children[1]) return elements @@ -210,7 +210,8 @@ class Selector: key = ".".join(key.split(".")[1:]) elif "." in key and key.split(".")[0] == "material": try: - element = ifcopenshell.util.element.get_material(element, should_skip_usage=True) + element = ifcopenshell.util.element.get_material( + element, should_skip_usage=True) if not element: return None except: From 3fa74469f4c3175d6c6daafee5bde347042907f1 Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Mon, 18 Jul 2022 15:39:19 +0200 Subject: [PATCH 2/8] minor changes --- .../blenderbim/bim/module/search/__init__.py | 6 ++--- .../blenderbim/bim/module/search/operator.py | 23 +++++++------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index 79630de378..6270057cf3 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -50,10 +50,8 @@ classes = ( def register(): - bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty( - type=prop.BIMSearchProperties) - bpy.types.Scene.IfcSelectorProperties = bpy.props.PointerProperty( - type=prop.IfcSelectorProperties) + bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties) + bpy.types.Scene.IfcSelectorProperties = bpy.props.PointerProperty(type=prop.IfcSelectorProperties) def unregister(): diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index cb0f635511..c467ec9c28 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -68,7 +68,6 @@ class EditBlenderCollection(bpy.types.Operator): getattr(context.bim_prop_group, self.collection).add() else: getattr(context.bim_prop_group, self.collection).remove(self.index) - return {"FINISHED"} @@ -104,8 +103,7 @@ class SelectIfcClass(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id or obj.is_library_indirect: continue - element = self.file.by_id( - obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) if does_keyword_exist(self.ifc_class, element.is_a(), context): obj.select_set(True) return {"FINISHED"} @@ -126,12 +124,10 @@ class SelectAttribute(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id( - obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) if context.scene.BIMSearchProperties.should_ignorecase: data = element.get_info() - value = next((v for k, v in data.items() - if k.lower() == attribute_name.lower()), None) + value = next((v for k, v in data.items() if k.lower() == attribute_name.lower()), None) else: value = getattr(element, attribute_name, None) if does_keyword_exist(pattern, value, context): @@ -155,8 +151,7 @@ class SelectPset(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id( - obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) psets = ifcopenshell.util.element.get_psets(element) if search_pset_name == "": props = {} @@ -164,10 +159,8 @@ class SelectPset(bpy.types.Operator): else: props = None if context.scene.BIMSearchProperties.should_ignorecase: - props = props or next( - (v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) - value = str(next((v for k, v in props.items() - if k.lower() == search_prop_name.lower()), None)) + props = props or next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) + value = str(next((v for k, v in props.items() if k.lower() == search_prop_name.lower()), None)) else: props = props or psets.get(search_pset_name, {}) value = props.get(search_prop_name, None) @@ -539,8 +532,8 @@ class FilterModelElements(bpy.types.Operator): selection += "[" - if f.selector == "Pset-Property": - selection += f'{f.active_option}.{f.active_sub_option} {"!" if f.negation else ""} ="{f.value}"' + if f.selector == "IfcPropertySet": + selection += f'{f.active_option.split(": ")[1]}.{f.active_sub_option.split(": ")[1]} {"!" if f.negation else ""}="{f.value}"' elif f.selector == "Attribute": selection += f'{f.attribute} {"!" if f.negation else ""}= "{f.value}"' From 2c29f89fe906ab3bf8ae7071c1c178e50523db31 Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Tue, 19 Jul 2022 09:19:32 +0200 Subject: [PATCH 3/8] Selector - update regex for pset filter --- src/ifcopenshell-python/ifcopenshell/util/selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index a26c4be1dd..8b876500d6 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -36,7 +36,7 @@ class Selector: filter: "[" filter_key (comparison filter_value)? "]" filter_key: WORD | pset_or_qto filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL - pset_or_qto: /[^.= ][^.=]*[^.= ]/ "." /[^.= ][^.=]*[^.= ](?= +\W+")/ + pset_or_qto: /[^\W][^.=]*[^\W]/ "." /[^\W][^.=]*[^\W]/ lfunction: and | or inverse_relationship: types | decomposed_by | bounded_by types: "*" From a0fdb9f777355f8d5b0e3fb0fdefcc0712cb5c52 Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Tue, 19 Jul 2022 09:21:13 +0200 Subject: [PATCH 4/8] update according to review comments --- src/blenderbim/blenderbim/bim/module/search/__init__.py | 2 +- src/blenderbim/blenderbim/bim/module/search/operator.py | 5 +++++ src/blenderbim/blenderbim/bim/module/search/prop.py | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index 6270057cf3..f604aac15f 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -32,7 +32,7 @@ classes = ( operator.SelectGlobalId, operator.SelectIfcClass, operator.SelectPset, - operator.Reset3dView, + operator.UnhideAllElements, operator.FilterModelElements, prop.BIMFilterClasses, prop.BIMFilterBuildingStoreys, diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index c467ec9c28..37385092ff 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -467,9 +467,14 @@ class ActivateIfcBuildingStoreyFilter(bpy.types.Operator): class Reset3dView(bpy.types.Operator): + + +class UnhideAllElements(bpy.types.Operator): """Filter model elements based on selection""" bl_idname = "bim.reset_3d_view" bl_label = "Reset 3D View" + bl_idname = "bim.unhide_all_elements" + bl_label = "Unhide All Elements" def execute(self, context): for obj in bpy.data.scenes["Scene"].objects: diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index a060514c81..0ddc730d51 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import bpy from ifcopenshell import util from ifcopenshell.util.selector import Selector import blenderbim.tool as tool From 4c562c1af0d9241a4c5cd25643a466d5493b2bcb Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Tue, 19 Jul 2022 10:05:20 +0200 Subject: [PATCH 5/8] turn ui into popup operator --- .../blenderbim/bim/module/search/__init__.py | 1 + .../blenderbim/bim/module/search/operator.py | 211 ++++++++++++++---- .../blenderbim/bim/module/search/ui.py | 116 +--------- 3 files changed, 167 insertions(+), 161 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index f604aac15f..2b2fbfef92 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -34,6 +34,7 @@ classes = ( operator.SelectPset, operator.UnhideAllElements, operator.FilterModelElements, + operator.IfcSelector, 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 37385092ff..a1bf90a160 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -192,12 +192,10 @@ class ColourByAttribute(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id( - obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) if context.scene.BIMSearchProperties.should_ignorecase: data = element.get_info() - value = next((v for k, v in data.items() - if k.lower() == attribute_name.lower()), None) + value = next((v for k, v in data.items() if k.lower() == attribute_name.lower()), None) else: value = getattr(element, attribute_name, None) if value not in values: @@ -211,8 +209,7 @@ class ColourByAttribute(bpy.types.Operator): def store_state(self, context): areas = [a for a in context.screen.areas if a.type == "VIEW_3D"] if areas: - self.transaction_data = { - "area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} + self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} def rollback(self, data): if data: @@ -247,8 +244,7 @@ class ColourByPset(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id( - obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) psets = ifcopenshell.util.element.get_psets(element) if search_pset_name == "": props = {} @@ -256,10 +252,8 @@ class ColourByPset(bpy.types.Operator): else: props = None if context.scene.BIMSearchProperties.should_ignorecase: - props = props or next( - (v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) - value = str(next((v for k, v in props.items() - if k.lower() == search_prop_name.lower()), None)) + props = props or next((v for k, v in psets.items() if k.lower() == search_pset_name.lower()), {}) + value = str(next((v for k, v in props.items() if k.lower() == search_prop_name.lower()), None)) else: props = props or psets.get(search_pset_name, {}) value = str(props.get(search_prop_name, None)) @@ -274,8 +268,7 @@ class ColourByPset(bpy.types.Operator): def store_state(self, context): areas = [a for a in context.screen.areas if a.type == "VIEW_3D"] if areas: - self.transaction_data = { - "area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} + self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} def rollback(self, data): if data: @@ -308,8 +301,7 @@ class ColourByClass(bpy.types.Operator): for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue - element = self.file.by_id( - obj.BIMObjectProperties.ifc_definition_id) + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) ifc_class = element.is_a() if ifc_class not in ifc_classes: ifc_classes[ifc_class] = next(colours) @@ -322,8 +314,7 @@ class ColourByClass(bpy.types.Operator): def store_state(self, context): areas = [a for a in context.screen.areas if a.type == "VIEW_3D"] if areas: - self.transaction_data = { - "area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} + self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} def rollback(self, data): if data: @@ -350,8 +341,7 @@ class ToggleFilterSelection(bpy.types.Operator): "Click to select/deselect current selection" bl_idname = "bim.toggle_filter_selection" bl_label = "Toggle Filter Selection" - action: bpy.props.EnumProperty( - items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", ""))) + action: bpy.props.EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", ""))) def execute(self, context): props = bpy.context.scene.BIMSearchProperties @@ -410,10 +400,8 @@ class ActivateIfcClassFilter(bpy.types.Operator): else len(bpy.context.scene.BIMSearchProperties.filter_classes), ) row = self.layout.row(align=True) - row.operator("bim.toggle_filter_selection", - text="Select All").action = "SELECT" - row.operator("bim.toggle_filter_selection", - text="Deselect All").action = "DESELECT" + row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" + row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" class ActivateIfcBuildingStoreyFilter(bpy.types.Operator): @@ -460,17 +448,13 @@ class ActivateIfcBuildingStoreyFilter(bpy.types.Operator): else len(bpy.context.scene.BIMSearchProperties.filter_building_storeys), ) row = self.layout.row(align=True) - row.operator("bim.toggle_filter_selection", - text="Select All").action = "SELECT" - row.operator("bim.toggle_filter_selection", - text="Deselect All").action = "DESELECT" - - -class Reset3dView(bpy.types.Operator): + row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" + row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" class UnhideAllElements(bpy.types.Operator): """Filter model elements based on selection""" + bl_idname = "bim.reset_3d_view" bl_label = "Reset 3D View" bl_idname = "bim.unhide_all_elements" @@ -484,6 +468,7 @@ class UnhideAllElements(bpy.types.Operator): class FilterModelElements(bpy.types.Operator): """Filter model elements based on selection""" + bl_idname = "bim.filter_model_elements" bl_label = "Filter Model Elements" option: bpy.props.StringProperty("select|isolate|hide") @@ -496,14 +481,14 @@ class FilterModelElements(bpy.types.Operator): return {"FINISHED"} def add_groups(self, selector): - selection = '' + selection = "" for group_index, group in enumerate(selector.groups): if group_index != 0: selection += " | " - selection += "(" if len(selector.groups) >1 else "" + selection += "(" if len(selector.groups) > 1 else "" selection = self.add_queries(selection, group) - selection += ")" if len(selector.groups) >1 else "" + selection += ")" if len(selector.groups) > 1 else "" return selection def add_queries(self, selection, group): @@ -522,7 +507,7 @@ class FilterModelElements(bpy.types.Operator): elif query.selector == "IfcElementType": index = int(query.active_sub_option.split(":")[0]) selection += f"* #{query.sub_options[index].global_id}" - + elif query.selector == "IfcSpatialElement": index = int(query.active_sub_option.split(":")[0]) selection += f"@ #{query.sub_options[index].global_id}" @@ -530,11 +515,11 @@ class FilterModelElements(bpy.types.Operator): def add_filters(self, selection, query): for f_index, f in enumerate(query.filters): - - if f_index !=0: + + if f_index != 0: selection += " & " if f.and_or == "and" else " | " selection += f".{query.active_option}" - + selection += "[" if f.selector == "IfcPropertySet": @@ -544,23 +529,157 @@ class FilterModelElements(bpy.types.Operator): selection += "]" return selection - + def update_model_view(self, context, selection): query = Selector.parse(IfcStore.file, selection) - sel_element_ids = [e.id() for e in query] - bpy.ops.object.select_all(action='DESELECT') + sel_element_ids = [e.id() for e in query] + bpy.ops.object.select_all(action="DESELECT") - for obj in bpy.data.scenes["Scene"].objects: - obj.hide_set(False) # reset 3d view - + obj.hide_set(False) # reset 3d view + if self.option == "select": if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids: obj.select_set(True) elif self.option == "isolate": - if obj.BIMObjectProperties.ifc_definition_id not in sel_element_ids: + if obj.BIMObjectProperties.ifc_definition_id not in sel_element_ids: obj.hide_set(True) elif self.option == "hide": - if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids: + if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids: obj.hide_set(True) + +from . import ui +class IfcSelector(bpy.types.Operator): + """Select elements in model with IFC Selector""" + bl_idname = "bim.ifc_selector" + bl_label = "Select elements with IFC Selector" + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=800) + + @classmethod + def poll(cls, context): + return IfcStore.get_file() + + def execute(self, context): + return {"FINISHED"} + + def draw(self, context): + ifc_selector = context.scene.IfcSelectorProperties + layout = self.layout + row = layout.row() + + row.context_pointer_set(name="bim_prop_group", data=ifc_selector) + op = row.operator("bim.edit_blender_collection", text="Add selection group") + op.option = "add" + op.collection = "groups" + layout.separator() + + self.draw_query_group_ui(ifc_selector, layout) + + if len(ifc_selector.groups) != 0: + row = layout.row() + row.alignment = "CENTER" + select = row.operator("bim.filter_model_elements", text="select") + select.option = "select" + isolate = row.operator("bim.filter_model_elements", text="isolate") + isolate.option = "isolate" + hide = row.operator("bim.filter_model_elements", text="hide") + hide.option = "hide" + row.operator("bim.unhide_all_elements", text="unhide all elements") + + row = layout.row() + row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax") + + def draw_query_group_ui(self, ifc_selector, layout): + for index, group in enumerate(ifc_selector.groups): + row = layout.row() + row.alignment = "CENTER" + row.label(text="or") if index !=0 else None + + box = layout.box() + row = box.row(align=True) + row.alignment = "CENTER" + row.label(text=f"Group #{str(index+1)}") + row.context_pointer_set(name="bim_prop_group", data=group) + op = row.operator("bim.edit_blender_collection", text="Add query", icon="PLUS") + op.option = "add" + op.collection = "queries" + + row.context_pointer_set(name="bim_prop_group", data=ifc_selector) + op = row.operator("bim.edit_blender_collection", text="Remove selection group") + op.option = "remove" + op.collection = "groups" + op.index = index + + self.draw_query_ui(group, box) + + def draw_query_ui(self, group, box): + for index, query in enumerate(group.queries): + row = box.row() + row.alignment = "LEFT" + + row.prop(query, "and_or", text="") if index != 0 else None + if query.and_or == "or": + row=box.row() + row.alignment = "LEFT" + row.prop(query, "selector", text="") + + if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]: + row.label(text="Equals") + row.prop_search(query, "active_option", query, "options", text="") + row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len(query.sub_options) != 0 else None + self.draw_filter_ui(index, box, query) + + elif query.selector in ["GlobalId", "Attribute"]: + row.prop(query, "negation", text="") + row.prop(query, "comparison", text="") + row.prop(query, "value", text="") + + row.context_pointer_set(name="bim_prop_group", data=group) + op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") + op.option = "remove" + op.collection = "queries" + op.index = index + + row.context_pointer_set(name="bim_prop_group", data=query) + op = ( + row.operator("bim.edit_blender_collection", text="Add filter") + if query.selector == "IFC Class" + else None + ) + if op: + op.option = "add" + op.collection = "filters" + + def draw_filter_ui(self, index, box, query): + for filter_index, f in enumerate(query.filters): + row = box.row() + row.alignment = "LEFT" + row.label(text=" ↪") + row.prop(f, "and_or", text="") if filter_index != 0 else None + if f.and_or == "or": + row=box.row() + row.alignment = "LEFT" + row.label(text=" ↪") + row.prop(f, "selector", text="") + + if f.selector == "Attribute": + row.prop(f, "attribute", text="") + row.prop(f, "negation",) + row.prop(f, "comparison", text="") + row.prop(f, "value", text="") + + elif f.selector == "IfcPropertySet": + row.prop_search(f, "active_option", f, "options", text="") + row.prop_search(f, "active_sub_option", f, "sub_options", text="") + row.prop(f, "negation") + row.prop(f, "comparison", text="") + row.prop(f, "value", text="") + + row.context_pointer_set(name="bim_prop_group", data=query) + op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") + op.option = "remove" + op.collection = "filters" + op.index = filter_index \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py index 7609b9c86e..949389bd2b 100644 --- a/src/blenderbim/blenderbim/bim/module/search/ui.py +++ b/src/blenderbim/blenderbim/bim/module/search/ui.py @@ -118,120 +118,6 @@ class BIM_PT_IFCSelector(Panel): return IfcStore.get_file() def draw(self, context): - ifc_selector = context.scene.IfcSelectorProperties layout = self.layout - row = layout.row() + layout.operator("bim.ifc_selector") - row.context_pointer_set(name="bim_prop_group", data=ifc_selector) - op = row.operator("bim.edit_blender_collection", text="Add selection group") - op.option = "add" - op.collection = "groups" - layout.separator() - - self.draw_query_group_ui(ifc_selector, layout) - - if len(ifc_selector.groups) != 0: - row = layout.row() - row.alignment = "CENTER" - select = row.operator("bim.filter_model_elements", text="select") - select.option = "select" - isolate = row.operator("bim.filter_model_elements", text="isolate") - isolate.option = "isolate" - hide = row.operator("bim.filter_model_elements", text="hide") - hide.option = "hide" - reset = row.operator("bim.reset_3d_view", text="reset 3d view") - - row = layout.row() - row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax") - - def draw_query_group_ui(self, ifc_selector, layout): - for index, group in enumerate(ifc_selector.groups): - row = layout.row() - row.alignment = "CENTER" - row.label(text="or") if index !=0 else None - - box = layout.box() - row = box.row(align=True) - row.alignment = "CENTER" - row.label(text=f"Group #{str(index+1)}") - row.context_pointer_set(name="bim_prop_group", data=group) - op = row.operator("bim.edit_blender_collection", text="Add query", icon="PLUS") - op.option = "add" - op.collection = "queries" - - row.context_pointer_set(name="bim_prop_group", data=ifc_selector) - op = row.operator("bim.edit_blender_collection", text="Remove selection group") - op.option = "remove" - op.collection = "groups" - op.index = index - - self.draw_query_ui(group, box) - - def draw_query_ui(self, group, box): - for index, query in enumerate(group.queries): - row = box.row() - row.alignment = "LEFT" - - row.prop(query, "and_or", text="") if index != 0 else None - if query.and_or == "or": - row=box.row() - row.alignment = "LEFT" - row.prop(query, "selector", text="") - - if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]: - row.label(text="Equals") - row.prop_search(query, "active_option", query, "options", text="") - row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len(query.sub_options) != 0 else None - self.draw_filter_ui(index, box, query) - - elif query.selector in ["GlobalId", "Attribute"]: - row.prop(query, "negation", text="") - row.prop(query, "comparison", text="") - row.prop(query, "value", text="") - - row.context_pointer_set(name="bim_prop_group", data=group) - op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") - op.option = "remove" - op.collection = "queries" - op.index = index - - row.context_pointer_set(name="bim_prop_group", data=query) - op = ( - row.operator("bim.edit_blender_collection", text="Add filter") - if query.selector == "IFC Class" - else None - ) - if op: - op.option = "add" - op.collection = "filters" - - def draw_filter_ui(self, index, box, query): - for filter_index, f in enumerate(query.filters): - row = box.row() - row.alignment = "LEFT" - row.label(text=" ↪") - row.prop(f, "and_or", text="") if filter_index != 0 else None - if f.and_or == "or": - row=box.row() - row.alignment = "LEFT" - row.label(text=" ↪") - row.prop(f, "selector", text="") - - if f.selector == "Attribute": - row.prop(f, "attribute", text="") - row.prop(f, "negation",) - row.prop(f, "comparison", text="") - row.prop(f, "value", text="") - - elif f.selector == "IfcPropertySet": - row.prop_search(f, "active_option", f, "options", text="") - row.prop_search(f, "active_sub_option", f, "sub_options", text="") - row.prop(f, "negation") - row.prop(f, "comparison", text="") - row.prop(f, "value", text="") - - row.context_pointer_set(name="bim_prop_group", data=query) - op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") - op.option = "remove" - op.collection = "filters" - op.index = filter_index From 64a22fe5c67ab0395c4deb362bbabf5c26076cbc Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Tue, 19 Jul 2022 13:55:20 +0200 Subject: [PATCH 6/8] further refinements --- .../blenderbim/bim/module/search/__init__.py | 5 +- .../blenderbim/bim/module/search/operator.py | 197 +++++++++++++----- .../blenderbim/bim/module/search/prop.py | 35 ++-- 3 files changed, 163 insertions(+), 74 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/search/__init__.py b/src/blenderbim/blenderbim/bim/module/search/__init__.py index 2b2fbfef92..0eadf9a42d 100644 --- a/src/blenderbim/blenderbim/bim/module/search/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/search/__init__.py @@ -35,6 +35,9 @@ classes = ( operator.UnhideAllElements, operator.FilterModelElements, operator.IfcSelector, + operator.SaveSelectorQuery, + operator.OpenQueryLibrary, + operator.LoadQuery, prop.BIMFilterClasses, prop.BIMFilterBuildingStoreys, prop.BIMSearchProperties, @@ -46,7 +49,7 @@ classes = ( ui.BIM_PT_search, ui.BIM_UL_ifc_class_filter, ui.BIM_UL_ifc_building_storey_filter, - ui.BIM_PT_IFCSelector + ui.BIM_PT_IFCSelector, ) diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index a1bf90a160..8a6bf77073 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -24,6 +24,17 @@ from ifcopenshell.util.selector import Selector import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from itertools import cycle +from bpy.types import PropertyGroup, Operator +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) colour_list = [ @@ -55,13 +66,13 @@ def does_keyword_exist(pattern, string, context): return True -class EditBlenderCollection(bpy.types.Operator): +class EditBlenderCollection(Operator): bl_idname = "bim.edit_blender_collection" bl_label = "Add or Remove blender collection item" bl_options = {"REGISTER", "UNDO"} - option: bpy.props.StringProperty() - collection: bpy.props.StringProperty() - index: bpy.props.IntProperty() + option: StringProperty() + collection: StringProperty() + index: IntProperty() def execute(self, context): if self.option == "add": @@ -71,13 +82,13 @@ class EditBlenderCollection(bpy.types.Operator): return {"FINISHED"} -class SelectGlobalId(bpy.types.Operator): +class SelectGlobalId(Operator): """Click to select the objects that match with the given Global ID""" bl_idname = "bim.select_global_id" bl_label = "Select GlobalId" bl_options = {"REGISTER", "UNDO"} - global_id: bpy.props.StringProperty() + global_id: StringProperty() def execute(self, context): ifc_file = tool.Ifc.get() @@ -90,13 +101,13 @@ class SelectGlobalId(bpy.types.Operator): return {"FINISHED"} -class SelectIfcClass(bpy.types.Operator): +class SelectIfcClass(Operator): """Click to select all objects that match with the given IFC class""" bl_idname = "bim.select_ifc_class" bl_label = "Select IFC Class" bl_options = {"REGISTER", "UNDO"} - ifc_class: bpy.props.StringProperty() + ifc_class: StringProperty() def execute(self, context): self.file = IfcStore.get_file() @@ -109,7 +120,7 @@ class SelectIfcClass(bpy.types.Operator): return {"FINISHED"} -class SelectAttribute(bpy.types.Operator): +class SelectAttribute(Operator): """Click to select all objects that match with the given Attribute Name and Value""" bl_idname = "bim.select_attribute" @@ -135,7 +146,7 @@ class SelectAttribute(bpy.types.Operator): return {"FINISHED"} -class SelectPset(bpy.types.Operator): +class SelectPset(Operator): """Click to select all objects that match with the given Pset Name, Properties Name and Value""" bl_idname = "bim.select_pset" @@ -169,7 +180,7 @@ class SelectPset(bpy.types.Operator): return {"FINISHED"} -class ColourByAttribute(bpy.types.Operator): +class ColourByAttribute(Operator): """Click to colour different objects according to given Attribute Name""" bl_idname = "bim.colour_by_attribute" @@ -220,7 +231,7 @@ class ColourByAttribute(bpy.types.Operator): data["area"].spaces[0].shading.color_type = "OBJECT" -class ColourByPset(bpy.types.Operator): +class ColourByPset(Operator): """Click to colour different objects according to given Prop Name""" bl_idname = "bim.colour_by_pset" @@ -279,7 +290,7 @@ class ColourByPset(bpy.types.Operator): data["area"].spaces[0].shading.color_type = "OBJECT" -class ColourByClass(bpy.types.Operator): +class ColourByClass(Operator): """Click to colour different objects according to their IFC Classes""" bl_idname = "bim.colour_by_class" @@ -325,7 +336,7 @@ class ColourByClass(bpy.types.Operator): data["area"].spaces[0].shading.color_type = "OBJECT" -class ResetObjectColours(bpy.types.Operator): +class ResetObjectColours(Operator): """Reset the colour of selected objects""" bl_idname = "bim.reset_object_colours" @@ -337,11 +348,11 @@ class ResetObjectColours(bpy.types.Operator): return {"FINISHED"} -class ToggleFilterSelection(bpy.types.Operator): +class ToggleFilterSelection(Operator): "Click to select/deselect current selection" bl_idname = "bim.toggle_filter_selection" bl_label = "Toggle Filter Selection" - action: bpy.props.EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", ""))) + action: EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", ""))) def execute(self, context): props = bpy.context.scene.BIMSearchProperties @@ -358,7 +369,7 @@ class ToggleFilterSelection(bpy.types.Operator): return {"FINISHED"} -class ActivateIfcClassFilter(bpy.types.Operator): +class ActivateIfcClassFilter(Operator): """Filter the current selection by IFC class""" bl_idname = "bim.activate_ifc_class_filter" @@ -404,7 +415,7 @@ class ActivateIfcClassFilter(bpy.types.Operator): row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" -class ActivateIfcBuildingStoreyFilter(bpy.types.Operator): +class ActivateIfcBuildingStoreyFilter(Operator): """Filter the current selection by Building Storey""" bl_idname = "bim.activate_ifc_building_storey_filter" @@ -452,7 +463,7 @@ class ActivateIfcBuildingStoreyFilter(bpy.types.Operator): row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" -class UnhideAllElements(bpy.types.Operator): +class UnhideAllElements(Operator): """Filter model elements based on selection""" bl_idname = "bim.reset_3d_view" @@ -466,16 +477,16 @@ class UnhideAllElements(bpy.types.Operator): return {"FINISHED"} -class FilterModelElements(bpy.types.Operator): +class FilterModelElements(Operator): """Filter model elements based on selection""" bl_idname = "bim.filter_model_elements" bl_label = "Filter Model Elements" - option: bpy.props.StringProperty("select|isolate|hide") + option: StringProperty("select|isolate|hide") def execute(self, context): selector = context.scene.IfcSelectorProperties - selection = self.add_groups(selector) + selection = selector.selector_query_syntax if selector.manual_override else self.add_groups(selector) selector.selector_query_syntax = selection self.update_model_view(context, selection) return {"FINISHED"} @@ -547,9 +558,9 @@ class FilterModelElements(bpy.types.Operator): elif self.option == "hide": if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids: obj.hide_set(True) - -from . import ui -class IfcSelector(bpy.types.Operator): + + +class IfcSelector(Operator): """Select elements in model with IFC Selector""" bl_idname = "bim.ifc_selector" @@ -557,11 +568,11 @@ class IfcSelector(bpy.types.Operator): def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width=800) - + @classmethod def poll(cls, context): return IfcStore.get_file() - + def execute(self, context): return {"FINISHED"} @@ -577,27 +588,31 @@ class IfcSelector(bpy.types.Operator): layout.separator() self.draw_query_group_ui(ifc_selector, layout) - + if len(ifc_selector.groups) != 0: - row = layout.row() - row.alignment = "CENTER" - select = row.operator("bim.filter_model_elements", text="select") - select.option = "select" - isolate = row.operator("bim.filter_model_elements", text="isolate") - isolate.option = "isolate" - hide = row.operator("bim.filter_model_elements", text="hide") - hide.option = "hide" - row.operator("bim.unhide_all_elements", text="unhide all elements") - - row = layout.row() + row = layout.row(align=True) row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax") + row.prop(ifc_selector, "manual_override", text="") + + select = row.operator("bim.filter_model_elements", text="", icon="RESTRICT_SELECT_OFF") + select.option = "select" + isolate = row.operator("bim.filter_model_elements", text="", icon="ZOOM_SELECTED") + isolate.option = "isolate" + hide = row.operator("bim.filter_model_elements", text="", icon="HIDE_ON") + hide.option = "hide" + row.operator("bim.unhide_all_elements", text="", icon="HIDE_OFF") + + row = layout.row(align=True) + row.alignment = "CENTER" + row.operator("bim.save_selector_query", text="Save Query") + row.operator("bim.open_query_library", text="Load Query") def draw_query_group_ui(self, ifc_selector, layout): for index, group in enumerate(ifc_selector.groups): row = layout.row() row.alignment = "CENTER" - row.label(text="or") if index !=0 else None - + row.label(text="or") if index != 0 else None + box = layout.box() row = box.row(align=True) row.alignment = "CENTER" @@ -619,19 +634,21 @@ class IfcSelector(bpy.types.Operator): for index, query in enumerate(group.queries): row = box.row() row.alignment = "LEFT" - + row.prop(query, "and_or", text="") if index != 0 else None if query.and_or == "or": - row=box.row() - row.alignment = "LEFT" + row = box.row() + row.alignment = "LEFT" row.prop(query, "selector", text="") if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]: row.label(text="Equals") row.prop_search(query, "active_option", query, "options", text="") - row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len(query.sub_options) != 0 else None + row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len( + query.sub_options + ) != 0 else None self.draw_filter_ui(index, box, query) - + elif query.selector in ["GlobalId", "Attribute"]: row.prop(query, "negation", text="") row.prop(query, "comparison", text="") @@ -645,10 +662,10 @@ class IfcSelector(bpy.types.Operator): row.context_pointer_set(name="bim_prop_group", data=query) op = ( - row.operator("bim.edit_blender_collection", text="Add filter") - if query.selector == "IFC Class" - else None - ) + row.operator("bim.edit_blender_collection", text="Add filter") + if query.selector == "IFC Class" + else None + ) if op: op.option = "add" op.collection = "filters" @@ -660,14 +677,17 @@ class IfcSelector(bpy.types.Operator): row.label(text=" ↪") row.prop(f, "and_or", text="") if filter_index != 0 else None if f.and_or == "or": - row=box.row() + row = box.row() row.alignment = "LEFT" - row.label(text=" ↪") + row.label(text=" ↪") row.prop(f, "selector", text="") if f.selector == "Attribute": row.prop(f, "attribute", text="") - row.prop(f, "negation",) + row.prop( + f, + "negation", + ) row.prop(f, "comparison", text="") row.prop(f, "value", text="") @@ -682,4 +702,73 @@ class IfcSelector(bpy.types.Operator): op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") op.option = "remove" op.collection = "filters" - op.index = filter_index \ No newline at end of file + op.index = filter_index + + +class SaveSelectorQuery(Operator): + bl_idname = "bim.save_selector_query" + bl_label = "Save Selector Query" + save_name: StringProperty() + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=400) + + def draw(self, context): + layout = self.layout + layout.prop(self, "save_name") + + def execute(self, context): + ifc_selector = context.scene.IfcSelectorProperties + + new = ifc_selector.query_library.add() + new.name = self.save_name + new.query = ifc_selector.selector_query_syntax + + return {"FINISHED"} + + +class OpenQueryLibrary(Operator): + """Open Query Library""" + + bl_idname = "bim.open_query_library" + bl_label = "Open Query Library" + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=400) + + def close_panel(event): + x, y = event.mouse_x, event.mouse_y + bpy.context.window.cursor_warp(10, 10) + + move_back = lambda: bpy.context.window.cursor_warp(x, y) + bpy.app.timers.register(move_back, first_interval=0.001) + + def draw(self, context): + layout = self.layout + ifc_selector = context.scene.IfcSelectorProperties + + for index, query in enumerate(ifc_selector.query_library): + row = layout.row(align=True) + row.prop(query, "query", text=query.name) + op = row.operator("bim.load_query", icon="SORT_ASC", text="") + op.index = index + + row.context_pointer_set(name="bim_prop_group", data=ifc_selector) + op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") + op.option = "remove" + op.collection = "query_library" + op.index = index + + def execute(self, context): + return {"FINISHED"} + + +class LoadQuery(Operator): + bl_idname = "bim.load_query" + bl_label = "Load Query" + index: IntProperty() + + def execute(self, context): + ifc_selector = context.scene.IfcSelectorProperties + ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index 0ddc730d51..37042db490 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -107,20 +107,20 @@ def load_sub_options(self, context): if self.selector not in ["IfcClass"]: self.load_option = "sub_options" load_selection_options(self, context) - + + def load_selection_options(self, context): ifc = IfcStore.file load_option = self.load_option op = getattr(self, load_option) op.clear() - options = [] if load_option == "options": self.sub_options.clear() if self.selector == "IFC Class": options = get_classes(self, "IfcElement") - options.append(("IfcSpace", "IfcSpace","")) + options.append(("IfcSpace", "IfcSpace", "")) elif self.selector == "IfcSpatialElement": options = get_classes(self, "IfcSpatialElement") elif self.selector == "IfcElementType": @@ -130,7 +130,7 @@ def load_selection_options(self, context): elif self.selector == "IfcPropertySet": psets = Selector.parse(ifc, ".IfcPropertySet") options = set([o.Name for o in psets]) - + elif load_option == "sub_options": if self.selector in ["IfcSpatialElement", "IfcElementType"]: active_option = self.active_option.split(": ")[1] @@ -144,7 +144,6 @@ def load_selection_options(self, context): for prop in pset.HasProperties: options.add(prop.Name) - for index, option in enumerate(options): new = op.add() if self.selector in ["IfcSpatialElement", "IfcElementType"]: @@ -157,7 +156,6 @@ def load_selection_options(self, context): new.name = f"{index}: {option}" else: new.name = f"{index}: {option[0]}" - self.load_option = "options" @@ -175,7 +173,8 @@ class SearchCollection(PropertyGroup): name: StringProperty() long_name: StringProperty() global_id: StringProperty() - + query: StringProperty() + class IfcSelector: and_or: EnumProperty( items=[(i, i, i) for i in ["and", "or"]], @@ -191,15 +190,14 @@ class IfcSelector: ("<", "less than", ""), ], ) - load_option: StringProperty(default="options") - + load_option: StringProperty( + default="options", description="controls whether or not options or sub_options are loaded" + ) options: CollectionProperty(type=SearchCollection) active_option: StringProperty(update=load_sub_options) - sub_options: CollectionProperty(type=SearchCollection) active_sub_option: StringProperty() - - value: StringProperty() + value: StringProperty(description="generic 'value' that can be used in multiple scenarios") class SearchQueryFilter(PropertyGroup, IfcSelector): @@ -214,13 +212,7 @@ class SearchQueryFilter(PropertyGroup, IfcSelector): name="Filter selection by", update=load_selection_options, ) - - # property_sets: CollectionProperty(type=StrProperty) - # selected_property_set: StringProperty(update=load_selection_options) - # prop_names: CollectionProperty(type=StrProperty) - # selected_prop: StringProperty() - # prop_value: StringProperty() class SearchQuery(PropertyGroup, IfcSelector): filters: CollectionProperty(type=SearchQueryFilter) @@ -231,6 +223,7 @@ class SearchQuery(PropertyGroup, IfcSelector): default="-", ) + class SearchQueryGroup(PropertyGroup, IfcSelector): queries: CollectionProperty(type=SearchQuery) @@ -239,4 +232,8 @@ class IfcSelectorProperties(PropertyGroup, IfcSelector): groups: CollectionProperty(type=SearchQueryGroup) selector_query_syntax: StringProperty() - + query_library: CollectionProperty(type=SearchCollection) + active_query: StringProperty() + + active_query: StringProperty() + manual_override: BoolProperty(default=False, description="Toggle to allow manual typing of query-syntax") From 31f21dcb29f853e753fa5c6e389a969bbd52ffd1 Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Tue, 19 Jul 2022 14:16:59 +0200 Subject: [PATCH 7/8] abstract away ui code from operator --- .../blenderbim/bim/module/search/operator.py | 130 +----------------- .../blenderbim/bim/module/search/ui.py | 130 ++++++++++++++++++ 2 files changed, 132 insertions(+), 128 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index 8a6bf77073..f1736d85f0 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -577,132 +577,8 @@ class IfcSelector(Operator): return {"FINISHED"} def draw(self, context): - ifc_selector = context.scene.IfcSelectorProperties - layout = self.layout - row = layout.row() - - row.context_pointer_set(name="bim_prop_group", data=ifc_selector) - op = row.operator("bim.edit_blender_collection", text="Add selection group") - op.option = "add" - op.collection = "groups" - layout.separator() - - self.draw_query_group_ui(ifc_selector, layout) - - if len(ifc_selector.groups) != 0: - row = layout.row(align=True) - row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax") - row.prop(ifc_selector, "manual_override", text="") - - select = row.operator("bim.filter_model_elements", text="", icon="RESTRICT_SELECT_OFF") - select.option = "select" - isolate = row.operator("bim.filter_model_elements", text="", icon="ZOOM_SELECTED") - isolate.option = "isolate" - hide = row.operator("bim.filter_model_elements", text="", icon="HIDE_ON") - hide.option = "hide" - row.operator("bim.unhide_all_elements", text="", icon="HIDE_OFF") - - row = layout.row(align=True) - row.alignment = "CENTER" - row.operator("bim.save_selector_query", text="Save Query") - row.operator("bim.open_query_library", text="Load Query") - - def draw_query_group_ui(self, ifc_selector, layout): - for index, group in enumerate(ifc_selector.groups): - row = layout.row() - row.alignment = "CENTER" - row.label(text="or") if index != 0 else None - - box = layout.box() - row = box.row(align=True) - row.alignment = "CENTER" - row.label(text=f"Group #{str(index+1)}") - row.context_pointer_set(name="bim_prop_group", data=group) - op = row.operator("bim.edit_blender_collection", text="Add query", icon="PLUS") - op.option = "add" - op.collection = "queries" - - row.context_pointer_set(name="bim_prop_group", data=ifc_selector) - op = row.operator("bim.edit_blender_collection", text="Remove selection group") - op.option = "remove" - op.collection = "groups" - op.index = index - - self.draw_query_ui(group, box) - - def draw_query_ui(self, group, box): - for index, query in enumerate(group.queries): - row = box.row() - row.alignment = "LEFT" - - row.prop(query, "and_or", text="") if index != 0 else None - if query.and_or == "or": - row = box.row() - row.alignment = "LEFT" - row.prop(query, "selector", text="") - - if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]: - row.label(text="Equals") - row.prop_search(query, "active_option", query, "options", text="") - row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len( - query.sub_options - ) != 0 else None - self.draw_filter_ui(index, box, query) - - elif query.selector in ["GlobalId", "Attribute"]: - row.prop(query, "negation", text="") - row.prop(query, "comparison", text="") - row.prop(query, "value", text="") - - row.context_pointer_set(name="bim_prop_group", data=group) - op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") - op.option = "remove" - op.collection = "queries" - op.index = index - - row.context_pointer_set(name="bim_prop_group", data=query) - op = ( - row.operator("bim.edit_blender_collection", text="Add filter") - if query.selector == "IFC Class" - else None - ) - if op: - op.option = "add" - op.collection = "filters" - - def draw_filter_ui(self, index, box, query): - for filter_index, f in enumerate(query.filters): - row = box.row() - row.alignment = "LEFT" - row.label(text=" ↪") - row.prop(f, "and_or", text="") if filter_index != 0 else None - if f.and_or == "or": - row = box.row() - row.alignment = "LEFT" - row.label(text=" ↪") - row.prop(f, "selector", text="") - - if f.selector == "Attribute": - row.prop(f, "attribute", text="") - row.prop( - f, - "negation", - ) - row.prop(f, "comparison", text="") - row.prop(f, "value", text="") - - elif f.selector == "IfcPropertySet": - row.prop_search(f, "active_option", f, "options", text="") - row.prop_search(f, "active_sub_option", f, "sub_options", text="") - row.prop(f, "negation") - row.prop(f, "comparison", text="") - row.prop(f, "value", text="") - - row.context_pointer_set(name="bim_prop_group", data=query) - op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") - op.option = "remove" - op.collection = "filters" - op.index = filter_index + from . import ui + ui.IfcSelectorUI.draw(context, self.layout) class SaveSelectorQuery(Operator): @@ -719,11 +595,9 @@ class SaveSelectorQuery(Operator): def execute(self, context): ifc_selector = context.scene.IfcSelectorProperties - new = ifc_selector.query_library.add() new.name = self.save_name new.query = ifc_selector.selector_query_syntax - return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/search/ui.py b/src/blenderbim/blenderbim/bim/module/search/ui.py index 949389bd2b..01669337d0 100644 --- a/src/blenderbim/blenderbim/bim/module/search/ui.py +++ b/src/blenderbim/blenderbim/bim/module/search/ui.py @@ -121,3 +121,133 @@ class BIM_PT_IFCSelector(Panel): layout = self.layout layout.operator("bim.ifc_selector") + +# this doesn't inherit from Panel since it's just a class to abstract away UI code from the IfcSelector Operator +class IfcSelectorUI: + @classmethod + def draw(self, context, layout): + ifc_selector = context.scene.IfcSelectorProperties + row = layout.row() + + row.context_pointer_set(name="bim_prop_group", data=ifc_selector) + op = row.operator("bim.edit_blender_collection", text="Add selection group") + op.option = "add" + op.collection = "groups" + layout.separator() + + self.draw_query_group_ui(self, ifc_selector, layout) + + if len(ifc_selector.groups) != 0: + row = layout.row(align=True) + row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax") + row.prop(ifc_selector, "manual_override", text="") + + select = row.operator("bim.filter_model_elements", text="", icon="RESTRICT_SELECT_OFF") + select.option = "select" + isolate = row.operator("bim.filter_model_elements", text="", icon="ZOOM_SELECTED") + isolate.option = "isolate" + hide = row.operator("bim.filter_model_elements", text="", icon="HIDE_ON") + hide.option = "hide" + row.operator("bim.unhide_all_elements", text="", icon="HIDE_OFF") + + row = layout.row(align=True) + row.alignment = "CENTER" + row.operator("bim.save_selector_query", text="Save Query") + row.operator("bim.open_query_library", text="Load Query") + + def draw_query_group_ui(self, ifc_selector, layout): + for index, group in enumerate(ifc_selector.groups): + row = layout.row() + row.alignment = "CENTER" + row.label(text="or") if index != 0 else None + + box = layout.box() + row = box.row(align=True) + row.alignment = "CENTER" + row.label(text=f"Group #{str(index+1)}") + row.context_pointer_set(name="bim_prop_group", data=group) + op = row.operator("bim.edit_blender_collection", text="Add query", icon="PLUS") + op.option = "add" + op.collection = "queries" + + row.context_pointer_set(name="bim_prop_group", data=ifc_selector) + op = row.operator("bim.edit_blender_collection", text="Remove selection group") + op.option = "remove" + op.collection = "groups" + op.index = index + + self.draw_query_ui(self, group, box) + + def draw_query_ui(self, group, box): + for index, query in enumerate(group.queries): + row = box.row() + row.alignment = "LEFT" + + row.prop(query, "and_or", text="") if index != 0 else None + if query.and_or == "or": + row = box.row() + row.alignment = "LEFT" + row.prop(query, "selector", text="") + + if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]: + row.label(text="Equals") + row.prop_search(query, "active_option", query, "options", text="") + row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len( + query.sub_options + ) != 0 else None + self.draw_filter_ui(self, box, query) + + elif query.selector in ["GlobalId", "Attribute"]: + row.prop(query, "negation", text="") + row.prop(query, "comparison", text="") + row.prop(query, "value", text="") + + row.context_pointer_set(name="bim_prop_group", data=group) + op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") + op.option = "remove" + op.collection = "queries" + op.index = index + + row.context_pointer_set(name="bim_prop_group", data=query) + op = ( + row.operator("bim.edit_blender_collection", text="Add filter") + if query.selector == "IFC Class" + else None + ) + if op: + op.option = "add" + op.collection = "filters" + + def draw_filter_ui(self, box, query): + for filter_index, f in enumerate(query.filters): + row = box.row() + row.alignment = "LEFT" + row.label(text=" ↪") + row.prop(f, "and_or", text="") if filter_index != 0 else None + if f.and_or == "or": + row = box.row() + row.alignment = "LEFT" + row.label(text=" ↪") + row.prop(f, "selector", text="") + + if f.selector == "Attribute": + row.prop(f, "attribute", text="") + row.prop( + f, + "negation", + ) + row.prop(f, "comparison", text="") + row.prop(f, "value", text="") + + elif f.selector == "IfcPropertySet": + row.prop_search(f, "active_option", f, "options", text="") + row.prop_search(f, "active_sub_option", f, "sub_options", text="") + row.prop(f, "negation") + row.prop(f, "comparison", text="") + row.prop(f, "value", text="") + + row.context_pointer_set(name="bim_prop_group", data=query) + op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="") + op.option = "remove" + op.collection = "filters" + op.index = filter_index From afa0db3c7e35985d0b0e0b4f91d41696bb6661b2 Mon Sep 17 00:00:00 2001 From: Vukas Pajic <83825269+vulevukusej@users.noreply.github.com> Date: Tue, 19 Jul 2022 14:51:48 +0200 Subject: [PATCH 8/8] all tests in test_selector.py now passing --- .../ifcopenshell/util/selector.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 8b876500d6..fb98c7a87d 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -21,6 +21,7 @@ import ifcopenshell.util.fm import ifcopenshell.util.element import lark + class Selector: @classmethod def parse(cls, ifc_file, query, elements=None): @@ -36,7 +37,7 @@ class Selector: filter: "[" filter_key (comparison filter_value)? "]" filter_key: WORD | pset_or_qto filter_value: ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL - pset_or_qto: /[^\W][^.=]*[^\W]/ "." /[^\W][^.=]*[^\W]/ + pset_or_qto: /[^\W][^.=<>]*[^\W]/ "." /[^\W][^.=<>]*[^\W]/ lfunction: and | or inverse_relationship: types | decomposed_by | bounded_by types: "*" @@ -141,8 +142,7 @@ class Selector: elif hasattr(element, "ObjectTypeOf") and element.ObjectTypeOf: results.extend(element.ObjectTypeOf[0].RelatedObjects) elif inverse_relationship == "decomposed_by": - results.extend( - ifcopenshell.util.element.get_decomposition(element)) + results.extend(ifcopenshell.util.element.get_decomposition(element)) elif inverse_relationship == "bounded_by" and hasattr(element, "BoundedBy"): for relationship in element.BoundedBy: results.append(relationship.RelatedBuildingElement) @@ -160,8 +160,7 @@ class Selector: if cls.elements is None: elements = cls.file.by_type(class_selector.children[0]) else: - elements = [e for e in cls.elements if e.is_a( - class_selector.children[0])] + elements = [e for e in cls.elements if e.is_a(class_selector.children[0])] if len(class_selector.children) > 1 and class_selector.children[1].data == "filter": return cls.filter_elements(elements, class_selector.children[1]) return elements @@ -185,7 +184,7 @@ class Selector: elif token_type == "SIGNED_FLOAT": value = float(filter_rule.children[2].children[0]) elif token_type == "BOOLEAN": - value = filter_rule.children[2].children[0].lower() == 'true' + value = filter_rule.children[2].children[0].lower() == "true" elif token_type == "NULL": value = None for element in elements: @@ -210,8 +209,7 @@ class Selector: key = ".".join(key.split(".")[1:]) elif "." in key and key.split(".")[0] == "material": try: - element = ifcopenshell.util.element.get_material( - element, should_skip_usage=True) + element = ifcopenshell.util.element.get_material(element, should_skip_usage=True) if not element: return None except: