Fix #3591. Fix bug where IfcCSV import was not symmetric capability wise. Now you can set all sorts of complex values with the exact same keys you are using to query.

This commit is contained in:
Dion Moult
2023-08-17 23:33:29 +10:00
parent f4971da42e
commit 2dcfcf9708
2 changed files with 95 additions and 75 deletions
+1 -71
View File
@@ -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__":
@@ -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