From efec360a00b3efc3b978dae7f2d197d02540f191 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 9 Aug 2021 17:57:34 +1000 Subject: [PATCH] Ifc5d can now parse cost category breakdowns in CSV form --- src/ifc5d/ifc5d/csv2ifc.py | 80 +++++++++++++------ src/ifc5d/test.csv | 24 +++--- .../ifcopenshell/util/unit.py | 12 +-- 3 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py index 14433d9c01..8e8b704d6e 100644 --- a/src/ifc5d/ifc5d/csv2ifc.py +++ b/src/ifc5d/ifc5d/csv2ifc.py @@ -1,4 +1,3 @@ - # Ifc5D - IFC costing utility # Copyright (C) 2021 Dion Moult # @@ -36,11 +35,21 @@ class Csv2Ifc: def parse_csv(self): self.parents = {} + self.headers = {} with open(self.csv, "r") as csv_file: reader = csv.reader(csv_file) for row in reader: if not row[0]: continue + if row[0] == "Hierarchy": + self.has_categories = True + for i, col in enumerate(row): + if not col: + continue + if col == "Value": + self.has_categories = False + self.headers[col] = i + continue cost_data = self.get_row_cost_data(row) hierarchy_key = int(row[0]) if hierarchy_key == 1: @@ -50,11 +59,23 @@ class Csv2Ifc: self.parents[hierarchy_key] = cost_data def get_row_cost_data(self, row): + name = row[self.headers["Name"]] + cost_quantities = row[self.headers["Quantity"]] + cost_quantities_unit = row[self.headers["Unit"]] + if self.has_categories: + cost_values = { + k: float(row[v]) + for k, v in self.headers.items() + if k not in ["Hierarchy", "Name", "Quantity", "Unit", "Subtotal"] and row[v] + } + else: + cost_values = row[self.headers["Value"]] + cost_values = float(cost_values) if cost_values else None return { - "Name": str(row[1]) if row[1] else None, - "CostQuantities": float(row[2]) if row[2] else None, - "CostQuantitiesUnit": str(row[3]) if row[3] else None, - "CostValues": float(row[4]) if row[4] else None, + "Name": str(name) if name else None, + "CostQuantities": float(cost_quantities) if cost_quantities else None, + "CostQuantitiesUnit": str(cost_quantities_unit) if cost_quantities_unit else None, + "CostValues": cost_values, "children": [], } @@ -67,26 +88,37 @@ class Csv2Ifc: def create_cost_items(self, cost_items, parent=None): for cost_item in cost_items: - if parent is None: - cost_item["ifc"] = ifcopenshell.api.run( - "cost.add_cost_item", self.file, cost_schedule=self.cost_schedule - ) - else: - cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=parent) - cost_item["ifc"].Name = cost_item["Name"] - if cost_item["CostValues"]: + self.create_cost_item(cost_item, parent) + + def create_cost_item(self, cost_item, parent): + if parent is None: + cost_item["ifc"] = ifcopenshell.api.run( + "cost.add_cost_item", self.file, cost_schedule=self.cost_schedule + ) + else: + cost_item["ifc"] = ifcopenshell.api.run("cost.add_cost_item", self.file, cost_item=parent) + + cost_item["ifc"].Name = cost_item["Name"] + + if not cost_item["CostValues"]: + cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) + cost_value.Category = "*" + elif self.has_categories: + for category, value in cost_item["CostValues"].items(): cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) - cost_value.AppliedValue = self.file.createIfcReal(cost_item["CostValues"]) - else: - cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) - cost_value.Category = "*" - if cost_item["CostQuantities"]: - quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["CostQuantitiesUnit"]) - quantity = ifcopenshell.api.run( - "cost.add_cost_item_quantity", self.file, cost_item=cost_item["ifc"], ifc_class=quantity_class - ) - quantity[3] = cost_item["CostQuantities"] - self.create_cost_items(cost_item["children"], cost_item["ifc"]) + cost_value.AppliedValue = self.file.createIfcReal(value) + cost_value.Category = category + else: + cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=cost_item["ifc"]) + cost_value.AppliedValue = self.file.createIfcReal(cost_item["CostValues"]) + + if cost_item["CostQuantities"]: + quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["CostQuantitiesUnit"]) + quantity = ifcopenshell.api.run( + "cost.add_cost_item_quantity", self.file, cost_item=cost_item["ifc"], ifc_class=quantity_class + ) + quantity[3] = cost_item["CostQuantities"] + self.create_cost_items(cost_item["children"], cost_item["ifc"]) def create_boilerplate_ifc(self): self.file = ifcopenshell.file(schema="IFC4") diff --git a/src/ifc5d/test.csv b/src/ifc5d/test.csv index 40d6bae51c..203122abae 100644 --- a/src/ifc5d/test.csv +++ b/src/ifc5d/test.csv @@ -1,10 +1,14 @@ -1,"Demolition",,, -2,"Building A",,, -3,"Soft strip",1,"m",5 -3,"Hard Strip",2,"m2",6 -3,"Hazmat",3,"m3",7 -,,,, -2,"Building B",,, -3,"Soft strip",4,"hr",8 -3,"Hard Strip",5,,9 -3,"Hazmat",6,"kg",10 +"Hierarchy","Name","Quantity","Unit","Foo","Bar","Baz","Subtotal" +1,"Demolition",,,,,,301 +,,,,,,, +2,"Building A",,,,,,96 +,,,,,,, +3,"Soft strip",1,"m",5,4,3,12 +3,"Hard Strip",2,"m2",6,5,4,30 +3,"Hazmat",3,"m3",7,6,5,54 +,,,,,,, +2,"Building B",,,,,,205 +,,,,,,, +3,"Soft strip",4,"hr",8,7,,60 +3,"Hard Strip",5,,9,,8,85 +3,"Hazmat",6,"kg",10,,,60 diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 52cfcfa8c5..5156ecac05 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -198,17 +198,19 @@ def get_property_unit(prop, ifc_file): def get_symbol_quantity_class(symbol): + # Dumb, but everybody gets it, unlike regex golf if not symbol: return "IfcQuantityCount" - elif symbol[-1:] == "g": + symbol = symbol.lower() + if symbol in ["kg", "g", "mt", "kt", "t"]: return "IfcQuantityWeight" - elif symbol[-1:] == "s" or symbol == "hr": + elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]: return "IfcQuantityTime" - elif symbol[-1:] == "3": + elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]: return "IfcQuantityVolume" - elif symbol[-1:] == "2": + elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]: return "IfcQuantityArea" - elif symbol[-1:] == "m" or symbol in ["in", "ft", "yd"]: + elif symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]: return "IfcQuantityLength" return "IfcQuantityCount"