From 3292b98080d8321f25d3012ea939e7a3b302e13a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 8 Aug 2020 17:58:45 +1000 Subject: [PATCH] Allow export of type relations in IFCCSV --- .../blenderbim/bim/import_ifc.py | 12 +- .../blenderbim/bim/operator.py | 11 +- src/ifccsv/ifccsv.py | 193 +----------------- .../ifcopenshell/util/element.py | 9 + .../ifcopenshell/util/selector.py | 7 + 5 files changed, 32 insertions(+), 200 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 78d63bf38e..f31a3a9113 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -2,6 +2,7 @@ import ifcopenshell import ifcopenshell.geom import ifcopenshell.util.geolocation import ifcopenshell.util.selector +import ifcopenshell.util.element import bpy import bmesh import os @@ -1272,19 +1273,10 @@ class IfcImporter(): new_prop.string_value = str(value) def add_defines_by_type_relation(self, element, obj): - related_type = self.get_type(element) + related_type = ifcopenshell.util.element.get_type(element) if related_type: obj.BIMObjectProperties.relating_type = self.type_products[related_type.GlobalId] - # TODO: migrate into util function - def get_type(self, element): - if hasattr(element, 'IsTypedBy') and element.IsTypedBy: - return element.IsTypedBy[0].RelatingType - elif hasattr(element, 'IsDefinedBy') and element.IsDefinedBy: # IFC2X3 - for relationship in element.IsDefinedBy: - if relationship.is_a('IfcRelDefinesByType'): - return relationship.RelatingType - def add_opening_relation(self, element, obj): if not element.is_a('IfcOpeningElement'): return diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 5fbab67e31..23562d02b6 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -5,6 +5,7 @@ import json import logging import webbrowser import ifcopenshell +import ifcopenshell.util.selector import tempfile from . import export_ifc from . import import_ifc @@ -2896,14 +2897,14 @@ class ExportIfcCsv(bpy.types.Operator): def execute(self, context): import ifccsv self.filepath = bpy.path.ensure_ext(self.filepath, '.csv') - ifc_selector_parser = ifccsv.IfcSelectorParser() - ifc_selector_parser.ifc = bpy.context.scene.BIMProperties.ifc_file - ifc_selector_parser.query = bpy.context.scene.BIMProperties.ifc_selector - ifc_selector_parser.parse() + ifc_file = ifcopenshell.open(bpy.context.scene.BIMProperties.ifc_file) + selector = ifcopenshell.util.selector.Selector() + results = selector.parse(ifc_file, bpy.context.scene.BIMProperties.ifc_selector) ifc_csv = ifccsv.IfcCsv() ifc_csv.output = self.filepath ifc_csv.attributes = [a.name for a in bpy.context.scene.BIMProperties.csv_attributes] - ifc_csv.export(ifc_selector_parser.file, ifc_selector_parser.results) + ifc_csv.selector = selector + ifc_csv.export(ifc_file, results) return {'FINISHED'} diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 3fce60cea0..c154556727 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -2,190 +2,12 @@ # This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py` import ifcopenshell +import ifcopenshell.util.selector import csv import lark import argparse -class IfcSelectorParser(): - def __init__(self): - self.ifc = '' - self.file = None - self.query = '' - self.results = [] - - def parse(self): - self.file = ifcopenshell.open(self.ifc) - 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 - types: "*" - contains_elements: "@" - 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 = IfcAttributeExtractor.get_element_key(element, key) - if not element_value: - continue - if not comparison \ - or self.filter_element(element, element_value, comparison, value): - results.append(element) - return results - - 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])] - - class IfcAttributeExtractor(): - @staticmethod - def get_element_key(element, key): - if key == 'IfcClass': - return element.is_a() - elif hasattr(element, key): - return getattr(element, key) - elif '.' not in key: - return None - pset_name, prop = key.split('.', 1) - pset = IfcAttributeExtractor.get_element_pset(element, pset_name) - if pset: - return IfcAttributeExtractor.get_pset_property(pset, prop) - qto = IfcAttributeExtractor.get_element_qto(element, pset_name) - if qto: - return IfcAttributeExtractor.get_qto_property(qto, prop) - return None - @staticmethod def set_element_key(element, key, value): if hasattr(element, key): @@ -268,6 +90,7 @@ class IfcCsv(): self.results = [] self.attributes = [] self.output = '' + self.selector = None def export(self, ifc_file, elements): self.ifc_file = ifc_file @@ -284,7 +107,7 @@ class IfcCsv(): del(self.attributes[index]) for attribute in self.attributes: - result.append(IfcAttributeExtractor.get_element_key(element, attribute)) + result.append(self.selector.get_element_value(element, attribute)) self.results.append(result) with open(self.output, 'w', newline='', encoding='utf-8') as f: @@ -363,14 +186,14 @@ if __name__ == '__main__': args = parser.parse_args() if args.export: - ifc_selector_parser = IfcSelectorParser() - ifc_selector_parser.ifc = args.ifc - ifc_selector_parser.query = args.query - ifc_selector_parser.parse() + ifc_file = ifcopenshell.open(args.ifc) + selector = ifcopenshell.util.selector.Selector() + results = selector.parse(ifc_file, args.query) ifc_csv = IfcCsv() ifc_csv.output = args.csv ifc_csv.attributes = args.arguments if args.arguments else [] - ifc_csv.export(ifc_selector_parser.file, ifc_selector_parser.results) + ifc_csv.selector = selector + ifc_csv.export(ifc_file, results) elif getattr(args, 'import'): ifc_csv = IfcCsv() ifc_csv.output = args.csv diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 19cd633a71..356d1550b5 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -53,6 +53,15 @@ def get_properties(properties): return results +def get_type(element): + if hasattr(element, 'IsTypedBy') and element.IsTypedBy: + return element.IsTypedBy[0].RelatingType + elif hasattr(element, 'IsDefinedBy') and element.IsDefinedBy: # IFC2X3 + for relationship in element.IsDefinedBy: + if relationship.is_a('IfcRelDefinesByType'): + return relationship.RelatingType + + def replace_attribute(element, old, new): for i, attribute in enumerate(element): if attribute == old: diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 19bc307af1..00ab727cf7 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -210,6 +210,13 @@ class Selector(): 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) + except: + return + key = '.'.join(key.split('.')[1:]) info = element.get_info() if key in info: return info[key]