mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
ifccsv - support importing enum values #4608
This commit is contained in:
@@ -329,6 +329,7 @@ class ImportIfcCsv(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
empty=props.empty_value,
|
empty=props.empty_value,
|
||||||
bool_true=props.true_value,
|
bool_true=props.true_value,
|
||||||
bool_false=props.false_value,
|
bool_false=props.false_value,
|
||||||
|
concat=props.concat_value,
|
||||||
)
|
)
|
||||||
if not props.should_load_from_memory:
|
if not props.should_load_from_memory:
|
||||||
ifc_file.write(props.csv_ifc_file)
|
ifc_file.write(props.csv_ifc_file)
|
||||||
|
|||||||
+15
-9
@@ -405,6 +405,7 @@ class IfcCsv:
|
|||||||
empty: str = "",
|
empty: str = "",
|
||||||
bool_true: str = "YES",
|
bool_true: str = "YES",
|
||||||
bool_false: str = "NO",
|
bool_false: str = "NO",
|
||||||
|
concat: str = ", ",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
@@ -413,11 +414,11 @@ class IfcCsv:
|
|||||||
ext: FILE_FORMAT = table.split(".")[-1].lower()
|
ext: FILE_FORMAT = table.split(".")[-1].lower()
|
||||||
|
|
||||||
if ext == "csv":
|
if ext == "csv":
|
||||||
self.import_csv(ifc_file, table, attributes, delimiter, null, empty, bool_true, bool_false)
|
self.import_csv(ifc_file, table, attributes, delimiter, null, empty, bool_true, bool_false, concat)
|
||||||
elif ext == "ods":
|
elif ext == "ods":
|
||||||
self.import_ods(ifc_file, table, attributes, null, empty, bool_true, bool_false)
|
self.import_ods(ifc_file, table, attributes, null, empty, bool_true, bool_false, concat)
|
||||||
elif ext == "xlsx":
|
elif ext == "xlsx":
|
||||||
self.import_xlsx(ifc_file, table, attributes, null, empty, bool_true, bool_false)
|
self.import_xlsx(ifc_file, table, attributes, null, empty, bool_true, bool_false, concat)
|
||||||
|
|
||||||
def import_csv(
|
def import_csv(
|
||||||
self,
|
self,
|
||||||
@@ -429,6 +430,7 @@ class IfcCsv:
|
|||||||
empty: str = "",
|
empty: str = "",
|
||||||
bool_true: str = "YES",
|
bool_true: str = "YES",
|
||||||
bool_false: str = "NO",
|
bool_false: str = "NO",
|
||||||
|
concat: str = ", ",
|
||||||
) -> None:
|
) -> None:
|
||||||
with open(table, newline="", encoding="utf-8") as f:
|
with open(table, newline="", encoding="utf-8") as f:
|
||||||
reader = csv.reader(f, delimiter=delimiter)
|
reader = csv.reader(f, delimiter=delimiter)
|
||||||
@@ -441,17 +443,19 @@ class IfcCsv:
|
|||||||
elif len(attributes) == len(headers) - 1:
|
elif len(attributes) == len(headers) - 1:
|
||||||
attributes.insert(0, "") # The GlobalId column
|
attributes.insert(0, "") # The GlobalId column
|
||||||
continue
|
continue
|
||||||
self.process_row(ifc_file, row, headers, attributes, null, empty, bool_true, bool_false)
|
self.process_row(ifc_file, row, headers, attributes, null, empty, bool_true, bool_false, concat)
|
||||||
|
|
||||||
def import_xlsx(self, ifc_file, table, attributes, null, empty, bool_true, bool_false):
|
def import_xlsx(self, ifc_file, table, attributes, null, empty, bool_true, bool_false, concat) -> None:
|
||||||
df = pd.read_excel(table)
|
df = pd.read_excel(table)
|
||||||
self.import_pd(ifc_file, df, attributes, null, empty, bool_true, bool_false)
|
self.import_pd(ifc_file, df, attributes, null, empty, bool_true, bool_false)
|
||||||
|
|
||||||
def import_ods(self, ifc_file, table, attributes, null, empty, bool_true, bool_false):
|
def import_ods(self, ifc_file, table, attributes, null, empty, bool_true, bool_false, concat) -> None:
|
||||||
df = pd.read_excel(table, engine="odf")
|
df = pd.read_excel(table, engine="odf")
|
||||||
self.import_pd(ifc_file, df, attributes, null, empty, bool_true, bool_false)
|
self.import_pd(ifc_file, df, attributes, null, empty, bool_true, bool_false)
|
||||||
|
|
||||||
def import_pd(self, ifc_file, df, attributes=None, null="-", empty="", bool_true="YES", bool_false="NO"):
|
def import_pd(
|
||||||
|
self, ifc_file, df, attributes=None, null="-", empty="", bool_true="YES", bool_false="NO", concat=", "
|
||||||
|
) -> None:
|
||||||
headers = df.columns.tolist()
|
headers = df.columns.tolist()
|
||||||
|
|
||||||
if not attributes:
|
if not attributes:
|
||||||
@@ -460,7 +464,7 @@ class IfcCsv:
|
|||||||
attributes.insert(0, "") # The GlobalId column
|
attributes.insert(0, "") # The GlobalId column
|
||||||
|
|
||||||
for _, row in df.iterrows():
|
for _, row in df.iterrows():
|
||||||
self.process_row(ifc_file, row.tolist(), headers, attributes, null, empty, bool_true, bool_false)
|
self.process_row(ifc_file, row.tolist(), headers, attributes, null, empty, bool_true, bool_false, concat)
|
||||||
|
|
||||||
def process_row(
|
def process_row(
|
||||||
self,
|
self,
|
||||||
@@ -472,6 +476,7 @@ class IfcCsv:
|
|||||||
empty: str,
|
empty: str,
|
||||||
bool_true: str,
|
bool_true: str,
|
||||||
bool_false: str,
|
bool_false: str,
|
||||||
|
concat: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
element = ifc_file.by_guid(row[0])
|
element = ifc_file.by_guid(row[0])
|
||||||
@@ -490,7 +495,7 @@ class IfcCsv:
|
|||||||
elif value == bool_false:
|
elif value == bool_false:
|
||||||
value = False
|
value = False
|
||||||
key = attributes[i] or headers[i]
|
key = attributes[i] or headers[i]
|
||||||
ifcopenshell.util.selector.set_element_value(ifc_file, element, key, value)
|
ifcopenshell.util.selector.set_element_value(ifc_file, element, key, value, concat=concat)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -552,5 +557,6 @@ if __name__ == "__main__":
|
|||||||
delimiter=args.delimiter,
|
delimiter=args.delimiter,
|
||||||
null=args.null,
|
null=args.null,
|
||||||
empty=args.empty,
|
empty=args.empty,
|
||||||
|
concat=args.concat,
|
||||||
)
|
)
|
||||||
ifc_file.write(args.ifc)
|
ifc_file.write(args.ifc)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import ifcopenshell.util.element
|
|||||||
import ifcopenshell.util.fm
|
import ifcopenshell.util.fm
|
||||||
import ifcopenshell.util.geolocation
|
import ifcopenshell.util.geolocation
|
||||||
import ifcopenshell.util.placement
|
import ifcopenshell.util.placement
|
||||||
|
import ifcopenshell.util.pset
|
||||||
import ifcopenshell.util.schema
|
import ifcopenshell.util.schema
|
||||||
import ifcopenshell.util.shape
|
import ifcopenshell.util.shape
|
||||||
import ifcopenshell.util.system
|
import ifcopenshell.util.system
|
||||||
@@ -464,7 +465,17 @@ def set_element_value(
|
|||||||
element: Union[ifcopenshell.entity_instance, Iterable[ifcopenshell.entity_instance], None],
|
element: Union[ifcopenshell.entity_instance, Iterable[ifcopenshell.entity_instance], None],
|
||||||
query: Union[str, list[str]],
|
query: Union[str, list[str]],
|
||||||
value: Any,
|
value: Any,
|
||||||
|
*,
|
||||||
|
concat: str = ", ",
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Set element value based on the provided query.
|
||||||
|
|
||||||
|
:param element: IFC element to change.
|
||||||
|
:param query: String query to identify the attribute to change.
|
||||||
|
:param value: Value to set.
|
||||||
|
:param concat: Concatenation symbol, used only to deserialize property
|
||||||
|
set enum values from string values.
|
||||||
|
"""
|
||||||
original_element = element
|
original_element = element
|
||||||
if isinstance(query, (list, tuple)):
|
if isinstance(query, (list, tuple)):
|
||||||
keys = query
|
keys = query
|
||||||
@@ -640,6 +651,55 @@ def set_element_value(
|
|||||||
elif pset.is_a("IfcElementQuantity") and prop_value != float(value):
|
elif pset.is_a("IfcElementQuantity") and prop_value != float(value):
|
||||||
ifcopenshell.api.pset.edit_qto(ifc_file, qto=pset, properties={prop: float(value)})
|
ifcopenshell.api.pset.edit_qto(ifc_file, qto=pset, properties={prop: float(value)})
|
||||||
elif pset.is_a("IfcPropertySet") and element.get(key, None) != value:
|
elif pset.is_a("IfcPropertySet") and element.get(key, None) != value:
|
||||||
|
|
||||||
|
def process_pset_prop_value(pset: ifcopenshell.entity_instance, prop: str, value: Any) -> Any:
|
||||||
|
"""Try to process value for edit_pset.
|
||||||
|
|
||||||
|
`edit_pset` is expecting a sequence of values
|
||||||
|
for enum properties, not just a string of some-symbol-separated values.
|
||||||
|
"""
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return value
|
||||||
|
template = ifcopenshell.util.pset.get_template(ifc_file.schema)
|
||||||
|
pset_template = template.get_by_name(pset.Name)
|
||||||
|
if pset_template is None:
|
||||||
|
return value
|
||||||
|
for prop_template in pset_template.HasPropertyTemplates:
|
||||||
|
# 2 IfcSimplePropertyTemplate.Name
|
||||||
|
if prop_template[2] != prop:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 4 IfcSimplePropertyTemplate.TemplateType
|
||||||
|
if prop_template[4] != "P_ENUMERATEDVALUE":
|
||||||
|
# Not a enum property.
|
||||||
|
return value
|
||||||
|
|
||||||
|
# 7 IfcSimplePropertyTemplate.Enumerators
|
||||||
|
if (enumeration := prop_template[7]) is None:
|
||||||
|
# Enum property but without enumerators,
|
||||||
|
# make it a sequence to keep it assignable as a enum.
|
||||||
|
return (value,)
|
||||||
|
|
||||||
|
# 1 IfcPropertyEnumeration.EnumerationValues
|
||||||
|
available_enum_values = {v.wrappedValue for v in enumeration[1]}
|
||||||
|
if value in available_enum_values:
|
||||||
|
# Valid enum item, just keep it a sequence.
|
||||||
|
return (value,)
|
||||||
|
|
||||||
|
# Taking a wild guess that it's `concat` separated list.
|
||||||
|
enum_values = value.split(concat)
|
||||||
|
if not all(v in available_enum_values for v in enum_values):
|
||||||
|
raise Exception(
|
||||||
|
"Error setting pset enum property.\n"
|
||||||
|
f"Invalid enum values for property '{prop} in pset '{pset}': '{', '.join(enum_values)}'.\n"
|
||||||
|
f"Possible enum values for this property: {', '.join(available_enum_values)}."
|
||||||
|
)
|
||||||
|
return enum_values
|
||||||
|
|
||||||
|
# Couldn't find property template for this prop - delegate decision to edit_pset.
|
||||||
|
return value
|
||||||
|
|
||||||
|
value = process_pset_prop_value(pset, key, value)
|
||||||
ifcopenshell.api.pset.edit_pset(ifc_file, pset=pset, properties={key: value})
|
ifcopenshell.api.pset.edit_pset(ifc_file, pset=pset, properties={key: value})
|
||||||
elif pset.is_a("IfcElementQuantity"):
|
elif pset.is_a("IfcElementQuantity"):
|
||||||
try:
|
try:
|
||||||
@@ -661,7 +721,7 @@ def set_element_value(
|
|||||||
return
|
return
|
||||||
|
|
||||||
raise SetElementValueException(
|
raise SetElementValueException(
|
||||||
f"Failed to set value for element '{original_element}' with query '{query}' (invalid or unsupported query)."
|
f"Failed to set value '{value}' for element '{original_element}' with query '{query}' (invalid or unsupported query)."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user