diff --git a/src/blenderbim/blenderbim/bim/module/csv/operator.py b/src/blenderbim/blenderbim/bim/module/csv/operator.py index daa85fbab9..dceeb0a568 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/operator.py +++ b/src/blenderbim/blenderbim/bim/module/csv/operator.py @@ -205,6 +205,7 @@ class ExportIfcCsv(bpy.types.Operator): delimiter=sep, include_global_id=props.include_global_id, null=props.null_value, + empty=props.empty_value, bool_true=props.true_value, bool_false=props.false_value, sort=sort, @@ -244,6 +245,7 @@ class ImportIfcCsv(bpy.types.Operator): attributes=attributes, delimiter=sep, null=props.null_value, + empty=props.empty_value, bool_true=props.true_value, bool_false=props.false_value, ) diff --git a/src/blenderbim/blenderbim/bim/module/csv/prop.py b/src/blenderbim/blenderbim/bim/module/csv/prop.py index db1ae07071..c04fc18ee6 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/prop.py +++ b/src/blenderbim/blenderbim/bim/module/csv/prop.py @@ -69,6 +69,7 @@ class CsvProperties(PropertyGroup): should_preserve_existing: BoolProperty(default=False, name="Preserve Existing") include_global_id: BoolProperty(default=True, name="Include GlobalId") 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") false_value: StringProperty(default="NO", name="False Value") csv_delimiter: EnumProperty( diff --git a/src/blenderbim/blenderbim/bim/module/csv/ui.py b/src/blenderbim/blenderbim/bim/module/csv/ui.py index 425bb2cb8d..49e096c2bc 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/ui.py +++ b/src/blenderbim/blenderbim/bim/module/csv/ui.py @@ -74,6 +74,8 @@ class BIM_PT_ifccsv(Panel): row = layout.row() row.prop(props, "null_value") row = layout.row() + row.prop(props, "empty_value") + row = layout.row() row.prop(props, "true_value") row = layout.row() row.prop(props, "false_value") diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 08ef8c6e5e..e1bbcfbd5d 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -70,6 +70,7 @@ class IfcCsv: include_global_id=True, delimiter=",", null="-", + empty="", bool_true="YES", bool_false="NO", sort=None, @@ -96,6 +97,8 @@ class IfcCsv: value = ifcopenshell.util.selector.get_element_value(element, attribute) if value is None: value = null + elif value == "": + value = empty elif value is True: value = bool_true elif value is False: @@ -368,17 +371,17 @@ class IfcCsv: results.update([p.Name for p in element.Quantities]) 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() 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": - 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": - 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: reader = csv.reader(f, delimiter=delimiter) headers = [] @@ -390,17 +393,17 @@ class IfcCsv: elif len(attributes) == len(headers) - 1: attributes.insert(0, "") # The GlobalId column 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) - 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") - 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() if not attributes: @@ -409,9 +412,9 @@ class IfcCsv: attributes.insert(0, "") # The GlobalId column 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: element = ifc_file.by_guid(row[0]) except: @@ -422,6 +425,8 @@ class IfcCsv: continue # Skip GlobalId if value == null: value = None + elif value == empty: + value = "" elif value == bool_true: value = True 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'", ) 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("--order", nargs="+", help="Choose the sort order from ASC or DESC for each sorted attribute.") diff --git a/src/ifcopenshell-python/docs/ifccsv.rst b/src/ifcopenshell-python/docs/ifccsv.rst index 0e89bf9622..54ef0a4b6a 100644 --- a/src/ifcopenshell-python/docs/ifccsv.rst +++ b/src/ifcopenshell-python/docs/ifccsv.rst @@ -66,7 +66,8 @@ utility: :: $ 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 @@ -80,12 +81,22 @@ utility: -d DELIMITER, --delimiter DELIMITER The delimiter in CSV. Defaults to a comma. -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 Specify a IFC query selector, such as "IfcWall" - -a ARGUMENTS [ARGUMENTS ...], --arguments ARGUMENTS [ARGUMENTS ...] - Specify attributes that are part of the extract, using the IfcQuery syntax such as 'type', 'Name' or 'Pset_Foo.Bar' - --export Export from IFC to CSV - --import Import from CSV to IFC + -a ATTRIBUTES [ATTRIBUTES ...], --attributes ATTRIBUTES [ATTRIBUTES ...] + Specify attributes that are part of the extract, using the IfcQuery syntax such as 'class', 'Name' or 'Pset_Foo.Bar' + --headers HEADERS [HEADERS ...] + 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 $ cat out.csv