Fix #3609. IfcCSV can now optionally attempt to "preserve" formatting for XLSX and ODS.

This commit is contained in:
Dion Moult
2023-08-20 16:34:42 +10:00
parent bfbb7bec94
commit de6676a516
4 changed files with 83 additions and 68 deletions
@@ -154,6 +154,7 @@ class ExportIfcCsv(bpy.types.Operator):
headers=headers, headers=headers,
output=self.filepath, output=self.filepath,
format=props.format, format=props.format,
should_preserve_existing=props.should_preserve_existing,
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,
@@ -54,6 +54,7 @@ class CsvProperties(PropertyGroup):
], ],
) )
csv_attributes: CollectionProperty(name="CSV Attributes", type=CsvAttribute) csv_attributes: CollectionProperty(name="CSV Attributes", type=CsvAttribute)
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")
true_value: StringProperty(default="YES", name="True Value") true_value: StringProperty(default="YES", name="True Value")
@@ -56,6 +56,7 @@ class BIM_PT_ifccsv(Panel):
row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="") row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="")
if props.should_show_settings: if props.should_show_settings:
layout.use_property_split = True
row = layout.row(align=True) row = layout.row(align=True)
row.prop(props, "format") row.prop(props, "format")
@@ -67,6 +68,8 @@ class BIM_PT_ifccsv(Panel):
row = layout.row(align=True) row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter") row.prop(props, "csv_custom_delimiter")
row = layout.row()
row.prop(props, "should_preserve_existing")
row = layout.row() row = layout.row()
row.prop(props, "include_global_id") row.prop(props, "include_global_id")
row = layout.row() row = layout.row()
@@ -75,6 +78,7 @@ class BIM_PT_ifccsv(Panel):
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")
layout.use_property_split = False
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv") blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv")
+77 -68
View File
@@ -20,6 +20,7 @@
# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py` # This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py`
import os
import csv import csv
import argparse import argparse
import ifcopenshell import ifcopenshell
@@ -32,12 +33,13 @@ try:
from odf.style import Style, TableCellProperties from odf.style import Style, TableCellProperties
from odf.table import Table, TableRow, TableCell from odf.table import Table, TableRow, TableCell
from odf.text import P from odf.text import P
from odf.opendocument import load
except: except:
pass # No ODF support pass # No ODF support
try: try:
from xlsxwriter import Workbook import openpyxl
except: except:
pass # No XLSX support pass # No XLSX support
@@ -62,6 +64,7 @@ class IfcCsv:
headers=None, headers=None,
output=None, output=None,
format=None, format=None,
should_preserve_existing=False,
include_global_id=True, include_global_id=True,
delimiter=",", delimiter=",",
null="-", null="-",
@@ -106,9 +109,9 @@ class IfcCsv:
if format == "csv": if format == "csv":
self.export_csv(output, delimiter=delimiter) self.export_csv(output, delimiter=delimiter)
elif format == "ods": elif format == "ods":
self.export_ods(output) self.export_ods(output, should_preserve_existing=should_preserve_existing)
elif format == "xlsx": elif format == "xlsx":
self.export_xlsx(output) self.export_xlsx(output, should_preserve_existing=should_preserve_existing)
elif format == "pd": elif format == "pd":
return self.export_pd() return self.export_pd()
@@ -119,78 +122,82 @@ class IfcCsv:
for row in self.results: for row in self.results:
writer.writerow(row) writer.writerow(row)
def export_ods(self, output): def export_ods(self, output, should_preserve_existing=False):
self.doc = OpenDocumentSpreadsheet() if os.path.exists(output) and should_preserve_existing:
ods_document = load(output)
first_table = ods_document.spreadsheet.getElementsByType(Table)[0]
self.colours = { df = self.export_pd()
"h": "dc8774", # Header for col_index, col in enumerate(df.columns):
"g": "eda786", # GlobalId # Assuming the first row of the table contains headers
"a": "96c7d0", # Other Attribute header_cell = first_table.getElementsByType(TableRow)[0].getElementsByType(TableCell)[col_index]
} self.set_cell_value(header_cell, col)
self.cell_formats = {} # Replace existing table data with data from DataFrame
for key, value in self.colours.items(): for row_index, (_, row) in enumerate(df.iterrows()):
style = Style(name=key, family="table-cell") table_row = self.get_row(first_table, row_index + 1) # + 1 for header
style.addElement(TableCellProperties(backgroundcolor="#" + value))
self.doc.automaticstyles.addElement(style)
self.cell_formats[key] = style
table = Table(name="IfcCSV") for col_index, value in enumerate(row):
tr = TableRow() cell = self.get_col(table_row, col_index)
for header in self.headers: self.set_cell_value(cell, str(value))
tc = TableCell(valuetype="string", stylename="h")
tc.addElement(P(text=header))
tr.addElement(tc)
table.addElement(tr)
for row in self.results:
tr = TableRow()
c = 0
for i, col in enumerate(row):
cell_format = "g" if i == 0 else "a"
tc = TableCell(valuetype="string", stylename=cell_format)
if col is None:
col = "NULL"
tc.addElement(P(text=col))
tr.addElement(tc)
c += 1
table.addElement(tr)
self.doc.spreadsheet.addElement(table)
if output[-4:].lower() == ".ods": # If the DataFrame has fewer rows than the table, blank out the extra rows
output = output[0:-4] num_rows_table = len(first_table.getElementsByType(TableRow)) - 1 # Exclude header row
self.doc.save(output, True) if len(df) < num_rows_table:
for i in range(len(df) + 1, num_rows_table + 1): # +1 to account for header
for cell in first_table.getElementsByType(TableRow)[i].getElementsByType(TableCell):
for item in cell.childNodes:
cell.removeChild(item)
def export_xlsx(self, output): ods_document.save(output)
self.workbook = Workbook(output) else:
df = self.export_pd()
df.to_excel(output, index=False, engine="odf")
self.colours = { def set_cell_value(self, cell, value):
"h": "dc8774", # Header for item in cell.childNodes:
"g": "eda786", # GlobalId cell.removeChild(item)
"a": "96c7d0", # Other Attribute
}
self.cell_formats = {} if isinstance(value, (int, float)):
for key, value in self.colours.items(): # Create a new cell with float value
self.cell_formats[key] = self.workbook.add_format() new_cell = TableCell(valuetype="float", value=str(value))
self.cell_formats[key].set_bg_color(value) cell.parentNode.insertBefore(new_cell, cell)
cell.parentNode.removeChild(cell)
else:
# Add string value in a text paragraph
p_element = P(text=str(value))
cell.addElement(p_element)
worksheet = self.workbook.add_worksheet("IfcCSV") def get_row(self, table, row_index):
r = 0 rows = table.getElementsByType(TableRow)
c = 0 if row_index < len(rows):
for header in self.headers: return rows[row_index]
cell = worksheet.write(r, c, header, self.cell_formats["h"]) new_row = TableRow()
c += 1 table.addElement(new_row)
c = 0 return new_row
r += 1
for row in self.results:
c = 0
for i, col in enumerate(row):
cell_format = "g" if i == 0 else "a"
cell = worksheet.write(r, c, col, self.cell_formats[cell_format])
c += 1
r += 1
self.workbook.close() def get_col(self, row, col_index):
cells = row.getElementsByType(TableCell)
if col_index < len(cells):
return cells[col_index]
new_cell = TableCell()
row.addElement(new_cell)
return new_cell
def export_xlsx(self, output, should_preserve_existing=False):
if os.path.exists(output):
book = openpyxl.load_workbook(output)
with pd.ExcelWriter(
output,
engine="openpyxl",
mode="a",
if_sheet_exists="overlay" if should_preserve_existing else "replace",
) as writer:
df = self.export_pd()
df.to_excel(writer, sheet_name=book.sheetnames[0], index=False)
else:
df = self.export_pd()
df.to_excel(output, index=False, engine="openpyxl")
def export_pd(self): def export_pd(self):
self.dataframe = pd.DataFrame(self.results, columns=self.headers) self.dataframe = pd.DataFrame(self.results, columns=self.headers)
@@ -216,12 +223,10 @@ class IfcCsv:
for row in reader: for row in reader:
if not headers: if not headers:
headers = row headers = row
print("csv", headers)
if not attributes: if not attributes:
attributes = [None] * len(headers) attributes = [None] * len(headers)
elif len(attributes) == len(headers) - 1: elif len(attributes) == len(headers) - 1:
attributes.insert(0, "") # The GlobalId column attributes.insert(0, "") # The GlobalId column
print("hea", attributes)
continue continue
try: try:
element = ifc_file.by_guid(row[0]) element = ifc_file.by_guid(row[0])
@@ -250,6 +255,8 @@ if __name__ == "__main__":
parser.add_argument( parser.add_argument(
"-n", "--null", type=str, default="-", help="How to represent null values. Defaults to a hyphen." "-n", "--null", type=str, default="-", help="How to represent null values. Defaults to a hyphen."
) )
parser.add_argument("--bool_true", type=str, default="YES", help="How to represent true values. Defaults to YES.")
parser.add_argument("--bool_false", type=str, default="NO", help="How to represent false values. Defaults to NO.")
parser.add_argument("-q", "--query", type=str, default="", help='Specify a IFC query selector, such as "IfcWall"') parser.add_argument("-q", "--query", type=str, default="", help='Specify a IFC query selector, such as "IfcWall"')
parser.add_argument( parser.add_argument(
"-a", "-a",
@@ -280,6 +287,8 @@ if __name__ == "__main__":
format=args.format, format=args.format,
delimiter=args.delimiter, delimiter=args.delimiter,
null=args.null, null=args.null,
bool_true=args.bool_true,
bool_false=args.bool_false,
) )
elif getattr(args, "import"): elif getattr(args, "import"):
ifc_csv = IfcCsv() ifc_csv = IfcCsv()