Selector now uses classmethods to make it easier for scriptwriters to use

This commit is contained in:
Dion Moult
2022-03-21 17:28:16 +11:00
parent c478e5bf1b
commit 44afa0df5d
2 changed files with 83 additions and 26 deletions
@@ -23,8 +23,9 @@ import lark
class Selector: class Selector:
def parse(self, ifc_file, query): @classmethod
self.file = ifc_file def parse(cls, ifc_file, query):
cls.file = ifc_file
l = lark.Lark( l = lark.Lark(
"""start: query (lfunction query)* """start: query (lfunction query)*
@@ -82,13 +83,14 @@ class Selector:
) )
start = l.parse(query) start = l.parse(query)
return self.get_group(start) return cls.get_group(start)
def get_group(self, group): @classmethod
def get_group(cls, group):
lfunction = None lfunction = None
for child in group.children: for child in group.children:
if child.data == "query": if child.data == "query":
new_results = self.get_query(child) new_results = cls.get_query(child)
if not lfunction: if not lfunction:
results = new_results results = new_results
elif lfunction == "or": elif lfunction == "or":
@@ -100,14 +102,16 @@ class Selector:
lfunction = child.children[0].data lfunction = child.children[0].data
return results return results
def get_query(self, query): @classmethod
def get_query(cls, query):
for child in query.children: for child in query.children:
if child.data == "selector": if child.data == "selector":
return self.get_selector(child) return cls.get_selector(child)
elif child.data == "group": elif child.data == "group":
return self.get_group(child) return cls.get_group(child)
def get_selector(self, selector): @classmethod
def get_selector(cls, selector):
if len(selector.children) == 1: if len(selector.children) == 1:
inverse_relationship = None inverse_relationship = None
class_or_guid_selector = selector.children[0] class_or_guid_selector = selector.children[0]
@@ -116,15 +120,16 @@ class Selector:
class_or_guid_selector = selector.children[1] class_or_guid_selector = selector.children[1]
if class_or_guid_selector.data == "class_selector": if class_or_guid_selector.data == "class_selector":
results = self.get_class_selector(class_or_guid_selector) results = cls.get_class_selector(class_or_guid_selector)
elif class_or_guid_selector.data == "guid_selector": elif class_or_guid_selector.data == "guid_selector":
results = self.get_guid_selector(class_or_guid_selector) results = cls.get_guid_selector(class_or_guid_selector)
if not inverse_relationship: if not inverse_relationship:
return results return results
return self.parse_inverse_relationship(results, inverse_relationship.children[0].data) return cls.parse_inverse_relationship(results, inverse_relationship.children[0].data)
def parse_inverse_relationship(self, elements, inverse_relationship): @classmethod
def parse_inverse_relationship(cls, elements, inverse_relationship):
results = [] results = []
for element in elements: for element in elements:
if inverse_relationship == "types": if inverse_relationship == "types":
@@ -140,20 +145,22 @@ class Selector:
results.append(relationship.RelatedBuildingElement) results.append(relationship.RelatedBuildingElement)
return results return results
def get_class_selector(self, class_selector): @classmethod
def get_class_selector(cls, class_selector):
if class_selector.children[0] == "COBie": if class_selector.children[0] == "COBie":
elements = ifcopenshell.util.fm.get_cobie_components(self.file) elements = ifcopenshell.util.fm.get_cobie_components(cls.file)
elif class_selector.children[0] == "COBieType": elif class_selector.children[0] == "COBieType":
elements = ifcopenshell.util.fm.get_cobie_types(self.file) elements = ifcopenshell.util.fm.get_cobie_types(cls.file)
elif class_selector.children[0] == "FMHEM": elif class_selector.children[0] == "FMHEM":
elements = ifcopenshell.util.fm.get_fmhem_types(self.file) elements = ifcopenshell.util.fm.get_fmhem_types(cls.file)
else: else:
elements = self.file.by_type(class_selector.children[0]) elements = cls.file.by_type(class_selector.children[0])
if len(class_selector.children) > 1 and class_selector.children[1].data == "filter": if len(class_selector.children) > 1 and class_selector.children[1].data == "filter":
return self.filter_elements(elements, class_selector.children[1]) return cls.filter_elements(elements, class_selector.children[1])
return elements return elements
def filter_elements(self, elements, filter_rule): @classmethod
def filter_elements(cls, elements, filter_rule):
results = [] results = []
key = filter_rule.children[0].children[0] key = filter_rule.children[0].children[0]
if not isinstance(key, str): if not isinstance(key, str):
@@ -163,14 +170,15 @@ class Selector:
comparison = filter_rule.children[1].children[0].data comparison = filter_rule.children[1].children[0].data
value = filter_rule.children[2].children[0][1:-1] value = filter_rule.children[2].children[0][1:-1]
for element in elements: for element in elements:
element_value = self.get_element_value(element, key) element_value = cls.get_element_value(element, key)
if element_value is None: if element_value is None:
continue continue
if not comparison or self.filter_element(element, element_value, comparison, value): if not comparison or cls.filter_element(element, element_value, comparison, value):
results.append(element) results.append(element)
return results return results
def get_element_value(self, element, key): @classmethod
def get_element_value(cls, element, key):
if "." in key and key.split(".")[0] == "type": if "." in key and key.split(".")[0] == "type":
try: try:
element = ifcopenshell.util.element.get_type(element) element = ifcopenshell.util.element.get_type(element)
@@ -206,7 +214,8 @@ class Selector:
if pset_name in psets and prop in psets[pset_name]: if pset_name in psets and prop in psets[pset_name]:
return psets[pset_name][prop] return psets[pset_name][prop]
def filter_element(self, element, element_value, comparison, value): @classmethod
def filter_element(cls, element, element_value, comparison, value):
if comparison == "equal": if comparison == "equal":
return str(element_value) == value return str(element_value) == value
elif comparison == "contains": elif comparison == "contains":
@@ -221,5 +230,6 @@ class Selector:
return element_value <= float(value) return element_value <= float(value)
return False return False
def get_guid_selector(self, guid_selector): @classmethod
return [self.file.by_id(guid_selector.children[0])] def get_guid_selector(cls, guid_selector):
return [cls.file.by_id(guid_selector.children[0])]
@@ -0,0 +1,47 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.selector as subject
class TestSelector(test.bootstrap.IFC4):
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(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_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]