mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-29 11:46:33 +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,
|
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
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user