mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 05:52:33 +00:00
IfcCSV can now create summary rows (e.g. total, average) at the end of the table (may or may not be in addition to group totals).
This commit is contained in:
@@ -82,6 +82,7 @@ class ImportCsvAttributes(bpy.types.Operator):
|
|||||||
new.header = attribute["header"]
|
new.header = attribute["header"]
|
||||||
new.sort = attribute["sort"]
|
new.sort = attribute["sort"]
|
||||||
new.group = attribute["group"]
|
new.group = attribute["group"]
|
||||||
|
new.summary = attribute["summary"]
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@@ -104,7 +105,8 @@ class ExportCsvAttributes(bpy.types.Operator):
|
|||||||
data = {
|
data = {
|
||||||
"query": tool.Search.export_filter_query(props.filter_groups),
|
"query": tool.Search.export_filter_query(props.filter_groups),
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{"name": a.name, "header": a.header, "sort": a.sort, "group": a.group} for a in props.csv_attributes
|
{"name": a.name, "header": a.header, "sort": a.sort, "group": a.group, "summary": a.summary}
|
||||||
|
for a in props.csv_attributes
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,11 +154,14 @@ class ExportIfcCsv(bpy.types.Operator):
|
|||||||
|
|
||||||
sort = []
|
sort = []
|
||||||
groups = []
|
groups = []
|
||||||
|
summaries = []
|
||||||
for attribute in props.csv_attributes:
|
for attribute in props.csv_attributes:
|
||||||
if attribute.sort != "NONE":
|
if attribute.sort != "NONE":
|
||||||
sort.append({"name": attribute.name, "order": attribute.sort})
|
sort.append({"name": attribute.name, "order": attribute.sort})
|
||||||
if attribute.group != "NONE":
|
if attribute.group != "NONE":
|
||||||
groups.append({"name": attribute.name, "type": attribute.group, "varies_value": attribute.varies_value})
|
groups.append({"name": attribute.name, "type": attribute.group, "varies_value": attribute.varies_value})
|
||||||
|
if attribute.summary != "NONE":
|
||||||
|
summaries.append({"name": attribute.name, "type": attribute.summary})
|
||||||
|
|
||||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||||
ifc_csv.export(
|
ifc_csv.export(
|
||||||
@@ -174,6 +179,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
|||||||
bool_false=props.false_value,
|
bool_false=props.false_value,
|
||||||
sort=sort,
|
sort=sort,
|
||||||
groups=groups,
|
groups=groups,
|
||||||
|
summaries=summaries,
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,15 @@ class CsvAttribute(PropertyGroup):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
varies_value: StringProperty(default="Varies", name="Varies Value")
|
varies_value: StringProperty(default="Varies", name="Varies Value")
|
||||||
|
summary: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("NONE", "None", ""),
|
||||||
|
("SUM", "Sum", "Sums the total value of all rows."),
|
||||||
|
("AVERAGE", "Average", "Averages the total value of all rows."),
|
||||||
|
("MIN", "Min", "Gets the minimum value of all rows."),
|
||||||
|
("MAX", "Max", "Gets the maximum value of all rows."),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CsvProperties(PropertyGroup):
|
class CsvProperties(PropertyGroup):
|
||||||
@@ -96,4 +105,5 @@ class CsvProperties(PropertyGroup):
|
|||||||
should_show_settings: BoolProperty(default=False, name="Show Settings")
|
should_show_settings: BoolProperty(default=False, name="Show Settings")
|
||||||
should_show_sort: BoolProperty(default=False, name="Show Sorting")
|
should_show_sort: BoolProperty(default=False, name="Show Sorting")
|
||||||
should_show_group: BoolProperty(default=False, name="Show Grouping")
|
should_show_group: BoolProperty(default=False, name="Show Grouping")
|
||||||
|
should_show_summary: BoolProperty(default=False, name="Show Summary")
|
||||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ class BIM_PT_ifccsv(Panel):
|
|||||||
row.alignment = "RIGHT"
|
row.alignment = "RIGHT"
|
||||||
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
||||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
||||||
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
|
|
||||||
row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="")
|
|
||||||
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
|
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
|
||||||
|
|
||||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||||
@@ -85,17 +83,22 @@ class BIM_PT_ifccsv(Panel):
|
|||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator("bim.add_csv_attribute", icon="ADD")
|
row.operator("bim.add_csv_attribute", icon="ADD")
|
||||||
|
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
|
||||||
|
row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="")
|
||||||
|
row.prop(props, "should_show_summary", icon="SYNTAX_ON", text="")
|
||||||
|
|
||||||
for index, attribute in enumerate(props.csv_attributes):
|
for index, attribute in enumerate(props.csv_attributes):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(attribute, "name", text="")
|
row.prop(attribute, "name", text="")
|
||||||
|
row.prop(attribute, "header", text="")
|
||||||
if props.should_show_sort:
|
if props.should_show_sort:
|
||||||
row.prop(attribute, "sort", text="")
|
row.prop(attribute, "sort", text="")
|
||||||
if props.should_show_group:
|
if props.should_show_group:
|
||||||
row.prop(attribute, "group", text="")
|
row.prop(attribute, "group", text="")
|
||||||
if attribute.group == "VARIES":
|
if attribute.group == "VARIES":
|
||||||
row.prop(attribute, "varies_value", text="")
|
row.prop(attribute, "varies_value", text="")
|
||||||
row.prop(attribute, "header", text="")
|
if props.should_show_summary:
|
||||||
|
row.prop(attribute, "summary", text="")
|
||||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
|
|||||||
+118
-66
@@ -74,6 +74,7 @@ class IfcCsv:
|
|||||||
bool_false="NO",
|
bool_false="NO",
|
||||||
sort=None,
|
sort=None,
|
||||||
groups=None,
|
groups=None,
|
||||||
|
summaries=None,
|
||||||
):
|
):
|
||||||
self.ifc_file = ifc_file
|
self.ifc_file = ifc_file
|
||||||
self.results = []
|
self.results = []
|
||||||
@@ -112,65 +113,119 @@ class IfcCsv:
|
|||||||
else:
|
else:
|
||||||
self.headers.append(attribute)
|
self.headers.append(attribute)
|
||||||
|
|
||||||
if groups:
|
self.group_results(groups, attributes)
|
||||||
group_results = {}
|
self.summarise_results(summaries, attributes)
|
||||||
group_indices = {}
|
self.sort_results(sort, attributes)
|
||||||
group_values = {}
|
|
||||||
group_varies_values = {}
|
|
||||||
|
|
||||||
for group in groups:
|
if format == "csv":
|
||||||
index = attributes.index(group["name"])
|
self.export_csv(output, delimiter=delimiter)
|
||||||
group_indices.setdefault(group["type"], [])
|
elif format == "ods":
|
||||||
group_indices[group["type"]].append(index)
|
self.export_ods(output, should_preserve_existing=should_preserve_existing)
|
||||||
if group["type"] == "VARIES":
|
elif format == "xlsx":
|
||||||
group_varies_values[index] = group["varies_value"]
|
self.export_xlsx(output, should_preserve_existing=should_preserve_existing)
|
||||||
|
elif format == "pd":
|
||||||
|
return self.export_pd()
|
||||||
|
|
||||||
for row in self.results:
|
def group_results(self, groups, attributes):
|
||||||
key = "-".join([str(row[gi]) for gi in group_indices.get("GROUP", [])])
|
if not groups:
|
||||||
for group_type, gis in group_indices.items():
|
return
|
||||||
if group_type in ("CONCAT", "VARIES"):
|
|
||||||
for gi in gis:
|
|
||||||
group_values.setdefault(key, {}).setdefault(gi, set())
|
|
||||||
group_values[key][gi].add(str(row[gi]))
|
|
||||||
elif group_type in ("SUM", "AVERAGE", "MIN", "MAX"):
|
|
||||||
for gi in gis:
|
|
||||||
group_values.setdefault(key, {}).setdefault(gi, [])
|
|
||||||
try:
|
|
||||||
value = float(row[gi])
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
group_values[key][gi].append(value)
|
|
||||||
group_results[key] = row
|
|
||||||
|
|
||||||
|
group_results = {}
|
||||||
|
group_indices = {}
|
||||||
|
group_values = {}
|
||||||
|
group_varies_values = {}
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
index = attributes.index(group["name"])
|
||||||
|
group_indices.setdefault(group["type"], [])
|
||||||
|
group_indices[group["type"]].append(index)
|
||||||
|
if group["type"] == "VARIES":
|
||||||
|
group_varies_values[index] = group["varies_value"]
|
||||||
|
|
||||||
|
for row in self.results:
|
||||||
|
key = "-".join([str(row[gi]) for gi in group_indices.get("GROUP", [])])
|
||||||
for group_type, gis in group_indices.items():
|
for group_type, gis in group_indices.items():
|
||||||
if group_type == "CONCAT":
|
if group_type in ("CONCAT", "VARIES"):
|
||||||
for key, result in group_results.items():
|
for gi in gis:
|
||||||
for gi in gis:
|
group_values.setdefault(key, {}).setdefault(gi, set())
|
||||||
result[gi] = ", ".join(group_values[key][gi])
|
group_values[key][gi].add(str(row[gi]))
|
||||||
elif group_type == "VARIES":
|
elif group_type in ("SUM", "AVERAGE", "MIN", "MAX"):
|
||||||
for key, result in group_results.items():
|
for gi in gis:
|
||||||
for gi in gis:
|
group_values.setdefault(key, {}).setdefault(gi, [])
|
||||||
if len(group_values[key][gi]) > 1:
|
try:
|
||||||
result[gi] = group_varies_values[gi]
|
value = float(row[gi])
|
||||||
elif group_type == "SUM":
|
except:
|
||||||
for key, result in group_results.items():
|
continue
|
||||||
for gi in gis:
|
group_values[key][gi].append(value)
|
||||||
result[gi] = sum(group_values[key][gi])
|
group_results[key] = row
|
||||||
elif group_type == "AVERAGE":
|
|
||||||
for key, result in group_results.items():
|
|
||||||
for gi in gis:
|
|
||||||
result[gi] = mean(group_values[key][gi])
|
|
||||||
elif group_type == "MIN":
|
|
||||||
for key, result in group_results.items():
|
|
||||||
for gi in gis:
|
|
||||||
result[gi] = min(group_values[key][gi])
|
|
||||||
elif group_type == "MAX":
|
|
||||||
for key, result in group_results.items():
|
|
||||||
for gi in gis:
|
|
||||||
result[gi] = max(group_values[key][gi])
|
|
||||||
|
|
||||||
self.results = group_results.values()
|
for group_type, gis in group_indices.items():
|
||||||
|
if group_type == "CONCAT":
|
||||||
|
for key, result in group_results.items():
|
||||||
|
for gi in gis:
|
||||||
|
result[gi] = ", ".join(group_values[key][gi])
|
||||||
|
elif group_type == "VARIES":
|
||||||
|
for key, result in group_results.items():
|
||||||
|
for gi in gis:
|
||||||
|
if len(group_values[key][gi]) > 1:
|
||||||
|
result[gi] = group_varies_values[gi]
|
||||||
|
elif group_type == "SUM":
|
||||||
|
for key, result in group_results.items():
|
||||||
|
for gi in gis:
|
||||||
|
result[gi] = sum(group_values[key][gi])
|
||||||
|
elif group_type == "AVERAGE":
|
||||||
|
for key, result in group_results.items():
|
||||||
|
for gi in gis:
|
||||||
|
result[gi] = mean(group_values[key][gi])
|
||||||
|
elif group_type == "MIN":
|
||||||
|
for key, result in group_results.items():
|
||||||
|
for gi in gis:
|
||||||
|
result[gi] = min(group_values[key][gi])
|
||||||
|
elif group_type == "MAX":
|
||||||
|
for key, result in group_results.items():
|
||||||
|
for gi in gis:
|
||||||
|
result[gi] = max(group_values[key][gi])
|
||||||
|
|
||||||
|
self.results = group_results.values()
|
||||||
|
|
||||||
|
def summarise_results(self, summaries, attributes):
|
||||||
|
self.summaries = [None] * len(attributes)
|
||||||
|
|
||||||
|
if not summaries:
|
||||||
|
return
|
||||||
|
|
||||||
|
summary_indices = {}
|
||||||
|
summary_values = {}
|
||||||
|
|
||||||
|
for summary in summaries:
|
||||||
|
index = attributes.index(summary["name"])
|
||||||
|
summary_indices.setdefault(summary["type"], [])
|
||||||
|
summary_indices[summary["type"]].append(index)
|
||||||
|
|
||||||
|
for row in self.results:
|
||||||
|
for summary_type, sis in summary_indices.items():
|
||||||
|
if summary_type in ("SUM", "AVERAGE", "MIN", "MAX"):
|
||||||
|
for si in sis:
|
||||||
|
summary_values.setdefault(si, [])
|
||||||
|
try:
|
||||||
|
value = float(row[si])
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
summary_values[si].append(value)
|
||||||
|
|
||||||
|
for summary_type, sis in summary_indices.items():
|
||||||
|
for si in sis:
|
||||||
|
if summary_type == "SUM":
|
||||||
|
self.summaries[si] = sum(summary_values[si])
|
||||||
|
elif summary_type == "AVERAGE":
|
||||||
|
self.summaries[si] = mean(summary_values[si])
|
||||||
|
elif summary_type == "MIN":
|
||||||
|
self.summaries[si] = min(summary_values[si])
|
||||||
|
elif summary_type == "MAX":
|
||||||
|
self.summaries[si] = max(summary_values[si])
|
||||||
|
self.summaries[si] = summary_type.title() + ": " + str(self.summaries[si])
|
||||||
|
|
||||||
|
def sort_results(self, sort, attributes):
|
||||||
if sort:
|
if sort:
|
||||||
def natural_sort(value):
|
def natural_sort(value):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
@@ -187,28 +242,24 @@ class IfcCsv:
|
|||||||
else:
|
else:
|
||||||
self.results = sorted(self.results, key=lambda x: x[1 if include_global_id else 0])
|
self.results = sorted(self.results, key=lambda x: x[1 if include_global_id else 0])
|
||||||
|
|
||||||
if format == "csv":
|
|
||||||
self.export_csv(output, delimiter=delimiter)
|
|
||||||
elif format == "ods":
|
|
||||||
self.export_ods(output, should_preserve_existing=should_preserve_existing)
|
|
||||||
elif format == "xlsx":
|
|
||||||
self.export_xlsx(output, should_preserve_existing=should_preserve_existing)
|
|
||||||
elif format == "pd":
|
|
||||||
return self.export_pd()
|
|
||||||
|
|
||||||
def export_csv(self, output, delimiter=None):
|
def export_csv(self, output, delimiter=None):
|
||||||
with open(output, "w", newline="", encoding="utf-8") as f:
|
with open(output, "w", newline="", encoding="utf-8") as f:
|
||||||
writer = csv.writer(f, delimiter=delimiter)
|
writer = csv.writer(f, delimiter=delimiter)
|
||||||
writer.writerow(self.headers)
|
writer.writerow(self.headers)
|
||||||
for row in self.results:
|
for row in self.results:
|
||||||
writer.writerow(row)
|
writer.writerow(row)
|
||||||
|
if any([s for s in self.summaries if s is not None]):
|
||||||
|
writer.writerow(self.summaries)
|
||||||
|
|
||||||
def export_ods(self, output, should_preserve_existing=False):
|
def export_ods(self, output, should_preserve_existing=False):
|
||||||
|
df = self.export_pd()
|
||||||
|
if self.summaries:
|
||||||
|
df.loc[df.shape[0]] = self.summaries
|
||||||
|
|
||||||
if os.path.exists(output) and should_preserve_existing:
|
if os.path.exists(output) and should_preserve_existing:
|
||||||
ods_document = load(output)
|
ods_document = load(output)
|
||||||
first_table = ods_document.spreadsheet.getElementsByType(Table)[0]
|
first_table = ods_document.spreadsheet.getElementsByType(Table)[0]
|
||||||
|
|
||||||
df = self.export_pd()
|
|
||||||
for col_index, col in enumerate(df.columns):
|
for col_index, col in enumerate(df.columns):
|
||||||
# Assuming the first row of the table contains headers
|
# Assuming the first row of the table contains headers
|
||||||
header_cell = self.get_col(first_table.getElementsByType(TableRow)[0], col_index)
|
header_cell = self.get_col(first_table.getElementsByType(TableRow)[0], col_index)
|
||||||
@@ -232,7 +283,6 @@ class IfcCsv:
|
|||||||
|
|
||||||
ods_document.save(output)
|
ods_document.save(output)
|
||||||
else:
|
else:
|
||||||
df = self.export_pd()
|
|
||||||
df.to_excel(output, index=False, engine="odf")
|
df.to_excel(output, index=False, engine="odf")
|
||||||
|
|
||||||
def set_cell_value(self, cell, value):
|
def set_cell_value(self, cell, value):
|
||||||
@@ -265,6 +315,10 @@ class IfcCsv:
|
|||||||
return new_cell
|
return new_cell
|
||||||
|
|
||||||
def export_xlsx(self, output, should_preserve_existing=False):
|
def export_xlsx(self, output, should_preserve_existing=False):
|
||||||
|
df = self.export_pd()
|
||||||
|
if self.summaries:
|
||||||
|
df.loc[df.shape[0]] = self.summaries
|
||||||
|
|
||||||
if os.path.exists(output):
|
if os.path.exists(output):
|
||||||
book = openpyxl.load_workbook(output)
|
book = openpyxl.load_workbook(output)
|
||||||
with pd.ExcelWriter(
|
with pd.ExcelWriter(
|
||||||
@@ -273,10 +327,8 @@ class IfcCsv:
|
|||||||
mode="a",
|
mode="a",
|
||||||
if_sheet_exists="overlay" if should_preserve_existing else "replace",
|
if_sheet_exists="overlay" if should_preserve_existing else "replace",
|
||||||
) as writer:
|
) as writer:
|
||||||
df = self.export_pd()
|
|
||||||
df.to_excel(writer, sheet_name=book.sheetnames[0], index=False)
|
df.to_excel(writer, sheet_name=book.sheetnames[0], index=False)
|
||||||
else:
|
else:
|
||||||
df = self.export_pd()
|
|
||||||
df.to_excel(output, index=False, engine="openpyxl")
|
df.to_excel(output, index=False, engine="openpyxl")
|
||||||
|
|
||||||
def export_pd(self):
|
def export_pd(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user