diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index d69f890df8..fb9a7ec7ce 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -48,76 +48,6 @@ except: pass # No Pandas support -class IfcAttributeSetter: - @staticmethod - def set_element_key(ifc_file, element, key, value): - if key == "class" and element.is_a() != value: - return ifcopenshell.util.schema.reassign_class(ifc_file, element, value) - if hasattr(element, key): - setattr(element, key, value) - return element - if "." not in key: - return element - pset_name, prop = key.split(".", 1) - pset = ifcopenshell.util.element.get_pset(element, pset_name, should_inherit=True) - if pset: - pset = ifc_file.by_id(pset["id"]) - if pset.is_a("IfcElementQuantity"): - IfcAttributeSetter.set_qto_property(pset, prop, value) - else: - IfcAttributeSetter.set_pset_property(ifc_file, pset, prop, value) - return element - - @staticmethod - def set_qto_property(qto, name, value): - for prop in qto.Quantities: - if prop.Name != name: - continue - try: - setattr(prop, prop.is_a()[len("IfcQuantity") :] + "Value", float(value)) - except: - pass - - @staticmethod - def set_pset_property(ifc_file, pset, name, value): - for property in pset.HasProperties: - if property.Name != name: - continue - - if value.lower() in ["null", "none"]: - property.NominalValue = None - continue - - # In lieu of loading a map for data casting, we only have four - # options, which this ugly method will determine. - if property.NominalValue is None: - if value.isnumeric(): - property.NominalValue = ifc_file.createIfcInteger(int(value)) - elif value.lower() in ["1", "true", "yes", "uh-huh"]: - property.NominalValue = ifc_file.createIfcBoolean(True) - elif value.lower() in ["0", "false", "no", "nope"]: - property.NominalValue = ifc_file.createIfcBoolean(False) - else: - try: - value = float(value) - property.NominalValue = ifc_file.createIfcReal(value) - except: - property.NominalValue = ifc_file.createIfcLabel(str(value)) - else: - try: - property.NominalValue.wrappedValue = str(value) - except: - try: - property.NominalValue.wrappedValue = float(value) - except: - try: - property.NominalValue.wrappedValue = int(value) - except: - property.NominalValue.wrappedValue = ( - True if value.lower() in ["1", "true", "yes", "uh-huh"] else False - ) - - class IfcCsv: def __init__(self, output="", delimiter=","): self.headers = [] @@ -268,7 +198,7 @@ class IfcCsv: for i, value in enumerate(row): if i == 0: continue # Skip GlobalId - element = IfcAttributeSetter.set_element_key(ifc_file, element, headers[i], value) + ifcopenshell.util.selector.set_element_value(ifc_file, element, headers[i], value) if __name__ == "__main__": diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 02bc5fd525..5cabcd6fe6 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -24,7 +24,8 @@ import ifcopenshell.util.element import ifcopenshell.util.classification -filter_elements_grammar = lark.Lark("""start: filter_group +filter_elements_grammar = lark.Lark( + """start: filter_group filter_group: facet_list ("+" facet_list)* facet_list: facet ("," facet)* @@ -86,9 +87,11 @@ filter_elements_grammar = lark.Lark("""start: filter_group NEWLINE: (CR? LF)+ %ignore WS // Disregard spaces in text -""") +""" +) -get_element_grammar = lark.Lark("""start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple +get_element_grammar = lark.Lark( + """start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)* keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)* keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)* @@ -104,7 +107,8 @@ get_element_grammar = lark.Lark("""start: WORD | ESCAPED_STRING | keys_regex | k WS: /[ \\t\\f\\r\\n]/+ %ignore WS // Disregard spaces in text - """) + """ +) def get_element_value(element, query): @@ -120,6 +124,92 @@ def filter_elements(ifc_file, query, elements=None): return transformer.elements +def set_element_value(ifc_file, element, query, value): + start = get_element_grammar.parse(query) + filter_query = Selector.parse_filter_query(start.children[0]) + keys = filter_query["keys"] + is_regex = filter_query["is_regex"] + + for i, key in enumerate(keys): + key = key.strip() + if element is None: + return + if key == "type": + element = ifcopenshell.util.element.get_type(element) + elif key in ("material", "mat"): + element = ifcopenshell.util.element.get_material(element, should_skip_usage=True) + elif key in ("materials", "mats"): + element = ifcopenshell.util.element.get_materials(element) + elif key == "styles": + element = ifcopenshell.util.element.get_styles(element) + elif key in ("item", "i"): + if element.is_a("IfcMaterialLayerSet"): + element = element.MaterialLayers + elif element.is_a("IfcMaterialProfileSet"): + element = element.MaterialProfiles + elif element.is_a("IfcMaterialConstituentSet"): + element = element.MaterialConstituents + elif key == "container": + element = ifcopenshell.util.element.get_container(element) + elif key == "class": + return ifcopenshell.util.schema.reassign_class(ifc_file, element, value) + elif key == "id": + return + elif isinstance(element, ifcopenshell.entity_instance): + if key == "Name" and element.is_a("IfcMaterialLayerSet"): + key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it. + + if hasattr(element, key): + return setattr(element, key, value) + else: + # Try to extract pset + if is_regex: + psets = ifcopenshell.util.element.get_psets(element) + matching_psets = [] + for pset_name, pset in psets.items(): + if re.match(key, pset_name): + matching_psets.append(pset) + result = matching_psets or None + else: + result = ifcopenshell.util.element.get_pset(element, key) + + if value and not result and len(keys) == i + 2: # The next key is the prop name + if "qto" in key.lower() or "quantity" in key.lower() or "quantities" in key.lower(): + pset = ifcopenshell.api.run("pset.add_qto", ifc_file, product=element, name=key) + else: + pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name=key) + result = {"id": pset.id()} + + element = result + elif isinstance(element, dict): # Such as from the result of a prior get_pset + pset = ifc_file.by_id(element["id"]) + if value in "NULL": + value = None + if is_regex: + for prop in element.keys(): + if re.match(key, prop): + if pset.is_a("IfcPropertySet"): + ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={prop: value}) + elif pset.is_a("IfcElementQuantity"): + ifcopenshell.api.run("pset.edit_qto", ifc_file, qto=pset, properties={prop: float(value)}) + elif pset.is_a("IfcPropertySet"): + ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={key: value}) + elif pset.is_a("IfcElementQuantity"): + ifcopenshell.api.run("pset.edit_qto", ifc_file, qto=pset, properties={key: float(value)}) + return + elif isinstance(element, (list, tuple)): # If we use regex + if key.isnumeric(): + try: + element = element[int(key)] + except IndexError: + return + else: + results = [] + for v in element: + cls.set_element_value(v, keys[i + 1 :], is_regex=is_regex) + return + + class FacetTransformer(lark.Transformer): def __init__(self, ifc_file, elements): self.file = ifc_file