From ba24f9b491cfb5d7ec35e64d7659096405c6c9d4 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:15:54 +0100 Subject: [PATCH] - Improve ODS export - Fix XLSX export bug - Improve active cost item UI active cost item - Fix missing tool cost function --- .../blenderbim/bim/module/cost/ui.py | 4 +- src/blenderbim/blenderbim/tool/cost.py | 8 +- src/ifc5d/README.md | 12 ++- src/ifc5d/ifc5d/ifc5Dspreadsheet.py | 87 +++++++++++-------- 4 files changed, 68 insertions(+), 43 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index c0e8c365f2..b5c9cf4cf1 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -175,7 +175,7 @@ class BIM_PT_cost_schedules(Panel): row.prop(self.props, "cost_types", text="") if self.props.cost_types == "CATEGORY": row.prop(self.props, "cost_category", text="") - op = row.operator("bim.add_cost_value", text="", icon="ADD") + op = row.operator("bim.add_cost_value", text="Add Value", icon="ADD") op.parent = self.props.active_cost_item_id op.cost_type = self.props.cost_types if self.props.cost_types == "CATEGORY": @@ -511,6 +511,8 @@ class BIM_UL_cost_items_trait: self.draw_quantity_column(split2, cost_item) self.draw_value_column(split2, cost_item) + if self.props.active_cost_item_id == item.ifc_definition_id: + row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL") for column in self.props.columns: split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-"))) diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index cb93868ad4..56e0e4204e 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -314,6 +314,10 @@ class Cost(blenderbim.core.tool.Cost): ) blenderbim.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback=callback) + @classmethod + def calculate_applied_value(cls, cost_item, cost_value): + return ifcopenshell.util.cost.calculate_applied_value(cost_item, cost_value) + @classmethod def is_active_schedule_of_rates(cls): return ( @@ -336,7 +340,7 @@ class Cost(blenderbim.core.tool.Cost): @classmethod def load_cost_item_value_formula_attributes(cls, cost_value=None): props = bpy.context.scene.BIMCostProperties - props.cost_value_attributes.clear() # why is this necessary? + props.cost_value_attributes.clear() bpy.context.scene.BIMCostProperties.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value) @classmethod @@ -517,7 +521,7 @@ class Cost(blenderbim.core.tool.Cost): elif format == "XLSX": from ifc5d.ifc5Dspreadsheet import Ifc5DXlsxWriter - writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path + ".xlsx") + writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path) writer.write() @classmethod diff --git a/src/ifc5d/README.md b/src/ifc5d/README.md index a431107580..dd9be5cddb 100644 --- a/src/ifc5d/README.md +++ b/src/ifc5d/README.md @@ -25,6 +25,8 @@ Planned (would you like to contribute? Please reach out!): ## Usage IFC to CSV, ODS, XSLS +#TODO + ### CLI app for converting IFC files to CSV, ODS or XLSX format. Usage: @@ -39,12 +41,14 @@ Options: -f, --format format_type (str): The output format to export in (csv/ods/xlsx). Default is csv. Examples: - python ifc5Dspreadsheet.py "C:\Users\Username\Desktop\test_cost.ifc" rev_01_schedule -l error.log -f ods - python ifc5Dspreadsheet.py "C:\Users\Dev-Machine\Desktop\test_cost.ifc" "C:\Users\Dev-Machine\Desktop" -l error.log -f csv + python ifc5Dspreadsheet.py "C:\Users\Dev-Machine\Desktop\test_cost.ifc" rev_01_schedule -l error.log -f ODS + python ifc5Dspreadsheet.py "C:\Users\Username\Desktop\test_cost.ifc" "C:\Users\Username\Desktop" -l error.log -f CSV + ### Scripting: Example for ODS exports: -""" + +``` import ifcopenshell from ifc5d.ifc5Dspreadsheet import Ifc5DOdsWriter @@ -54,4 +58,4 @@ path = "directory/cost_schedule" writer = Ifc5DOdsWriter(file=file, output=path) writer.write() -""" \ No newline at end of file +``` \ No newline at end of file diff --git a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py index 0c12c98afd..7c91934790 100644 --- a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py +++ b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py @@ -25,6 +25,7 @@ import logging import ifcopenshell import ifcopenshell.util.element import ifcopenshell.util.cost +import ifcopenshell.util.date class IfcDataGetter: @@ -34,6 +35,12 @@ class IfcDataGetter: return None return file.by_type("IfcCostSchedule") + @staticmethod + def canonicalise_time(time): + if not time: + return "-" + return time.strftime("%d/%m/%y") + @staticmethod def get_root_costs(cost_schedule): return [obj for rel in cost_schedule.Controls or [] for obj in rel.RelatedObjects or []] @@ -78,7 +85,6 @@ class IfcDataGetter: categories = set() for cost_item in IfcDataGetter.get_root_costs(schedule): IfcDataGetter.process_cost_item_categories(cost_item, categories) - print("COST CATEGORIES", categories) return categories @staticmethod @@ -88,7 +94,7 @@ class IfcDataGetter: quantity_data = IfcDataGetter.get_cost_item_quantity(file, cost_item) cost_values_data = IfcDataGetter.get_cost_item_values(cost_item) - children = [obj for rel in cost_item.IsNestedBy or [] for obj in rel.RelatedObjects or []] + data = { "Index": index, "Hierarchy": hierarchy, @@ -97,15 +103,13 @@ class IfcDataGetter: "Description": cost_item.Name, "Unit": cost_values_data[0]["unit"] if cost_values_data else "", "Quantity": quantity_data["quantity"]["total_quantity"], - # "Total": str(quantity_data["total_quantity"] * cost_values_data[0]["applied_value"] if cost_values_data else 0), - # "Children": listToString(children), "ChildrenData": [], } for cost_value in cost_values_data or []: cost_category = "{}{}".format(cost_value["category"], " Cost") data[cost_category] = cost_value["applied_value"] if data.get("* Cost", None): - data["Total"] = data["* Cost"] + data["Total Price"] = data["* Cost"] data["* Cost"] = "" rate_subtotal = 0 for key, value in data.items(): @@ -182,9 +186,7 @@ class IfcDataGetter: for quantity in cost_item.CostQuantities or []: if not quantity in accounted_for: total_cost_quantity += add_quantity(quantity, take_off_name) - print(cost_item.Name, quantity[0], quantity[3], "was not accounted for parametrically") - print("Total Cost Quantity Including Non Parametric", total_cost_quantity) return { "id": cost_item.id(), "name": cost_item.Name, @@ -215,6 +217,7 @@ class Ifc5Dwriter: def parse(self): self.column_indexes = [] + self.used_names = [] for i in range(26): self.column_indexes.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i % 26]) for cost_schedule in self.cost_schedules: @@ -230,11 +233,19 @@ class Ifc5Dwriter: "Unit", ] cost_rate_categories = IfcDataGetter.get_cost_rates_categories(cost_schedule) - self.sheet_data[sheet_id]["headers"].extend(list(cost_rate_categories)) - self.sheet_data[sheet_id]["headers"].extend(["General Cost", "Rate Subtotal", "Total", "Children"]) + self.sheet_data[sheet_id]["headers"].extend(list(cost_rate_categories)).extend( + ["Rate Subtotal", "Total Price", "Children"] + ) self.sheet_data[sheet_id]["cost_items"] = IfcDataGetter.get_cost_items_data(self.file, cost_schedule) - - print(self.sheet_data) + self.sheet_data[sheet_id]["UpdateDate"] = IfcDataGetter.canonicalise_time( + ifcopenshell.util.date.ifc2datetime(cost_schedule.UpdateDate) + ) + self.sheet_data[sheet_id]["PredefinedType"] = cost_schedule.PredefinedType + schedule_name = cost_schedule.Name or "Unnamed" + if schedule_name in self.used_names: + schedule_name = "{}_{}".format(schedule_name, self.used_names.count(schedule_name)) + self.sheet_data[sheet_id]["Name"] = schedule_name + self.used_names.append(schedule_name) def multiply_cells(self, cell1, cell2): return "={}*{}".format(cell1, cell2) @@ -265,15 +276,13 @@ class Ifc5DCsvWriter(Ifc5Dwriter): super().write() for sheet, data in self.sheet_data.items(): - print("-------------------------------------") - print(sheet, data) - print("-------------------------------------") - with open(os.path.join(self.output, "{}.csv".format(sheet)), "w", newline="", encoding="utf-8") as file: + with open( + os.path.join(self.output, "{}.csv".format(data["Name"])), "w", newline="", encoding="utf-8" + ) as file: writer = csv.writer(file) writer.writerow(data["headers"]) row = [] for cost_item_data in data["cost_items"]: - print(cost_item_data) writer.writerow([cost_item_data.get(column, "") for column in data["headers"]]) @@ -332,7 +341,7 @@ class Ifc5DOdsWriter(Ifc5Dwriter): self.row_count += 1 for i, column in enumerate(self.sheet_data[cost_schedule.id()]["headers"]): - if column == "Total" and cost_data["Quantity"] != 0 and cost_data["Rate Subtotal"]: + if column == "Total Price" and cost_data["Quantity"] != 0 and cost_data["Rate Subtotal"]: cell_quantity = self.get_cell_position(cost_schedule.id(), "Quantity") cell_subtotal_rate = self.get_cell_position(cost_schedule.id(), "Rate Subtotal") value = self.multiply_cells(cell_quantity, cell_subtotal_rate) @@ -356,52 +365,58 @@ class Ifc5DOdsWriter(Ifc5Dwriter): row.addElement(cell) table.addElement(row) - table = Table(name=cost_schedule.Name) + table = Table(name=self.sheet_data[cost_schedule.id()].get("Name", "")) + + new = row() + add_cell(type="text", value="Predefined Type:", row=new, style="fed8b1") + add_cell( + type="text", value=self.sheet_data[cost_schedule.id()].get("PredefinedType", ""), row=new, style="fed8b1" + ) + table.addElement(new) new = row() add_cell(type="text", value="Cost Schedule:", row=new, style="fed8b1") - add_cell(type="text", value=cost_schedule.Name or "Unnamed Cost Schedule", row=new, style="fed8b1") + add_cell(type="text", value=self.sheet_data[cost_schedule.id()]["Name"], row=new, style="fed8b1") + table.addElement(new) + + new = row() + add_cell(type="text", value="Update Date:", row=new, style="fed8b1") + add_cell(type="text", value=self.sheet_data[cost_schedule.id()].get("UpdateDate", ""), row=new, style="fed8b1") + table.addElement(new) + + new = row() table.addElement(new) header_row = TableRow() - print("currently data is ", self.sheet_data) for header in self.sheet_data[cost_schedule.id()]["headers"]: add_cell(type="text", value=header, row=header_row, style="fed8b1") table.addElement(header_row) - new = row() - new2 = row() - table.addElement(new) - table.addElement(new2) - - self.row_count = 4 + self.row_count = 5 for cost_item_data in self.sheet_data[cost_schedule.id()]["cost_items"]: add_cost_item_rows(table, cost_item_data) self.doc.spreadsheet.addElement(table) + class Ifc5DXlsxWriter(Ifc5Dwriter): def write(self): import xlsxwriter + super().write() - self.workbook = xlsxwriter.Workbook(self.output) + self.workbook = xlsxwriter.Workbook(self.output + ".xlsx") self.used_names = [] for cost_schedule in self.cost_schedules: self.write_table(cost_schedule) self.workbook.close() - - def write_table(self, cost_schedule): - schedule_name = cost_schedule.Name or "Unnamed Cost Schedule" - if schedule_name in self.used_names: - schedule_name = "{}_{}".format(schedule_name, self.used_names.count(schedule_name)) - worksheet = self.workbook.add_worksheet(schedule_name) - self.used_names.append(schedule_name) + def write_table(self, cost_schedule): + worksheet = self.workbook.add_worksheet(self.sheet_data[cost_schedule.id()]["Name"]) headers = self.sheet_data[cost_schedule.id()]["headers"] for i, header in enumerate(headers): worksheet.write(0, i, header) - + row = 1 for cost_item_data in self.sheet_data[cost_schedule.id()]["cost_items"]: col = 0 @@ -417,7 +432,7 @@ if __name__ == "__main__": parser.add_argument("output", help="The output directory for CSV or filename for other formats") parser.add_argument("-l", "--log", type=str, help="Specify where errors should be logged", default="process.log") parser.add_argument( - "-f", "--format", type=str, help="Choose which format to export in (csv/ods/xlsx)", default="csv" + "-f", "--format", type=str, help="Choose which format to export in (CSV/ODS/XLSX)", default="CSV" ) args = vars(parser.parse_args())