You can now customise the display of empty strings in IfcCSV.

This commit is contained in:
Dion Moult
2023-09-07 18:15:12 +10:00
parent 6ad51ebd18
commit e2224386bf
5 changed files with 40 additions and 19 deletions
@@ -205,6 +205,7 @@ class ExportIfcCsv(bpy.types.Operator):
delimiter=sep, delimiter=sep,
include_global_id=props.include_global_id, include_global_id=props.include_global_id,
null=props.null_value, null=props.null_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,
sort=sort, sort=sort,
@@ -244,6 +245,7 @@ class ImportIfcCsv(bpy.types.Operator):
attributes=attributes, attributes=attributes,
delimiter=sep, delimiter=sep,
null=props.null_value, null=props.null_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,
) )
@@ -69,6 +69,7 @@ class CsvProperties(PropertyGroup):
should_preserve_existing: BoolProperty(default=False, name="Preserve Existing") should_preserve_existing: BoolProperty(default=False, name="Preserve Existing")
include_global_id: BoolProperty(default=True, name="Include GlobalId") include_global_id: BoolProperty(default=True, name="Include GlobalId")
null_value: StringProperty(default="N/A", name="Null Value") null_value: StringProperty(default="N/A", name="Null Value")
empty_value: StringProperty(default="-", name="Empty String Value")
true_value: StringProperty(default="YES", name="True Value") true_value: StringProperty(default="YES", name="True Value")
false_value: StringProperty(default="NO", name="False Value") false_value: StringProperty(default="NO", name="False Value")
csv_delimiter: EnumProperty( csv_delimiter: EnumProperty(
@@ -74,6 +74,8 @@ class BIM_PT_ifccsv(Panel):
row = layout.row() row = layout.row()
row.prop(props, "null_value") row.prop(props, "null_value")
row = layout.row() row = layout.row()
row.prop(props, "empty_value")
row = layout.row()
row.prop(props, "true_value") row.prop(props, "true_value")
row = layout.row() row = layout.row()
row.prop(props, "false_value") row.prop(props, "false_value")
+19 -14
View File
@@ -70,6 +70,7 @@ class IfcCsv:
include_global_id=True, include_global_id=True,
delimiter=",", delimiter=",",
null="-", null="-",
empty="",
bool_true="YES", bool_true="YES",
bool_false="NO", bool_false="NO",
sort=None, sort=None,
@@ -96,6 +97,8 @@ class IfcCsv:
value = ifcopenshell.util.selector.get_element_value(element, attribute) value = ifcopenshell.util.selector.get_element_value(element, attribute)
if value is None: if value is None:
value = null value = null
elif value == "":
value = empty
elif value is True: elif value is True:
value = bool_true value = bool_true
elif value is False: elif value is False:
@@ -368,17 +371,17 @@ class IfcCsv:
results.update([p.Name for p in element.Quantities]) results.update([p.Name for p in element.Quantities])
return ["{}.{}".format(pset_qto_name, n) for n in results] return ["{}.{}".format(pset_qto_name, n) for n in results]
def Import(self, ifc_file, table, attributes=None, delimiter=",", null="-", bool_true="YES", bool_false="NO"): def Import(self, ifc_file, table, attributes=None, delimiter=",", null="-", empty="", bool_true="YES", bool_false="NO"):
ext = table.split(".")[-1].lower() ext = table.split(".")[-1].lower()
if ext == "csv": if ext == "csv":
self.import_csv(ifc_file, table, attributes, delimiter, null, bool_true, bool_false) self.import_csv(ifc_file, table, attributes, delimiter, null, empty, bool_true, bool_false)
elif ext == "ods": elif ext == "ods":
self.import_ods(ifc_file, table, attributes, null, bool_true, bool_false) self.import_ods(ifc_file, table, attributes, null, empty, bool_true, bool_false)
elif ext == "xlsx": elif ext == "xlsx":
self.import_xlsx(ifc_file, table, attributes, null, bool_true, bool_false) self.import_xlsx(ifc_file, table, attributes, null, empty, bool_true, bool_false)
def import_csv(self, ifc_file, table, attributes=None, delimiter=",", null="-", bool_true="YES", bool_false="NO"): def import_csv(self, ifc_file, table, attributes=None, delimiter=",", null="-", empty="", bool_true="YES", bool_false="NO"):
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)
headers = [] headers = []
@@ -390,17 +393,17 @@ 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, bool_true, bool_false) self.process_row(ifc_file, row, headers, attributes, null, empty, bool_true, bool_false)
def import_xlsx(self, ifc_file, table, attributes, null, bool_true, bool_false): def import_xlsx(self, ifc_file, table, attributes, null, empty, bool_true, bool_false):
df = pd.read_excel(table) df = pd.read_excel(table)
self.import_pd(ifc_file, df, attributes, null, 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, bool_true, bool_false): def import_ods(self, ifc_file, table, attributes, null, empty, bool_true, bool_false):
df = pd.read_excel(table, engine="odf") df = pd.read_excel(table, engine="odf")
self.import_pd(ifc_file, df, attributes, null, 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="-", bool_true="YES", bool_false="NO"): def import_pd(self, ifc_file, df, attributes=None, null="-", empty="", bool_true="YES", bool_false="NO"):
headers = df.columns.tolist() headers = df.columns.tolist()
if not attributes: if not attributes:
@@ -409,9 +412,9 @@ 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, bool_true, bool_false) self.process_row(ifc_file, row.tolist(), headers, attributes, null, empty, bool_true, bool_false)
def process_row(self, ifc_file, row, headers, attributes, null, bool_true, bool_false): def process_row(self, ifc_file, row, headers, attributes, null, empty, bool_true, bool_false):
try: try:
element = ifc_file.by_guid(row[0]) element = ifc_file.by_guid(row[0])
except: except:
@@ -422,6 +425,8 @@ class IfcCsv:
continue # Skip GlobalId continue # Skip GlobalId
if value == null: if value == null:
value = None value = None
elif value == empty:
value = ""
elif value == bool_true: elif value == bool_true:
value = True value = True
elif value == bool_false: elif value == bool_false:
@@ -449,7 +454,7 @@ if __name__ == "__main__":
help="Specify attributes that are part of the extract, using the IfcQuery syntax such as 'class', 'Name' or 'Pset_Foo.Bar'", help="Specify attributes that are part of the extract, using the IfcQuery syntax such as 'class', 'Name' or 'Pset_Foo.Bar'",
) )
parser.add_argument( parser.add_argument(
"-h", "--headers", nargs="+", help="Specify human readable headers that correlate to each attribute." "--headers", nargs="+", help="Specify human readable headers that correlate to each attribute."
) )
parser.add_argument("--sort", nargs="+", help="Specify one or more attributes to sort by.") parser.add_argument("--sort", nargs="+", help="Specify one or more attributes to sort by.")
parser.add_argument("--order", nargs="+", help="Choose the sort order from ASC or DESC for each sorted attribute.") parser.add_argument("--order", nargs="+", help="Choose the sort order from ASC or DESC for each sorted attribute.")
+16 -5
View File
@@ -66,7 +66,8 @@ utility:
:: ::
$ python -m ifccsv -h $ python -m ifccsv -h
usage: ifccsv.py [-h] -i IFC [-s SPREADSHEET] [-f FORMAT] [-d DELIMITER] [-n NULL] [-q QUERY] [-a ARGUMENTS [ARGUMENTS ...]] [--export] [--import] usage: ifccsv.py [-h] -i IFC [-s SPREADSHEET] [-f FORMAT] [-d DELIMITER] [-n NULL] [--bool_true BOOL_TRUE] [--bool_false BOOL_FALSE] [-q QUERY] [-a ATTRIBUTES [ATTRIBUTES ...]] [--headers HEADERS [HEADERS ...]] [--sort SORT [SORT ...]]
[--order ORDER [ORDER ...]] [--export] [--import]
Exports IFC data to and from CSV Exports IFC data to and from CSV
@@ -80,12 +81,22 @@ utility:
-d DELIMITER, --delimiter DELIMITER -d DELIMITER, --delimiter DELIMITER
The delimiter in CSV. Defaults to a comma. The delimiter in CSV. Defaults to a comma.
-n NULL, --null NULL How to represent null values. Defaults to a hyphen. -n NULL, --null NULL How to represent null values. Defaults to a hyphen.
--bool_true BOOL_TRUE
How to represent true values. Defaults to YES.
--bool_false BOOL_FALSE
How to represent false values. Defaults to NO.
-q QUERY, --query QUERY -q QUERY, --query QUERY
Specify a IFC query selector, such as "IfcWall" Specify a IFC query selector, such as "IfcWall"
-a ARGUMENTS [ARGUMENTS ...], --arguments ARGUMENTS [ARGUMENTS ...] -a ATTRIBUTES [ATTRIBUTES ...], --attributes ATTRIBUTES [ATTRIBUTES ...]
Specify attributes that are part of the extract, using the IfcQuery syntax such as 'type', 'Name' or 'Pset_Foo.Bar' Specify attributes that are part of the extract, using the IfcQuery syntax such as 'class', 'Name' or 'Pset_Foo.Bar'
--export Export from IFC to CSV --headers HEADERS [HEADERS ...]
--import Import from CSV to IFC Specify human readable headers that correlate to each attribute.
--sort SORT [SORT ...]
Specify one or more attributes to sort by.
--order ORDER [ORDER ...]
Choose the sort order from ASC or DESC for each sorted attribute.
--export Export from IFC to the desired format.
--import Import from the autodetected format to IFC.
$ python -m ifccsv -i model.ifc -s out.csv -f csv -q .IfcProduct -a "Name" "Description" --export $ python -m ifccsv -i model.ifc -s out.csv -f csv -q .IfcProduct -a "Name" "Description" --export
$ cat out.csv $ cat out.csv