From f0a19cb054961ec9960ffeb8cf20e8b99323bf98 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 7 Jun 2024 20:08:11 +1000 Subject: [PATCH] Fix #4815. Purge deprecated old selector syntax. --- .../blenderbim/bim/module/group/__init__.py | 1 - .../blenderbim/bim/module/group/operator.py | 31 -- .../blenderbim/bim/module/group/prop.py | 1 - .../blenderbim/bim/module/group/ui.py | 10 +- .../blenderbim/bim/module/search/operator.py | 3 - .../blenderbim/bim/module/search/prop.py | 5 +- src/blenderbim/blenderbim/core/search.py | 8 - src/blenderbim/blenderbim/core/tool.py | 4 +- src/blenderbim/blenderbim/tool/search.py | 7 - .../ifcopenshell/util/selector.py | 490 +++++------------- .../test/util/test_selector.py | 233 --------- 11 files changed, 128 insertions(+), 665 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/group/__init__.py b/src/blenderbim/blenderbim/bim/module/group/__init__.py index 4650cb1833..f89bcaa605 100644 --- a/src/blenderbim/blenderbim/bim/module/group/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/group/__init__.py @@ -31,7 +31,6 @@ classes = ( operator.SelectGroupProducts, operator.ToggleGroup, operator.UnassignGroup, - operator.UpdateGroup, operator.SelectGroupElements, prop.ExpandedGroups, prop.Group, diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py index ddb5bb8845..528b1b59b8 100644 --- a/src/blenderbim/blenderbim/bim/module/group/operator.py +++ b/src/blenderbim/blenderbim/bim/module/group/operator.py @@ -22,7 +22,6 @@ import ifcopenshell.api import blenderbim.bim.helper import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore -from ifcopenshell.util.selector import Selector import json @@ -48,7 +47,6 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator): new = self.props.groups.add() new.ifc_definition_id = group.id() new.name = group.Name or "Unnamed" - new.selection_query = "" new.tree_depth = tree_depth new.has_children = False new.is_expanded = group.id() in self.expanded_groups @@ -113,7 +111,6 @@ class EditGroup(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_group" bl_label = "Edit Group" bl_options = {"REGISTER", "UNDO"} - copy_from_selector: bpy.props.BoolProperty(name="Copy from Selector", default=False) def _execute(self, context): props = context.scene.BIMGroupProperties @@ -124,9 +121,6 @@ class EditGroup(bpy.types.Operator, tool.Ifc.Operator): else: attributes[attribute.name] = attribute.string_value - if self.copy_from_selector: - attributes["Description"] = context.scene.IFCSelector.selection_query - self.file = IfcStore.get_file() ifcopenshell.api.run( "group.edit_group", self.file, **{"group": self.file.by_id(props.active_group_id), "attributes": attributes} @@ -242,31 +236,6 @@ class SelectGroupProducts(bpy.types.Operator, tool.Ifc.Operator): return {"FINISHED"} -class UpdateGroup(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.update_group" - bl_label = "Update Group" - bl_options = {"REGISTER", "UNDO"} - query: bpy.props.StringProperty() - group_id: bpy.props.IntProperty() - - def _execute(self, context): - self.file = IfcStore.get_file() - group = self.file.by_id(self.group_id) - query = self.query - - new_products = Selector.parse(self.file, query) - ifcopenshell.api.run( - "group.update_group_products", - self.file, - **{ - "group": group, - "products": new_products, - } - ) - bpy.ops.bim.load_groups() - return {"FINISHED"} - - class SelectGroupElements(bpy.types.Operator): bl_idname = "bim.select_group_elements" bl_label = "Select Group elements" diff --git a/src/blenderbim/blenderbim/bim/module/group/prop.py b/src/blenderbim/blenderbim/bim/module/group/prop.py index bec18bbdae..c4f6133533 100644 --- a/src/blenderbim/blenderbim/bim/module/group/prop.py +++ b/src/blenderbim/blenderbim/bim/module/group/prop.py @@ -44,7 +44,6 @@ class ExpandedGroups(StrProperty): class Group(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") - selection_query: StringProperty(name="Selection Query") is_expanded: BoolProperty(name="Is Expanded", default=False) has_children: BoolProperty(name="Has Children", default=False) tree_depth: IntProperty(name="Tree Depth") diff --git a/src/blenderbim/blenderbim/bim/module/group/ui.py b/src/blenderbim/blenderbim/bim/module/group/ui.py index 5762d8f705..ff0487cc13 100644 --- a/src/blenderbim/blenderbim/bim/module/group/ui.py +++ b/src/blenderbim/blenderbim/bim/module/group/ui.py @@ -131,7 +131,7 @@ class BIM_UL_groups(UIList): else: row.label(text="", icon="BLANK1") - row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name) + row.label(text=item.name) group_id = item.ifc_definition_id if context.scene.BIMGroupProperties.active_group_id == group_id: op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") @@ -145,10 +145,6 @@ class BIM_UL_groups(UIList): op.group = group_id op = row.operator("bim.remove_group", text="", icon="X") op.group = group_id - if item.selection_query != "": - op = row.operator("bim.update_group", text="", icon="FILE_REFRESH") - op.group_id = item.ifc_definition_id - op.query = item.selection_query else: op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF") op.group = group_id @@ -158,10 +154,6 @@ class BIM_UL_groups(UIList): op.group = group_id op = row.operator("bim.remove_group", text="", icon="X") op.group = group_id - if item.selection_query != "": - op = row.operator("bim.update_group", text="", icon="FILE_REFRESH") - op.group_id = item.ifc_definition_id - op.query = item.selection_query class BIM_UL_object_groups(UIList): diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index b4b56f9f3a..f0516dfdfb 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import re import bpy import json import ifcopenshell @@ -24,10 +23,8 @@ import ifcopenshell.api import ifcopenshell.guid import ifcopenshell.util.element import ifcopenshell.util.selector -from ifcopenshell.util.selector import Selector import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore -from blenderbim.bim.module.group import ui import blenderbim.core.search as core from itertools import cycle from bpy.types import PropertyGroup, Operator diff --git a/src/blenderbim/blenderbim/bim/module/search/prop.py b/src/blenderbim/blenderbim/bim/module/search/prop.py index a7f2cc7d25..d2accf9027 100644 --- a/src/blenderbim/blenderbim/bim/module/search/prop.py +++ b/src/blenderbim/blenderbim/bim/module/search/prop.py @@ -19,12 +19,9 @@ import bpy import blenderbim.tool as tool from ifcopenshell import util -from ifcopenshell.util.selector import Selector -from blenderbim.bim.prop import ObjProperty, StrProperty, BIMFilterGroup -from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.prop import ObjProperty, BIMFilterGroup from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData from bpy.types import PropertyGroup -from blenderbim.tool.ifc import Ifc from . import ui, prop, operator from bpy.props import ( PointerProperty, diff --git a/src/blenderbim/blenderbim/core/search.py b/src/blenderbim/blenderbim/core/search.py index 4e390cb828..0fe60fdb65 100644 --- a/src/blenderbim/blenderbim/core/search.py +++ b/src/blenderbim/blenderbim/core/search.py @@ -1,10 +1,2 @@ def show_scene_elements(spatial): spatial.show_scene_objects() - -def search(search, spatial, query, action): - products = search.from_selector_query(query) - spatial.deselect_objects() - try: - spatial.filter_products(products, action) - except: - return "One or More Products could not be found because they are hidden in the ViewLayer" \ No newline at end of file diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 59cdff7fc4..7b66ce782e 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -707,7 +707,7 @@ class Selector: @interface class Search: - def from_selector_query(cls, query): pass + pass @interface class Sequence: @@ -831,7 +831,7 @@ class Spatial: def can_reference(cls, structure, element): pass def contract_container(cls, container): pass def copy_xy(cls, src_obj, destination_obj): pass - def import_spatial_structure(cls, element, level_index): pass + def import_spatial_element(cls, element, level_index): pass def deselect_objects(cls): pass def disable_editing(cls, obj): pass def duplicate_object_and_data(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/search.py b/src/blenderbim/blenderbim/tool/search.py index dad303d15b..e018d3aa12 100644 --- a/src/blenderbim/blenderbim/tool/search.py +++ b/src/blenderbim/blenderbim/tool/search.py @@ -2,10 +2,8 @@ import bpy import json import lark import blenderbim.core.tool -import blenderbim.tool as tool import ifcopenshell.guid import ifcopenshell.util.selector -from ifcopenshell.util.selector import Selector from blenderbim.bim.prop import BIMFacet from typing import Union, Literal @@ -115,11 +113,6 @@ class Search(blenderbim.core.tool.Search): return value return '"' + value.replace('"', '\\"') + '"' - @classmethod - def from_selector_query(cls, query: str) -> list[ifcopenshell.entity_instance]: - """Returns a list of products from a selector query""" - return Selector().parse(tool.Ifc.get(), query) - class ImportFilterQueryTransformer(lark.Transformer): def __init__(self, filter_groups): diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 6097b9572c..251668aecc 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -276,7 +276,130 @@ def format(query: str) -> str: def get_element_value(element: ifcopenshell.entity_instance, query: str) -> Any: keys: list[str] = GetElementTransformer().transform(get_element_grammar.parse(query)) - return Selector.get_element_value(element, keys) + return _get_element_value(element, keys) + + +def _get_element_value(element: ifcopenshell.entity_instance, keys: list[str]) -> Any: + value = element + for key in keys: + if value is None: + return + if key == "type": + value = ifcopenshell.util.element.get_type(value) + elif key in ("material", "mat"): + value = ifcopenshell.util.element.get_material(value, should_skip_usage=True) + elif key in ("materials", "mats"): + value = ifcopenshell.util.element.get_materials(value) + elif key == "profiles": + value = ifcopenshell.util.shape.get_profiles(value) + elif key == "styles": + value = ifcopenshell.util.element.get_styles(value) + elif key in ("item", "i"): + if value.is_a("IfcMaterialLayerSet"): + value = value.MaterialLayers + elif value.is_a("IfcMaterialProfileSet"): + value = value.MaterialProfiles + elif value.is_a("IfcMaterialConstituentSet"): + value = value.MaterialConstituents + elif key == "container": + value = ifcopenshell.util.element.get_container(value) + elif key == "space": + value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSpace") + elif key == "storey": + value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuildingStorey") + elif key == "building": + value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuilding") + elif key == "site": + value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSite") + elif key == "parent": + value = ifcopenshell.util.element.get_parent(value) + elif key in ("types", "occurrences"): + value = ifcopenshell.util.element.get_types(value) + elif key == "count": + if isinstance(value, set): + value = len(list(value)) + elif isinstance(value, (list, tuple)): + value = len(value) + else: + value = 1 + elif key == "class": + value = value.is_a() + elif key == "predefined_type": + value = ifcopenshell.util.element.get_predefined_type(value) + elif key == "id": + value = value.id() + elif key == "classification": + value = ifcopenshell.util.classification.get_references(value) + elif key in ("x", "y", "z", "easting", "northing", "elevation") and hasattr(value, "ObjectPlacement"): + if getattr(value, "ObjectPlacement", None): + matrix = ifcopenshell.util.placement.get_local_placement(value.ObjectPlacement) + xyz = matrix[:, 3][:3] + if key in ("x", "y", "z"): + value = xyz["xyz".index(key)] + else: + enh = ifcopenshell.util.geolocation.auto_xyz2enh(element.wrapped_data.file, *xyz) + value = enh[("easting", "northing", "elevation").index(key)] + else: + value = None + elif isinstance(value, ifcopenshell.entity_instance): + if key == "Name" and value.is_a("IfcMaterialLayerSet"): + key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it. + + if isinstance(key, re.Pattern): + attribute = None # Should we support regex attributes? Probably not for now. + else: + attribute = getattr(value, key, None) + + if attribute is not None: + value = attribute + else: + # Try to extract pset + if isinstance(key, re.Pattern): + psets = ifcopenshell.util.element.get_psets(value) + matching_psets = [] + for pset_name, pset in psets.items(): + if key.match(pset_name): + del pset["id"] + matching_psets.append(pset) + result = matching_psets or None + if result and len(result) == 1: + result = result[0] + else: + result = ifcopenshell.util.element.get_pset(value, key) + if result: + del result["id"] + + value = result + elif isinstance(value, dict): # Such as from the result of a prior get_pset + if isinstance(key, re.Pattern): + results = [] + for prop_name, prop_value in value.items(): + if key.match(prop_name): + if isinstance(prop_value, (list, tuple)): + results.extend(prop_value) + else: + results.append(prop_value) + value = results or None + if value and len(value) == 1: + value = value[0] + else: + value = value.get(key, None) + elif isinstance(value, (list, tuple, set)): # If we use regex + if isinstance(key, str) and key.isnumeric(): + try: + value = value[int(key)] + except IndexError: + return + else: + results = [] + for v in value: + subvalue = _get_element_value(v, [key]) + if isinstance(subvalue, list): + results.extend(subvalue) + else: + results.append(subvalue) + value = results + return value def filter_elements( @@ -840,368 +963,3 @@ class FacetTransformer(lark.Transformer): if comparison.startswith("!"): return not result return result - - -class Selector: - @classmethod - def parse( - cls, ifc_file: ifcopenshell.file, query: str, elements: Optional[list[ifcopenshell.entity_instance]] = None - ) -> list[ifcopenshell.entity_instance]: - cls.file = ifc_file - cls.elements = elements - l = lark.Lark( - """start: query (lfunction query)* - query: selector | group - group: "(" query (lfunction query)* ")" - selector: (inverse_relationship)? guid_selector | (inverse_relationship)? class_selector - guid_selector: "#" /[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$]{22}/ - class_selector: "." WORD filter ? - filter: "[" filter_key (comparison filter_value)? "]" - filter_key: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple - filter_value: filter_regex | ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL - filter_regex: "r" ESCAPED_STRING - keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)* - keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)* - keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)* - lfunction: and | or - inverse_relationship: types | decomposed_by | bounded_by | grouped_by - types: "*" - decomposed_by: "@" - bounded_by: "@@" - grouped_by: "@@@" - and: "&" - or: "|" - not: "!" - comparison: (not)* (oneof | contains | morethanequalto | lessthanequalto | equal | morethan | lessthan) - oneof: "%=" - contains: "*=" - morethanequalto: ">=" - lessthanequalto: "<=" - equal: "=" - morethan: ">" - lessthan: "<" - BOOLEAN: "TRUE" | "FALSE" | "true" | "false"| "True" | "False" - NULL: "NULL" - - // Embed common.lark for packaging - DIGIT: "0".."9" - HEXDIGIT: "a".."f"|"A".."F"|DIGIT - INT: DIGIT+ - SIGNED_INT: ["+"|"-"] INT - DECIMAL: INT "." INT? | "." INT - _EXP: ("e"|"E") SIGNED_INT - FLOAT: INT _EXP | DECIMAL _EXP? - SIGNED_FLOAT: ["+"|"-"] FLOAT - NUMBER: FLOAT | INT - SIGNED_NUMBER: ["+"|"-"] NUMBER - _STRING_INNER: /.*?/ - _STRING_ESC_INNER: _STRING_INNER /(? 1 and class_selector.children[1].data == "filter": - return cls.filter_elements(elements, class_selector.children[1]) - return elements - - @classmethod - def filter_elements(cls, elements, filter_rule): - results = [] - filter_query = cls.parse_filter_query(filter_rule.children[0].children[0]) - comparison = value = None - if len(filter_rule.children) > 1: - comparison = filter_rule.children[1].children[0].data - if comparison == "not": - comparison += filter_rule.children[1].children[1].data - filter_value = filter_rule.children[2].children[0] - if isinstance(filter_value, lark.Tree): - is_regex = True - token_type = filter_value.data - else: - is_regex = False - token_type = filter_value.type - if token_type == "filter_regex": - value = str(filter_value.children[0][1:-1]) - elif token_type == "ESCAPED_STRING": - value = str(filter_value[1:-1]) - elif token_type == "SIGNED_INT": - value = int(filter_value) - elif token_type == "SIGNED_FLOAT": - value = float(filter_value) - elif token_type == "BOOLEAN": - value = filter_value.lower() == "true" - elif token_type == "NULL": - value = None - for element in elements: - if filter_query["is_regex"]: - filter_query["keys"] = [re.compile(k) for k in filter_query["keys"]] - element_value = cls.get_element_value(element, filter_query["keys"]) - if element_value is None and value is not None and "not" not in comparison: - continue - if comparison and cls.filter_element(element, element_value, comparison, value, is_regex=is_regex): - results.append(element) - elif not comparison and element_value: - results.append(element) - return results - - @classmethod - def parse_filter_query(cls, filter_query): - keys = filter_query - is_regex = False - if isinstance(keys, str): - keys = [keys] - elif keys.data == "keys_regex": - is_regex = True - keys = [k[1:-1].replace('\\"', '"') for k in keys.children] - elif keys.data == "keys_quoted": - keys = [k[1:-1].replace('\\"', '"') for k in keys.children] - elif keys.data == "keys_simple": - keys = keys.children - return {"keys": keys, "is_regex": is_regex} - - @classmethod - def get_element_value(cls, element: ifcopenshell.entity_instance, keys: list[str]) -> Any: - value = element - for key in keys: - if value is None: - return - if key == "type": - value = ifcopenshell.util.element.get_type(value) - elif key in ("material", "mat"): - value = ifcopenshell.util.element.get_material(value, should_skip_usage=True) - elif key in ("materials", "mats"): - value = ifcopenshell.util.element.get_materials(value) - elif key == "profiles": - value = ifcopenshell.util.shape.get_profiles(value) - elif key == "styles": - value = ifcopenshell.util.element.get_styles(value) - elif key in ("item", "i"): - if value.is_a("IfcMaterialLayerSet"): - value = value.MaterialLayers - elif value.is_a("IfcMaterialProfileSet"): - value = value.MaterialProfiles - elif value.is_a("IfcMaterialConstituentSet"): - value = value.MaterialConstituents - elif key == "container": - value = ifcopenshell.util.element.get_container(value) - elif key == "space": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSpace") - elif key == "storey": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuildingStorey") - elif key == "building": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuilding") - elif key == "site": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSite") - elif key == "parent": - value = ifcopenshell.util.element.get_parent(value) - elif key in ("types", "occurrences"): - value = ifcopenshell.util.element.get_types(value) - elif key == "count": - if isinstance(value, set): - value = len(list(value)) - elif isinstance(value, (list, tuple)): - value = len(value) - else: - value = 1 - elif key == "class": - value = value.is_a() - elif key == "predefined_type": - value = ifcopenshell.util.element.get_predefined_type(value) - elif key == "id": - value = value.id() - elif key == "classification": - value = ifcopenshell.util.classification.get_references(value) - elif key in ("x", "y", "z", "easting", "northing", "elevation") and hasattr(value, "ObjectPlacement"): - if getattr(value, "ObjectPlacement", None): - matrix = ifcopenshell.util.placement.get_local_placement(value.ObjectPlacement) - xyz = matrix[:, 3][:3] - if key in ("x", "y", "z"): - value = xyz["xyz".index(key)] - else: - enh = ifcopenshell.util.geolocation.auto_xyz2enh(element.wrapped_data.file, *xyz) - value = enh[("easting", "northing", "elevation").index(key)] - else: - value = None - elif isinstance(value, ifcopenshell.entity_instance): - if key == "Name" and value.is_a("IfcMaterialLayerSet"): - key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it. - - if isinstance(key, re.Pattern): - attribute = None # Should we support regex attributes? Probably not for now. - else: - attribute = getattr(value, key, None) - - if attribute is not None: - value = attribute - else: - # Try to extract pset - if isinstance(key, re.Pattern): - psets = ifcopenshell.util.element.get_psets(value) - matching_psets = [] - for pset_name, pset in psets.items(): - if key.match(pset_name): - del pset["id"] - matching_psets.append(pset) - result = matching_psets or None - if result and len(result) == 1: - result = result[0] - else: - result = ifcopenshell.util.element.get_pset(value, key) - if result: - del result["id"] - - value = result - elif isinstance(value, dict): # Such as from the result of a prior get_pset - if isinstance(key, re.Pattern): - results = [] - for prop_name, prop_value in value.items(): - if key.match(prop_name): - if isinstance(prop_value, (list, tuple)): - results.extend(prop_value) - else: - results.append(prop_value) - value = results or None - if value and len(value) == 1: - value = value[0] - else: - value = value.get(key, None) - elif isinstance(value, (list, tuple, set)): # If we use regex - if isinstance(key, str) and key.isnumeric(): - try: - value = value[int(key)] - except IndexError: - return - else: - results = [] - for v in value: - subvalue = cls.get_element_value(v, [key]) - if isinstance(subvalue, list): - results.extend(subvalue) - else: - results.append(subvalue) - value = results - return value - - @classmethod - def filter_element(cls, element, element_value, comparison, value, is_regex=False): - if comparison.startswith("not"): - return not cls.filter_element(element, element_value, comparison[3:], value, is_regex=is_regex) - elif comparison == "equal" and isinstance(element_value, list): - if is_regex: - for element_v in element_value: - if re.match(value, element_v): - return True - return False - return value in element_value - elif comparison == "equal": - if is_regex: - return bool(re.match(value, element_value)) - return element_value == value - elif comparison == "contains" and isinstance(element_value, list): - return bool([ev for ev in element_value if value in str(ev)]) - elif comparison == "contains": - return value in str(element_value) - elif comparison == "morethan": - return element_value > value - elif comparison == "lessthan": - return element_value < value - elif comparison == "morethanequalto": - return element_value >= value - elif comparison == "lessthanequalto": - return element_value <= value - elif comparison == "oneof": - return element_value in value.split(",") - return False - - @classmethod - def get_guid_selector(cls, guid_selector): - return [cls.file.by_id(guid_selector.children[0])] diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index 469b8918ef..4dcc177bb7 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -348,236 +348,3 @@ class TestSetElementValue(test.bootstrap.IFC4): layer.Material = material subject.set_element_value(self.file, layer, "Material.Name", "Foo") assert material.Name == "Foo" - - -class TestSelector(test.bootstrap.IFC4): - def test_selecting_from_specified_elements(self): - elements = [ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") for _ in range(2)] - assert subject.Selector.parse(self.file, ".IfcWall", elements[:1]) == [elements[0]] - - def test_selecting_by_class(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") - assert subject.Selector.parse(self.file, ".IfcWall") == [element] - - def test_selecting_by_globalid(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") - assert subject.Selector.parse(self.file, f"#{element.GlobalId}") == [element] - - def test_selecting_by_attribute_existence(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element.Name = "Foobar" - ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") - assert subject.Selector.parse(self.file, ".IfcElement[Name]") == [element] - assert subject.Selector.parse(self.file, ".IfcElement[Description]") == [] - - def test_selecting_by_attribute(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element.Name = "Foobar" - ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") - assert subject.Selector.parse(self.file, '.IfcElement[Name="Foobar"]') == [element] - assert subject.Selector.parse(self.file, '.IfcElement[Name="Foobaz"]') == [] - - def test_selecting_by_regex(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element.Name = "Foobar" - ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") - assert subject.Selector.parse(self.file, '.IfcElement[Name=r"Foo.*"]') == [element] - - def test_selecting_by_property_existence(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="Foo_Bar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo]") == [element] - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Fox]") == [] - assert subject.Selector.parse(self.file, '.IfcElement[r"Foo.*ar"."Fo.*"]') == [element] - - def test_selecting_by_string_property(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="Foo_Bar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"}) - assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo="Bar"]') == [element] - - def test_selecting_by_enumerated_property(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="Pset_WallCommon") - template = ifcopenshell.util.pset.get_template("IFC4").get_by_name("Pset_WallCommon") - ifcopenshell.api.run( - "pset.edit_pset", self.file, pset=pset, properties={"Status": ["NEW"]}, pset_template=template - ) - assert subject.Selector.parse(self.file, '.IfcElement[Pset_WallCommon.Status="NEW"]') == [element] - - def test_selecting_by_integer_property(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="Foo_Bar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": 42}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo=42]") == [element] - - def test_selecting_by_float_property(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="Foo_Bar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": 4.2}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo=4.2]") == [element] - - def test_selecting_by_boolean_property(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="Foo_Bar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": True}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo=TRUE]") == [element] - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": False}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo=FALSE]") == [element] - - def test_selecting_by_null_property(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="Foo_Bar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo=NULL]") == [] - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": None}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo=NULL]") == [element] - - def test_comparing_by_not_equal(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element.Name = "Foobar" - assert subject.Selector.parse(self.file, '.IfcElement[Name!="Foobaz"]') == [element] - assert subject.Selector.parse(self.file, '.IfcElement[Name!="Foobar"]') == [] - - def test_comparing_by_ranges(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="Foo_Bar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": 4.2}) - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo>2]") == [element] - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo>20]") == [] - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo<2]") == [] - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo<20]") == [element] - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo>=4.2]") == [element] - assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo<=4.2]") == [element] - - def test_comparing_if_value_contains_a_wildcard_string(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element.Name = "Foobar" - assert subject.Selector.parse(self.file, '.IfcElement[Name*="Foo"]') == [element] - assert subject.Selector.parse(self.file, '.IfcElement[Name*="oba"]') == [element] - 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_selecting_a_property_which_includes_a_dot(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.b") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"c.d": "Bar"}) - assert subject.Selector.parse(self.file, '.IfcElement["a.b"."c.d"="Bar"]') == [element] - - def test_selecting_a_property_which_includes_an_escaped_quote(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.b"') - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={'"c.d"': "Bar"}) - assert subject.Selector.parse(self.file, r'.IfcElement["\"a.b\""."\"c.d\""="Bar"]') == [element] - - def test_comparing_if_value_is_in_a_list(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element.Name = "Foobar" - assert subject.Selector.parse(self.file, '.IfcElement[Name%="Foobar,Foobaz"]') == [element] - element.Name = "Foobaz" - assert subject.Selector.parse(self.file, '.IfcElement[Name%="Foobar,Foobaz"]') == [element] - element.Name = "Foobat" - assert subject.Selector.parse(self.file, '.IfcElement[Name%="Foobar,Foobaz"]') == [] - - def test_getting_occurrences_of_a_filtered_type(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type) - element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element_type2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element2], relating_type=element_type2) - assert set(subject.Selector.parse(self.file, "* .IfcWallType")) == {element, element2} - - def test_getting_decomposition_of_a_filtered_type(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcMember") - building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") - ifcopenshell.api.run("spatial.assign_container", self.file, products=[element], relating_structure=building) - ifcopenshell.api.run("aggregate.assign_object", self.file, products=[subelement], relating_object=element) - assert set(subject.Selector.parse(self.file, "@ .IfcBuilding")) == {element, subelement} - - def test_selecting_elements_from_a_prefiltered_list(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab") - assert subject.Selector.parse(self.file, ".IfcWall", elements=[element]) - assert not subject.Selector.parse(self.file, ".IfcWall", elements=[element2]) - - def test_selecting_a_property_via_a_wildcard_pset_name(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="Foobar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"}) - pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobaz") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Baz"}) - assert subject.Selector.parse(self.file, '.IfcElement[r"Foo.*"."Foo"="Bar"]') == [element] - - def test_selecting_a_property_via_a_wildcard_property_name(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="Foobar") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"}) - pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foobaz") - ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Baz"}) - assert subject.Selector.parse(self.file, '.IfcElement[r"Foo.*"."F.*"="Bar"]') == [element] - - def test_selecting_an_attribute_via_a_type(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - element_type.Name = "Foo" - ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type) - assert set(subject.Selector.parse(self.file, '.IfcWall[type.Name="Foo"]')) == {element} - - def test_selecting_via_a_material(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") - ifcopenshell.api.run("material.assign_material", self.file, products=[element], material=material) - assert set(subject.Selector.parse(self.file, '.IfcWall[material.Name="CON01"]')) == {element} - - def test_selecting_via_a_material_set(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") - material_set = ifcopenshell.api.run( - "material.add_material_set", self.file, name="FOO", set_type="IfcMaterialLayerSet" - ) - layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material) - ifcopenshell.api.run("material.edit_layer", self.file, layer=layer, attributes={"LayerThickness": 13}) - ifcopenshell.api.run("material.assign_material", self.file, products=[element], material=material_set) - assert set(subject.Selector.parse(self.file, '.IfcWall[material.LayerSetName="FOO"]')) == {element} - - def test_selecting_via_a_material_set_item(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") - material2 = ifcopenshell.api.run("material.add_material", self.file, name="CON02") - material_set = ifcopenshell.api.run( - "material.add_material_set", self.file, name="FOO", set_type="IfcMaterialLayerSet" - ) - layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material) - layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=material_set, material=material2) - ifcopenshell.api.run("material.assign_material", self.file, products=[element], material=material_set) - assert set(subject.Selector.parse(self.file, '.IfcWall[material.item.Material.Name="CON01"]')) == {element} - assert set(subject.Selector.parse(self.file, '.IfcWall[material.item.Material.Name="CON02"]')) == {element} - assert set(subject.Selector.parse(self.file, '.IfcWall[material.item.Material.Name="CON03"]')) == set()