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: