From 528964ca561cb94782f90d9a09650cabade92481 Mon Sep 17 00:00:00 2001 From: Massimo Fabbro Date: Mon, 5 Jan 2026 08:58:50 +0100 Subject: [PATCH] See #6570. Formula column for ifc5d import from csv Now it's possible to specify the Formula column in the csv in order to calculate cost item quantities --- src/ifc5d/ifc5d/csv2ifc.py | 34 +++++++++++++++++-- .../api/cost/assign_cost_item_quantity.py | 7 ++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py index c33d6e847e..aaab6c7ab1 100644 --- a/src/ifc5d/ifc5d/csv2ifc.py +++ b/src/ifc5d/ifc5d/csv2ifc.py @@ -55,6 +55,9 @@ class CsvHeader(TypedDict): RateSchedule: NotRequired[str] RateID: NotRequired[str] + # Formula + Formula: NotRequired[str] + #QuantityClass: NotRequired[str] # Currently we assume that if column is not part of the main header, # then it is a cost value category. So here we list any additional column @@ -91,6 +94,8 @@ class CostItem(TypedDict): Property: Union[str, None] Query: Union[str, None] + Formula: Union[str, None] + #QuantityClass: Union[str, None] class Csv2Ifc: # Inputs. @@ -108,6 +113,7 @@ class Csv2Ifc: categories: dict[str, int] has_categories: bool has_rates: bool + has_formula: bool def __init__( self, @@ -163,9 +169,13 @@ class Csv2Ifc: if not self.headers: self.has_categories = True self.has_rates = False + self.has_categories = False + self.has_formula = False self.headers = {col: i for i, col in enumerate(row) if col} if "RateSchedule" in self.headers and "RateID" in self.headers: self.has_rates = True + if "Formula" in self.headers: + self.has_formula = True if "Value" in self.headers: self.has_categories = False else: @@ -233,6 +243,11 @@ class Csv2Ifc: else: cost_rate = None + if self.has_formula: + cost_formula = row[(self.headers["Formula"])] if "Formula" in self.headers else None + else: + cost_formula = None + return { "Identification": str(identification) if identification else None, "Name": str(name) if name else None, @@ -244,6 +259,7 @@ class Csv2Ifc: "Query": query, "children": [], "CostRate": cost_rate, + "Formula": cost_formula, } def create_ifc(self) -> None: @@ -381,17 +397,31 @@ class Csv2Ifc: # and some query in "Query" column. # If query is provided it will override the defined value # due current behaviour in cost.assign_cost_item_quantity. - if results: + if results and not cost_item["Formula"]: ifcopenshell.api.cost.assign_cost_item_quantity( self.file, cost_item=cost_item["ifc"], products=results, prop_name=prop_name, ) - elif not quantity: + elif not quantity and not cost_item["Formula"]: quantity = ifcopenshell.api.cost.add_cost_item_quantity( self.file, cost_item=cost_item["ifc"], ifc_class=quantity_class ) + if cost_item["Formula"]: + results = ifcopenshell.util.selector.filter_elements(self.file, cost_item["Query"]) + results = [r for r in results] + ifc_quantity_class = ifcopenshell.util.unit.get_symbol_quantity_class(cost_item["Unit"]) + try: + quantity = ifcopenshell.api.cost.assign_cost_item_quantity( + self.file, + cost_item=cost_item["ifc"], + products=results, + formula=cost_item["Formula"], + ifc_class=ifc_quantity_class, + ) + except: + quantity=0 self.create_cost_items(cost_item["children"], cost_item["ifc"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py index f70a3c4b66..7a57372e8b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py @@ -125,14 +125,14 @@ class Usecase: settings: dict[str, Any] def execute(self): - if self.settings["prop_name"]: + if self.settings["prop_name"] or self.settings["formula"]: self.quantities = set(self.settings["cost_item"].CostQuantities or []) for product in self.settings["products"]: if product.is_a("IfcSpatialElement"): continue self.assign_cost_control(related_object=product, cost_item=self.settings["cost_item"]) if self.settings["formula"]: - # not so much elegant, but simple and useful because variables can't has the dot in the name + # not so much elegant, but simple and useful because variables can't have the dot in the name # and Psets have the dot separator = "0" variables = self.extract_variables(self.settings["formula"]) @@ -153,9 +153,8 @@ class Usecase: result = eval(formula_modified, {}, variables_modified) new_quantity = None - self.quantities = set(self.settings["cost_item"].CostQuantities or []) for quantity in self.quantities: - if quantity.Formula == self.settings["formula"]: + if quantity.Formula == self.settings["formula"] and len(self.settings["products"]) == 1: #Todo improve it new_quantity = quantity self.settings["ifc_class"] = quantity.is_a() continue