mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
Allow export of type relations in IFCCSV
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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'}
|
||||
|
||||
|
||||
|
||||
+8
-185
@@ -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 /(?<!\\\\)(\\\\\\\\)*?/
|
||||
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(self.query)
|
||||
self.results = self.get_group(start)
|
||||
|
||||
def get_group(self, group):
|
||||
lfunction = None
|
||||
for child in group.children:
|
||||
if child.data == 'query':
|
||||
new_results = self.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
|
||||
|
||||
def get_query(self, query):
|
||||
for child in query.children:
|
||||
if child.data == 'selector':
|
||||
return self.get_selector(child)
|
||||
elif child.data == 'group':
|
||||
return self.get_group(child)
|
||||
|
||||
def get_selector(self, 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 = self.get_class_selector(class_or_guid_selector)
|
||||
elif class_or_guid_selector.data == 'guid_selector':
|
||||
results = self.get_guid_selector(class_or_guid_selector)
|
||||
|
||||
if not inverse_relationship:
|
||||
return results
|
||||
return self.parse_inverse_relationship(results, inverse_relationship.children[0].data)
|
||||
|
||||
def parse_inverse_relationship(self, 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 == 'contains_elements' \
|
||||
and hasattr(element, 'ContainsElements'):
|
||||
for relationship in element.ContainsElements:
|
||||
results.extend(relationship.RelatedElements)
|
||||
return results
|
||||
|
||||
def get_class_selector(self, class_selector):
|
||||
elements = self.file.by_type(class_selector.children[0])
|
||||
if len(class_selector.children) > 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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user