From a80fbdbc1df0fcb4a18d828f7fb6c25148c08533 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 8 May 2021 12:18:27 +1000 Subject: [PATCH] Implement arithmetic formulas for cost value components --- src/blenderbim/blenderbim/bim/helper.py | 2 +- .../blenderbim/bim/module/cost/__init__.py | 4 +- .../blenderbim/bim/module/cost/operator.py | 18 ++--- .../blenderbim/bim/module/cost/ui.py | 69 +++++++++++++------ .../api/cost/add_cost_item_value.py | 16 ----- .../ifcopenshell/api/cost/add_cost_value.py | 18 +++++ .../ifcopenshell/api/cost/data.py | 25 ++++++- ..._cost_item_value.py => edit_cost_value.py} | 0 8 files changed, 99 insertions(+), 53 deletions(-) delete mode 100644 src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_value.py create mode 100644 src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py rename src/ifcopenshell-python/ifcopenshell/api/cost/{edit_cost_item_value.py => edit_cost_value.py} (100%) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index c39759e72f..e445aa67fb 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -11,7 +11,7 @@ from blenderbim.bim.ifc import IfcStore def import_attributes(ifc_class, props, data, callback=None): for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": + if data_type == "entity" or (isinstance(data_type, tuple) and "entity" in ".".join(data_type)): continue new = props.add() new.name = attribute.name() diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index 3ded7ee9e1..242c41809b 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -7,7 +7,7 @@ classes = ( operator.EditCostSchedule, operator.EditCostItem, operator.EditCostItemQuantity, - operator.EditCostItemValue, + operator.EditCostValue, operator.EnableEditingCostSchedule, operator.EnableEditingCostItems, operator.EnableEditingCostItem, @@ -28,7 +28,7 @@ classes = ( operator.UnassignControl, operator.AddCostItemQuantity, operator.RemoveCostItemQuantity, - operator.AddCostItemValue, + operator.AddCostValue, operator.RemoveCostItemValue, prop.CostItem, prop.BIMCostProperties, diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 8cdfa51fb0..134fdb757a 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -423,10 +423,10 @@ class EditCostItemQuantity(bpy.types.Operator): return {"FINISHED"} -class AddCostItemValue(bpy.types.Operator): - bl_idname = "bim.add_cost_item_value" - bl_label = "Add Cost Item Value" - cost_item: bpy.props.IntProperty() +class AddCostValue(bpy.types.Operator): + bl_idname = "bim.add_cost_value" + bl_label = "Add Cost Value" + parent: bpy.props.IntProperty() cost_type: bpy.props.StringProperty() cost_category: bpy.props.StringProperty() @@ -438,9 +438,9 @@ class AddCostItemValue(bpy.types.Operator): category = "*" elif self.cost_type == "CATEGORY": category = self.cost_category - value = ifcopenshell.api.run("cost.add_cost_item_value", self.file, cost_item=self.file.by_id(self.cost_item)) + value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=self.file.by_id(self.parent)) ifcopenshell.api.run( - "cost.edit_cost_item_value", self.file, cost_value=value, attributes={"Category": category} + "cost.edit_cost_value", self.file, cost_value=value, attributes={"Category": category} ) Data.load(self.file) return {"FINISHED"} @@ -497,8 +497,8 @@ class DisableEditingCostItemValue(bpy.types.Operator): return {"FINISHED"} -class EditCostItemValue(bpy.types.Operator): - bl_idname = "bim.edit_cost_item_value" +class EditCostValue(bpy.types.Operator): + bl_idname = "bim.edit_cost_value" bl_label = "Edit Cost Item Value" cost_value: bpy.props.IntProperty() @@ -507,7 +507,7 @@ class EditCostItemValue(bpy.types.Operator): attributes = blenderbim.bim.helper.export_attributes(props.cost_value_attributes) self.file = IfcStore.get_file() ifcopenshell.api.run( - "cost.edit_cost_item_value", + "cost.edit_cost_value", self.file, **{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes}, ) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 588ddac7c4..30ecd8f07f 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -149,35 +149,59 @@ 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_item_value", text="", icon="ADD") - op.cost_item = self.props.active_cost_item_id + op = row.operator("bim.add_cost_value", text="", icon="ADD") + op.parent = self.props.active_cost_item_id op.cost_type = self.props.cost_types if self.props.cost_types == "CATEGORY": op.cost_category = self.props.cost_category for cost_value_id in Data.cost_items[self.props.active_cost_item_id]["CostValues"]: - cost_value = Data.cost_values[cost_value_id] - row = self.layout.row(align=True) - row.label(text=str(cost_value["Category"])) - row.label(text=str(cost_value["AppliedValue"])) - if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id: - op = row.operator("bim.edit_cost_item_value", text="", icon="CHECKMARK") - op.cost_value = cost_value_id - row.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL") - elif self.props.active_cost_item_value_id: - op = row.operator("bim.remove_cost_item_value", text="", icon="X") - op.cost_value = cost_value_id - else: - op = row.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL") - op.cost_value = cost_value_id - op = row.operator("bim.remove_cost_item_value", text="", icon="X") - op.cost_value = cost_value_id + self.draw_readonly_cost_value_ui(self.layout, cost_value_id) - if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id: - box = self.layout.box() - self.draw_editable_cost_item_value_ui(box) + if self.props.active_cost_item_value_id: + box = self.layout.box() + self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_item_value_id]) - def draw_editable_cost_item_value_ui(self, layout): + + def draw_readonly_cost_value_ui(self, layout, cost_value_id): + cost_value = Data.cost_values[cost_value_id] + row = layout.row(align=True) + row.label(text=str(cost_value["Category"]), icon="DISC") + row.label(text=str(cost_value["AppliedValue"])) + if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id: + op = row.operator("bim.edit_cost_value", text="", icon="CHECKMARK") + op.cost_value = cost_value_id + op = row.operator("bim.add_cost_value", text="", icon="ADD") + op.parent = cost_value_id + op.cost_type = self.props.cost_types + if self.props.cost_types == "CATEGORY": + op.cost_category = self.props.cost_category + row.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL") + elif self.props.active_cost_item_value_id: + op = row.operator("bim.add_cost_value", text="", icon="ADD") + op.parent = cost_value_id + op.cost_type = self.props.cost_types + if self.props.cost_types == "CATEGORY": + op.cost_category = self.props.cost_category + op = row.operator("bim.remove_cost_item_value", text="", icon="X") + op.cost_value = cost_value_id + else: + op = row.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL") + op.cost_value = cost_value_id + op = row.operator("bim.add_cost_value", text="", icon="ADD") + op.parent = cost_value_id + op.cost_type = self.props.cost_types + if self.props.cost_types == "CATEGORY": + op.cost_category = self.props.cost_category + op = row.operator("bim.remove_cost_item_value", text="", icon="X") + op.cost_value = cost_value_id + + if len(cost_value["Components"]): + box = layout.box() + for component_id in cost_value["Components"]: + self.draw_readonly_cost_value_ui(box, component_id) + + def draw_editable_cost_value_ui(self, layout, cost_value): for attribute in self.props.cost_value_attributes: row = layout.row(align=True) if attribute.data_type == "string": @@ -194,6 +218,7 @@ class BIM_PT_cost_schedules(Panel): row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + class BIM_UL_cost_items(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_value.py deleted file mode 100644 index b39b42ff32..0000000000 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_item_value.py +++ /dev/null @@ -1,16 +0,0 @@ -import ifcopenshell.api - - -class Usecase: - def __init__(self, file, **settings): - self.file = file - self.settings = {"cost_item": None, "ifc_class": "IfcQuantityCount"} - for key, value in settings.items(): - self.settings[key] = value - - def execute(self): - value = self.file.create_entity("IfcCostValue") - values = list(self.settings["cost_item"].CostValues or []) - values.append(value) - self.settings["cost_item"].CostValues = values - return value diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py new file mode 100644 index 0000000000..eb3c44f2c7 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_value.py @@ -0,0 +1,18 @@ +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"parent": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + value = self.file.create_entity("IfcCostValue") + if self.settings["parent"].is_a("IfcCostItem"): + values = list(self.settings["parent"].CostValues or []) + values.append(value) + self.settings["parent"].CostValues = values + elif self.settings["parent"].is_a("IfcCostValue"): + values = list(self.settings["parent"].Components or []) + values.append(value) + self.settings["parent"].Components = values + return value diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py index 3fe3ff51cc..9d01cdcbdc 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py @@ -87,12 +87,31 @@ class Data: value_data["Components"] = [c.id() for c in value_data["Components"] or []] value_data["AppliedValue"] = cls.calculate_applied_value(cost_item, cost_value) cls.cost_values[cost_value.id()] = value_data + for component in cost_value.Components or []: + cls.load_cost_item_value(cost_item, component) @classmethod def calculate_applied_value(cls, cost_item, cost_value, category_filter=None): - result = 0 if cost_value.ArithmeticOperator and cost_value.Components: - pass # TODO + component_values = [] + for component in cost_value.Components: + component_values.append(cls.calculate_applied_value(cost_item, component, category_filter)) + if cost_value.ArithmeticOperator == "ADD": + return sum(component_values) + result = component_values.pop(0) + if cost_value.ArithmeticOperator == "DIVIDE": + for value in component_values: + try: + result /= value + except ZeroDivisionError: + pass + elif cost_value.ArithmeticOperator == "MULTIPLY": + for value in component_values: + result *= value + elif cost_value.ArithmeticOperator == "SUBTRACT": + for value in component_values: + result -= value + return result if cost_value.Category is None: return cls.get_primitive_applied_value(cost_value.AppliedValue) elif cost_value.Category == "*": @@ -105,7 +124,7 @@ class Data: return cls.sum_child_cost_items(cost_item, category_filter=cost_value.Category) else: return cls.get_primitive_applied_value(cost_value.AppliedValue) - return result + return 0 @classmethod def sum_child_cost_items(cls, cost_item, category_filter=None): diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_item_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py similarity index 100% rename from src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_item_value.py rename to src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py