diff --git a/src/ifcopenshell-python/ifcopenshell/util/__init__.py b/src/ifcopenshell-python/ifcopenshell/util/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py new file mode 100644 index 0000000000..e9325feabf --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -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 diff --git a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py new file mode 100644 index 0000000000..53fc034e9c --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py @@ -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) diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py new file mode 100644 index 0000000000..be3441a499 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -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 /(? 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])] diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py new file mode 100644 index 0000000000..42687afdee --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -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