Selector: added new tests and improved UX

Selector: added tests to check non-standard characters and missing values
This commit is contained in:
Vukas Pajic
2022-07-21 07:37:48 +02:00
committed by GitHub
6 changed files with 70 additions and 16 deletions
@@ -38,6 +38,7 @@ classes = (
operator.SaveSelectorQuery, operator.SaveSelectorQuery,
operator.OpenQueryLibrary, operator.OpenQueryLibrary,
operator.LoadQuery, operator.LoadQuery,
operator.AddToIfcGroup,
prop.BIMFilterClasses, prop.BIMFilterClasses,
prop.BIMFilterBuildingStoreys, prop.BIMFilterBuildingStoreys,
prop.BIMSearchProperties, prop.BIMSearchProperties,
@@ -20,6 +20,7 @@ import re
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
from ifcopenshell.api.group.data import Data
from ifcopenshell.util.selector import Selector from ifcopenshell.util.selector import Selector
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -65,6 +66,13 @@ def does_keyword_exist(pattern, string, context):
elif string == pattern: elif string == pattern:
return True return True
# hack to close popup
# https://blender.stackexchange.com/a/202576/130742
def close_operator_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.01)
class EditBlenderCollection(Operator): class EditBlenderCollection(Operator):
bl_idname = "bim.edit_blender_collection" bl_idname = "bim.edit_blender_collection"
@@ -513,7 +521,7 @@ class FilterModelElements(Operator):
selection = self.add_filters(selection, query) selection = self.add_filters(selection, query)
elif query.selector == "GlobalId": elif query.selector == "GlobalId":
selection += f"#{query.global_id}" selection += f"#{query.value}"
elif query.selector == "IfcElementType": elif query.selector == "IfcElementType":
index = int(query.active_sub_option.split(":")[0]) index = int(query.active_sub_option.split(":")[0])
@@ -562,7 +570,6 @@ class FilterModelElements(Operator):
class IfcSelector(Operator): class IfcSelector(Operator):
"""Select elements in model with IFC Selector""" """Select elements in model with IFC Selector"""
bl_idname = "bim.ifc_selector" bl_idname = "bim.ifc_selector"
bl_label = "Select elements with IFC Selector" bl_label = "Select elements with IFC Selector"
@@ -603,19 +610,11 @@ class SaveSelectorQuery(Operator):
class OpenQueryLibrary(Operator): class OpenQueryLibrary(Operator):
"""Open Query Library""" """Open Query Library"""
bl_idname = "bim.open_query_library" bl_idname = "bim.open_query_library"
bl_label = "Open Query Library" bl_label = "Open Query Library"
def invoke(self, context, event): def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400) return context.window_manager.invoke_popup(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): def draw(self, context):
layout = self.layout layout = self.layout
@@ -642,7 +641,37 @@ class LoadQuery(Operator):
bl_label = "Load Query" bl_label = "Load Query"
index: IntProperty() index: IntProperty()
def invoke(self, context, event):
close_operator_panel(event)
return self.execute(context)
def execute(self, context): def execute(self, context):
ifc_selector = context.scene.IfcSelectorProperties ifc_selector = context.scene.IfcSelectorProperties
ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query
return {"FINISHED"} return {"FINISHED"}
class AddToIfcGroup(Operator):
bl_idname = "bim.add_to_ifc_group"
bl_label = "Add to IFC Group"
group_name: StringProperty(name="Group Name")
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, "group_name")
def execute(self, context):
self.file = IfcStore.get_file()
ifc_selector = context.scene.IfcSelectorProperties
selector_query_syntax = ifc_selector.selector_query_syntax
group = ifcopenshell.api.run("group.add_group", self.file, **{"Name": self.group_name})
objects = Selector.parse(self.file, selector_query_syntax)
for obj in objects:
ifcopenshell.api.run("group.assign_group", self.file, **{"product": obj, "group": group})
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -153,7 +153,8 @@ class IfcSelectorUI:
row = layout.row(align=True) row = layout.row(align=True)
row.alignment = "CENTER" row.alignment = "CENTER"
row.operator("bim.save_selector_query", text="Save Query") row.operator("bim.save_selector_query", text="Save Query")
row.operator("bim.open_query_library", text="Load Query") op = row.operator("bim.open_query_library", text="Load Query")
row.operator("bim.add_to_ifc_group", text="Add to IFC Group")
def draw_query_group_ui(self, ifc_selector, layout): def draw_query_group_ui(self, ifc_selector, layout):
for index, group in enumerate(ifc_selector.groups): for index, group in enumerate(ifc_selector.groups):
@@ -23,7 +23,9 @@ import ifcopenshell.api
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
self.settings = {} self.settings = {
"Name": "Unnamed"
}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
@@ -33,6 +35,6 @@ class Usecase:
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"Name": "Unnamed", "Name": self.settings["Name"],
} }
) )
@@ -189,7 +189,7 @@ class Selector:
value = None value = None
for element in elements: for element in elements:
element_value = cls.get_element_value(element, key) element_value = cls.get_element_value(element, key)
if element_value is None and value is not None: if element_value is None and value is not None and "not" not in comparison:
continue continue
if comparison and cls.filter_element(element, element_value, comparison, value): if comparison and cls.filter_element(element, element_value, comparison, value):
results.append(element) results.append(element)
@@ -112,6 +112,27 @@ class TestSelector(test.bootstrap.IFC4):
assert subject.Selector.parse(self.file, '.IfcElement[Name*="oba"]') == [element] assert subject.Selector.parse(self.file, '.IfcElement[Name*="oba"]') == [element]
assert subject.Selector.parse(self.file, '.IfcElement[Name*="abc"]') == [] assert subject.Selector.parse(self.file, '.IfcElement[Name*="abc"]') == []
def test_selecting_if_value_not_matching(self):
element_1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element_2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset_1 = ifcopenshell.api.run("pset.add_pset", self.file, product=element_1, name="Foo_Bar")
pset_2 = ifcopenshell.api.run("pset.add_pset", self.file, product=element_2, name="Foo_Bar")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_1, properties={"Foo": "Bar"})
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_2, properties={"Foo": "BOO"})
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "Bar"]') == [element_2]
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "BOO"]') == [element_1]
def test_selecting_when_attribute_is_none(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
assert subject.Selector.parse(self.file, '.IfcElement[PredefinedType !="non-existent predefined type"]') == [element]
def test_selecting_a_property_which_includes_non_standard_characters(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="a !%$§&/()?|*-+,€~#@µ^°a")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a !%$§&/()?|*-+,€~#@µ^°a": "Bar"})
assert subject.Selector.parse(self.file, '.IfcElement[a !%$§&/()?|*-+,€~#@µ^°a.a !%$§&/()?|*-+,€~#@µ^°a="Bar"]') == [element]
def test_comparing_if_value_is_in_a_list(self): def test_comparing_if_value_is_in_a_list(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element.Name = "Foobar" element.Name = "Foobar"