import ifcopenshell.util import ifcopenshell.util.fm import ifcopenshell.util.element import lark class Selector: def parse(self, ifc_file, query): self.file = ifc_file 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 | pset_or_qto filter_value: ESCAPED_STRING pset_or_qto: /[A-Za-z0-9_]+/ "." /[A-Za-z0-9_]+/ lfunction: and | or inverse_relationship: types | contains_elements | boundedby types: "*" contains_elements: "@" boundedby: "@@" and: "&" or: "|" comparison: contains | morethanequalto | lessthanequalto | equal | morethan | lessthan contains: "*=" morethanequalto: ">=" lessthanequalto: "<" equal: "=" morethan: ">" lessthan: "<" // 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 self.filter_elements(elements, class_selector.children[1]) return elements def filter_elements(self, elements, filter_rule): results = [] key = filter_rule.children[0].children[0] if not isinstance(key, str): key = key.children[0] + "." + key.children[1] comparison = value = None if len(filter_rule.children) > 1: comparison = filter_rule.children[1].children[0].data value = filter_rule.children[2].children[0][1:-1] for element in elements: element_value = self.get_element_value(element, key) if element_value is None: continue if not comparison or self.filter_element(element, element_value, comparison, value): results.append(element) return results def get_element_value(self, element, key): if "." in key and key.split(".")[0] == "type": try: element = ifcopenshell.util.element.get_type(element) if not element: return None except: return key = ".".join(key.split(".")[1:]) elif "." in key and key.split(".")[0] == "material": try: element = ifcopenshell.util.element.get_material(element, should_skip_usage=True) if not element: return None except: return key = ".".join(key.split(".")[1:]) elif "." in key and key.split(".")[0] == "container": try: element = ifcopenshell.util.element.get_container(element) if not element: return None except: return key = ".".join(key.split(".")[1:]) info = element.get_info() if key in info: return info[key] elif "." in key: pset_name, prop = key.split(".") psets = ifcopenshell.util.element.get_psets(element) if pset_name in psets and prop in psets[pset_name]: return psets[pset_name][prop] def filter_element(self, element, element_value, comparison, value): if comparison == "equal": return str(element_value) == value elif comparison == "contains": return value in str(element_value) elif comparison == "morethan": return element_value > float(value) elif comparison == "lessthan": return element_value < float(value) elif comparison == "morethanequalto": return element_value >= float(value) elif comparison == "lessthanequalto": return element_value <= float(value) return False def get_guid_selector(self, guid_selector): return [self.file.by_id(guid_selector.children[0])]