mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Add util module to IfcOpenShell, including element, geolocation, unit, and selector. See #829.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
def get_psets(element):
|
||||
psets = {}
|
||||
try:
|
||||
if element.is_a('IfcTypeObject'):
|
||||
if element.HasPropertySets:
|
||||
for definition in element.HasPropertySets:
|
||||
psets[definition.Name] = get_properties(definition)
|
||||
else:
|
||||
for relationship in element.IsDefinedBy:
|
||||
if relationship.is_a('IfcRelDefinesByProperties'):
|
||||
definition = relationship.RelatingPropertyDefinition
|
||||
psets[definition.Name] = get_properties(definition)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print('failed to load properties: {}'.format(e))
|
||||
traceback.print_exc()
|
||||
return psets
|
||||
|
||||
def get_properties(definition):
|
||||
if definition is not None:
|
||||
props = {}
|
||||
if definition.is_a('IfcElementQuantity'):
|
||||
for q in definition.Quantities:
|
||||
if q.is_a('IfcPhysicalSimpleQuantity'):
|
||||
props[q.Name] = q[3]
|
||||
elif definition.is_a('IfcPropertySet'):
|
||||
for prop in definition.HasProperties:
|
||||
if prop.is_a('IfcPropertySingleValue'):
|
||||
props[prop.Name] = prop.NominalValue
|
||||
else:
|
||||
# Entity introduced in IFC4
|
||||
# definition.is_a('IfcPreDefinedPropertySet'):
|
||||
for prop in range(4, len(definition)):
|
||||
props[definition.attribute_name(prop)] = definition[prop]
|
||||
return props
|
||||
@@ -0,0 +1,24 @@
|
||||
def dms2dd(degrees, minutes, seconds, milliseconds=0):
|
||||
dd = float(degrees) + float(minutes)/60 + float(seconds)/(3600)
|
||||
return dd
|
||||
|
||||
def dd2dms(dd):
|
||||
dd = float(dd)
|
||||
sign = 1 if dd >= 0 else -1
|
||||
dd = abs(dd)
|
||||
minutes, seconds = divmod(dd*3600, 60)
|
||||
degrees, minutes = divmod(minutes, 60)
|
||||
if dd < 0:
|
||||
degrees = -degrees
|
||||
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign)
|
||||
|
||||
def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
|
||||
if scale is None:
|
||||
scale = 1.
|
||||
rotation = atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
a = scale * cos(rotation)
|
||||
b = scale * sin(rotation)
|
||||
eastings = (a * x) - (b * y) + eastings
|
||||
northings = (b * x) + (a * y) + northings
|
||||
height = z + orthogonal_height
|
||||
return (eastings, northings, height)
|
||||
@@ -0,0 +1,156 @@
|
||||
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
|
||||
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(query)
|
||||
return 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])]
|
||||
@@ -0,0 +1,64 @@
|
||||
from math import pi
|
||||
|
||||
prefixes = {'EXA': 1e18, 'PETA': 1e15, 'TERA': 1e12, 'GIGA': 1e9, 'MEGA':
|
||||
1e6, 'KILO': 1e3, 'HECTO': 1e2, 'DECA': 1e1, 'DECI': 1e-1, 'CENTI':
|
||||
1e-2, 'MILLI': 1e-3, 'MICRO': 1e-6, 'NANO': 1e-9, 'PICO': 1e-12,
|
||||
'FEMTO': 1e-15, 'ATTO': 1e-18}
|
||||
|
||||
unit_names = ['AMPERE', 'BECQUEREL', 'CANDELA', 'COULOMB',
|
||||
'CUBIC_METRE', 'DEGREE CELSIUS', 'FARAD', 'GRAM', 'GRAY', 'HENRY',
|
||||
'HERTZ', 'JOULE', 'KELVIN', 'LUMEN', 'LUX', 'MOLE', 'NEWTON', 'OHM',
|
||||
'PASCAL', 'RADIAN', 'SECOND', 'SIEMENS', 'SIEVERT', 'SQUARE METRE',
|
||||
'METRE', 'STERADIAN', 'TESLA', 'VOLT', 'WATT', 'WEBER']
|
||||
|
||||
si_conversions = {
|
||||
'inch': 0.0254,
|
||||
'foot': 0.3048,
|
||||
'yard': 0.914,
|
||||
'mile': 1609,
|
||||
'square inch': 0.0006452,
|
||||
'square foot': 0.09290,
|
||||
'square yard': 0.83612736,
|
||||
'acre': 4046.86,
|
||||
'square mile': 2588881,
|
||||
'cubic inch': 0.00001639,
|
||||
'cubic foot': 0.02832,
|
||||
'cubic yard': 0.7636,
|
||||
'litre': 0.001,
|
||||
'fluid ounce UK': 0.0000284130625,
|
||||
'fluid ounce US': 0.00002957353,
|
||||
'pint UK': 0.000568,
|
||||
'pint US': 0.000473,
|
||||
'gallon UK': 0.004546,
|
||||
'gallon US': 0.003785,
|
||||
'degree': pi/180,
|
||||
'ounce': 0.02835,
|
||||
'pound': 0.454,
|
||||
'ton UK': 1016.0469088,
|
||||
'ton US': 907.18474,
|
||||
'lbf': 4.4482216153,
|
||||
'kip': 4448.2216153,
|
||||
'psi': 6894.7572932,
|
||||
'ksi': 6894757.2932,
|
||||
'minute': 60,
|
||||
'hour': 3600,
|
||||
'day': 86400,
|
||||
'btu': 1055.056}
|
||||
|
||||
def get_prefix(text):
|
||||
for prefix in prefixes.keys():
|
||||
if prefix in text.upper():
|
||||
return prefix
|
||||
|
||||
def get_prefix_multiplier(text):
|
||||
if not text:
|
||||
return 1
|
||||
prefix = get_prefix(text)
|
||||
if prefix:
|
||||
return prefixes[prefix]
|
||||
return 1
|
||||
|
||||
def get_unit_name(text):
|
||||
for name in unit_names:
|
||||
if name in text.upper().replace('METER', 'METRE'):
|
||||
return name
|
||||
Reference in New Issue
Block a user