Fix #4815. Purge deprecated old selector syntax.

This commit is contained in:
Dion Moult
2024-06-07 20:08:11 +10:00
parent f565b4e8f0
commit f0a19cb054
11 changed files with 128 additions and 665 deletions
@@ -31,7 +31,6 @@ classes = (
operator.SelectGroupProducts,
operator.ToggleGroup,
operator.UnassignGroup,
operator.UpdateGroup,
operator.SelectGroupElements,
prop.ExpandedGroups,
prop.Group,
@@ -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"
@@ -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")
@@ -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):
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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
@@ -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,
-8
View File
@@ -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"
+2 -2
View File
@@ -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
-7
View File
@@ -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):
@@ -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 /(?<!\\\\)(\\\\\\\\)*?/
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
LCASE_LETTER: "a".."z"
UCASE_LETTER: "A".."Z"
LETTER: UCASE_LETTER | LCASE_LETTER
WORD: LETTER+
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
WS_INLINE: (" "|/\\t/)+
WS: /[ \\t\\f\\r\\n]/+
CR : /\\r/
LF : /\\n/
NEWLINE: (CR? LF)+
%ignore WS // Disregard spaces in text
"""
)
start = l.parse(query)
return cls.get_group(start)
@classmethod
def get_group(cls, group):
lfunction = None
for child in group.children:
if child.data == "query":
new_results = cls.get_query(child)
if not lfunction:
results = new_results
elif lfunction == "or":
results.extend(new_results)
elif lfunction == "and":
results = list(set(results).intersection(new_results))
results = list(set(results))
elif child.data == "lfunction":
lfunction = child.children[0].data
return results
@classmethod
def get_query(cls, query):
for child in query.children:
if child.data == "selector":
return cls.get_selector(child)
elif child.data == "group":
return cls.get_group(child)
@classmethod
def get_selector(cls, selector):
if len(selector.children) == 1:
inverse_relationship = None
class_or_guid_selector = selector.children[0]
else:
inverse_relationship = selector.children[0]
class_or_guid_selector = selector.children[1]
if class_or_guid_selector.data == "class_selector":
results = cls.get_class_selector(class_or_guid_selector)
elif class_or_guid_selector.data == "guid_selector":
results = cls.get_guid_selector(class_or_guid_selector)
if not inverse_relationship:
return results
return cls.parse_inverse_relationship(results, inverse_relationship.children[0].data)
@classmethod
def parse_inverse_relationship(cls, elements, inverse_relationship):
results = []
for element in elements:
if inverse_relationship == "types":
if hasattr(element, "Types") and element.Types:
results.extend(element.Types[0].RelatedObjects)
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))
elif inverse_relationship == "grouped_by":
results.extend(ifcopenshell.util.element.get_grouped_by(element))
elif inverse_relationship == "bounded_by" and hasattr(element, "BoundedBy"):
for relationship in element.BoundedBy:
results.append(relationship.RelatedBuildingElement)
return results
@classmethod
def get_class_selector(cls, class_selector):
if class_selector.children[0] == "COBie":
elements = ifcopenshell.util.fm.get_cobie_components(cls.file)
elif class_selector.children[0] == "COBieType":
elements = ifcopenshell.util.fm.get_cobie_types(cls.file)
elif class_selector.children[0] == "FMHEM":
elements = ifcopenshell.util.fm.get_fmhem_types(cls.file)
else:
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])]
if len(class_selector.children) > 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])]
@@ -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()