mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 10:57:49 +00:00
Fix #3609. IfcCSV can now optionally attempt to "preserve" formatting for XLSX and ODS.
This commit is contained in:
@@ -154,6 +154,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
headers=headers,
|
||||
output=self.filepath,
|
||||
format=props.format,
|
||||
should_preserve_existing=props.should_preserve_existing,
|
||||
delimiter=sep,
|
||||
include_global_id=props.include_global_id,
|
||||
null=props.null_value,
|
||||
|
||||
@@ -54,6 +54,7 @@ class CsvProperties(PropertyGroup):
|
||||
],
|
||||
)
|
||||
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")
|
||||
null_value: StringProperty(default="N/A", name="Null 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="")
|
||||
|
||||
if props.should_show_settings:
|
||||
layout.use_property_split = True
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "format")
|
||||
|
||||
@@ -67,6 +68,8 @@ class BIM_PT_ifccsv(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_preserve_existing")
|
||||
row = layout.row()
|
||||
row.prop(props, "include_global_id")
|
||||
row = layout.row()
|
||||
@@ -75,6 +78,7 @@ class BIM_PT_ifccsv(Panel):
|
||||
row.prop(props, "true_value")
|
||||
row = layout.row()
|
||||
row.prop(props, "false_value")
|
||||
layout.use_property_split = False
|
||||
|
||||
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv")
|
||||
|
||||
|
||||
+77
-68
@@ -20,6 +20,7 @@
|
||||
|
||||
# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py`
|
||||
|
||||
import os
|
||||
import csv
|
||||
import argparse
|
||||
import ifcopenshell
|
||||
@@ -32,12 +33,13 @@ try:
|
||||
from odf.style import Style, TableCellProperties
|
||||
from odf.table import Table, TableRow, TableCell
|
||||
from odf.text import P
|
||||
from odf.opendocument import load
|
||||
except:
|
||||
pass # No ODF support
|
||||
|
||||
|
||||
try:
|
||||
from xlsxwriter import Workbook
|
||||
import openpyxl
|
||||
except:
|
||||
pass # No XLSX support
|
||||
|
||||
@@ -62,6 +64,7 @@ class IfcCsv:
|
||||
headers=None,
|
||||
output=None,
|
||||
format=None,
|
||||
should_preserve_existing=False,
|
||||
include_global_id=True,
|
||||
delimiter=",",
|
||||
null="-",
|
||||
@@ -106,9 +109,9 @@ class IfcCsv:
|
||||
if format == "csv":
|
||||
self.export_csv(output, delimiter=delimiter)
|
||||
elif format == "ods":
|
||||
self.export_ods(output)
|
||||
self.export_ods(output, should_preserve_existing=should_preserve_existing)
|
||||
elif format == "xlsx":
|
||||
self.export_xlsx(output)
|
||||
self.export_xlsx(output, should_preserve_existing=should_preserve_existing)
|
||||
elif format == "pd":
|
||||
return self.export_pd()
|
||||
|
||||
@@ -119,78 +122,82 @@ class IfcCsv:
|
||||
for row in self.results:
|
||||
writer.writerow(row)
|
||||
|
||||
def export_ods(self, output):
|
||||
self.doc = OpenDocumentSpreadsheet()
|
||||
def export_ods(self, output, should_preserve_existing=False):
|
||||
if os.path.exists(output) and should_preserve_existing:
|
||||
ods_document = load(output)
|
||||
first_table = ods_document.spreadsheet.getElementsByType(Table)[0]
|
||||
|
||||
self.colours = {
|
||||
"h": "dc8774", # Header
|
||||
"g": "eda786", # GlobalId
|
||||
"a": "96c7d0", # Other Attribute
|
||||
}
|
||||
df = self.export_pd()
|
||||
for col_index, col in enumerate(df.columns):
|
||||
# Assuming the first row of the table contains headers
|
||||
header_cell = first_table.getElementsByType(TableRow)[0].getElementsByType(TableCell)[col_index]
|
||||
self.set_cell_value(header_cell, col)
|
||||
|
||||
self.cell_formats = {}
|
||||
for key, value in self.colours.items():
|
||||
style = Style(name=key, family="table-cell")
|
||||
style.addElement(TableCellProperties(backgroundcolor="#" + value))
|
||||
self.doc.automaticstyles.addElement(style)
|
||||
self.cell_formats[key] = style
|
||||
# Replace existing table data with data from DataFrame
|
||||
for row_index, (_, row) in enumerate(df.iterrows()):
|
||||
table_row = self.get_row(first_table, row_index + 1) # + 1 for header
|
||||
|
||||
table = Table(name="IfcCSV")
|
||||
tr = TableRow()
|
||||
for header in self.headers:
|
||||
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)
|
||||
for col_index, value in enumerate(row):
|
||||
cell = self.get_col(table_row, col_index)
|
||||
self.set_cell_value(cell, str(value))
|
||||
|
||||
if output[-4:].lower() == ".ods":
|
||||
output = output[0:-4]
|
||||
self.doc.save(output, True)
|
||||
# If the DataFrame has fewer rows than the table, blank out the extra rows
|
||||
num_rows_table = len(first_table.getElementsByType(TableRow)) - 1 # Exclude header row
|
||||
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):
|
||||
self.workbook = Workbook(output)
|
||||
ods_document.save(output)
|
||||
else:
|
||||
df = self.export_pd()
|
||||
df.to_excel(output, index=False, engine="odf")
|
||||
|
||||
self.colours = {
|
||||
"h": "dc8774", # Header
|
||||
"g": "eda786", # GlobalId
|
||||
"a": "96c7d0", # Other Attribute
|
||||
}
|
||||
def set_cell_value(self, cell, value):
|
||||
for item in cell.childNodes:
|
||||
cell.removeChild(item)
|
||||
|
||||
self.cell_formats = {}
|
||||
for key, value in self.colours.items():
|
||||
self.cell_formats[key] = self.workbook.add_format()
|
||||
self.cell_formats[key].set_bg_color(value)
|
||||
if isinstance(value, (int, float)):
|
||||
# Create a new cell with float value
|
||||
new_cell = TableCell(valuetype="float", value=str(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")
|
||||
r = 0
|
||||
c = 0
|
||||
for header in self.headers:
|
||||
cell = worksheet.write(r, c, header, self.cell_formats["h"])
|
||||
c += 1
|
||||
c = 0
|
||||
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
|
||||
def get_row(self, table, row_index):
|
||||
rows = table.getElementsByType(TableRow)
|
||||
if row_index < len(rows):
|
||||
return rows[row_index]
|
||||
new_row = TableRow()
|
||||
table.addElement(new_row)
|
||||
return new_row
|
||||
|
||||
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):
|
||||
self.dataframe = pd.DataFrame(self.results, columns=self.headers)
|
||||
@@ -216,12 +223,10 @@ class IfcCsv:
|
||||
for row in reader:
|
||||
if not headers:
|
||||
headers = row
|
||||
print("csv", headers)
|
||||
if not attributes:
|
||||
attributes = [None] * len(headers)
|
||||
elif len(attributes) == len(headers) - 1:
|
||||
attributes.insert(0, "") # The GlobalId column
|
||||
print("hea", attributes)
|
||||
continue
|
||||
try:
|
||||
element = ifc_file.by_guid(row[0])
|
||||
@@ -250,6 +255,8 @@ if __name__ == "__main__":
|
||||
parser.add_argument(
|
||||
"-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(
|
||||
"-a",
|
||||
@@ -280,6 +287,8 @@ if __name__ == "__main__":
|
||||
format=args.format,
|
||||
delimiter=args.delimiter,
|
||||
null=args.null,
|
||||
bool_true=args.bool_true,
|
||||
bool_false=args.bool_false,
|
||||
)
|
||||
elif getattr(args, "import"):
|
||||
ifc_csv = IfcCsv()
|
||||
|
||||
Reference in New Issue
Block a user