From b8d191ac1c40c0136587f74f52693cc6e97ad813 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 21 Aug 2023 23:44:20 +1000 Subject: [PATCH] Now support custom queries for advanced users in the facet selector (similar to old selector). Also support types and count keys. (e.g. filter by objects with zero types, or zero materials, etc). --- src/blenderbim/blenderbim/bim/helper.py | 4 +++ .../blenderbim/bim/module/search/operator.py | 1 + .../blenderbim/bim/module/search/prop.py | 3 +- src/blenderbim/blenderbim/tool/search.py | 24 +++++++-------- .../ifcopenshell/util/selector.py | 30 +++++++++++++++---- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 40af36886b..8b6ffe2a6b 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -324,6 +324,10 @@ def draw_filter(layout, props, data, module): elif ifc_filter.type == "location": row = box.row(align=True) row.prop(ifc_filter, "name", text="", icon="PACKAGE") + elif ifc_filter.type == "query": + row = box.row(align=True) + row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA") + row.prop(ifc_filter, "value", text="") elif ifc_filter.type == "instance": row = box.row(align=True) row.prop(ifc_filter, "value", text="", icon="GRIP") diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index 62a5b496b6..c929938632 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -235,6 +235,7 @@ class SaveSearch(Operator, tool.Ifc.Operator): group = [g for g in tool.Ifc.get().by_type("IfcGroup") if g.Name == self.name] if group: group = group[0] + group.Description = description else: group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description) if results: diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index 8b51f58a74..bdcc3d223c 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -128,7 +128,8 @@ class BIMSearchProperties(PropertyGroup): ("classification", "Classification", "", "OUTLINER", 4), ("location", "Location", "", "PACKAGE", 5), ("type", "Type", "", "FILE_VOLUME", 6), - ("instance", "GlobalId", "", "GRIP", 7), + ("query", "Query", "", "POINTCLOUD_DATA", 7), + ("instance", "GlobalId", "", "GRIP", 8), ], ) saved_searches: EnumProperty(items=get_saved_searches, name="Saved Searches") diff --git a/src/blenderbim/blenderbim/tool/search.py b/src/blenderbim/blenderbim/tool/search.py index 892a49a3b4..49c5b978ef 100644 --- a/src/blenderbim/blenderbim/tool/search.py +++ b/src/blenderbim/blenderbim/tool/search.py @@ -61,6 +61,10 @@ class Search(blenderbim.core.tool.Search): elif ifc_filter.type == "location": comparison, value = cls.get_comparison_and_value(ifc_filter) filter_group_query.append(f"location{comparison}{value}") + elif ifc_filter.type == "query": + keys = cls.wrap_value(ifc_filter, ifc_filter.name) + comparison, value = cls.get_comparison_and_value(ifc_filter) + filter_group_query.append(f"query:{keys}{comparison}{value}") if not has_instance_or_entity_filter: filter_group_query.insert(0, "IfcProduct") filter_group_query.insert(0, "IfcTypeProduct") @@ -158,9 +162,16 @@ class ImportFilterQueryTransformer(lark.Transformer): comparison, value = args return {"type": "location", "value": f"{comparison}{value}"} + def query(self, args): + keys, comparison, value = args + return {"type": "query", "name": keys, "value": f"{comparison}{value}"} + def comparison(self, args): return "" if args[0].data == "equals" else "!=" + def keys(self, args): + return self.value(args) + def pset(self, args): return self.value(args) @@ -181,16 +192,3 @@ class ImportFilterQueryTransformer(lark.Transformer): return "TRUE" elif args[0].children[0].data == "false": return "FALSE" - - def compare(self, element_value, comparison, value): - if isinstance(value, str): - if isinstance(element_value, int): - value = int(value) - elif isinstance(element_value, float): - value = float(value) - result = element_value == value - elif isinstance(value, re.Pattern): - result = bool(value.match(element_value)) - elif value in (None, True, False): - result = element_value is value - return result if comparison == "=" else not result diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 6929b69e05..a4a2ce4f0f 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -29,7 +29,7 @@ filter_elements_grammar = lark.Lark( filter_group: facet_list ("+" facet_list)* facet_list: facet ("," facet)* - facet: instance | entity | attribute | type | material | property | classification | location + facet: instance | entity | attribute | type | material | query | classification | location | property instance: not? globalid globalid: /[0-3][a-zA-Z0-9_$]{21}/ @@ -40,9 +40,11 @@ filter_elements_grammar = lark.Lark( property: pset "." prop comparison value classification: "classification" comparison value location: "location" comparison value + query: "query:" keys comparison value pset: quoted_string | unquoted_string | regex_string prop: quoted_string | unquoted_string | regex_string + keys: quoted_string | unquoted_string attribute_name: /[A-Z]\\w+/ ifc_class: /Ifc\\w+/ @@ -361,6 +363,14 @@ class FacetTransformer(lark.Transformer): self.elements = set(filter(filter_function, self.elements)) + def query(self, args): + keys, comparison, value = args + + def filter_function(element): + return self.compare(get_element_value(element, keys), comparison, value) + + self.elements = set(filter(filter_function, self.elements)) + def get_container_tree(self, container): tree = self.container_trees.get(container, None) if tree: @@ -382,6 +392,9 @@ class FacetTransformer(lark.Transformer): def comparison(self, args): return "=" if args[0].data == "equals" else "!=" + def keys(self, args): + return self.value(args) + def pset(self, args): return self.value(args) @@ -646,13 +659,20 @@ class Selector: elif key == "container": value = ifcopenshell.util.element.get_container(value) elif key == "space": - element = ifcopenshell.util.element.get_container(element, ifc_class="IfcSpace") + value = ifcopenshell.util.element.get_container(element, ifc_class="IfcSpace") elif key == "storey": - element = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuildingStorey") + value = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuildingStorey") elif key == "building": - element = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuilding") + value = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuilding") elif key == "site": - element = ifcopenshell.util.element.get_container(element, ifc_class="IfcSite") + value = ifcopenshell.util.element.get_container(element, ifc_class="IfcSite") + elif key == "types": + value = ifcopenshell.util.element.get_types(element) + elif key == "count": + if isinstance(value, set): + value = len(list(value)) + elif isinstance(value, (list, tuple)): + value = len(value) elif key == "class": value = value.is_a() elif key == "id":