diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index e0259d2ba6..1c9bd13b03 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -19,6 +19,8 @@ classes = ( operator.DisableEditingCostSchedule, operator.DisableEditingCostItemQuantity, operator.DisableEditingCostItemValue, + operator.AddCostColumn, + operator.RemoveCostColumn, operator.AddCostItem, operator.AddSummaryCostItem, operator.ExpandCostItem, @@ -38,6 +40,7 @@ classes = ( prop.BIMCostProperties, ui.BIM_PT_cost_schedules, ui.BIM_UL_cost_items, + ui.BIM_UL_cost_columns, ) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index e0ec2d12ea..f6da24010d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -673,3 +673,32 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): Data.load(IfcStore.get_file()) print("Import finished in {:.2f} seconds".format(time.time() - start)) return {"FINISHED"} + + +class AddCostColumn(bpy.types.Operator): + bl_idname = "bim.add_cost_column" + bl_label = "Add Cost Column" + bl_options = {"REGISTER", "UNDO"} + name: bpy.props.StringProperty() + + def execute(self, context): + self.props = context.scene.BIMCostProperties + new = self.props.columns.add() + new.name = self.name + Data.set_categories([c.name for c in self.props.columns]) + Data.load(IfcStore.get_file()) + return {"FINISHED"} + + +class RemoveCostColumn(bpy.types.Operator): + bl_idname = "bim.remove_cost_column" + bl_label = "Remove Cost Column" + bl_options = {"REGISTER", "UNDO"} + name: bpy.props.StringProperty() + + def execute(self, context): + self.props = context.scene.BIMCostProperties + self.props.columns.remove(self.props.columns.find(self.name)) + Data.set_categories([c.name for c in self.props.columns]) + Data.load(IfcStore.get_file()) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index c02660b537..98936463f9 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -105,3 +105,7 @@ class BIMCostProperties(PropertyGroup): cost_category: StringProperty(name="Cost Category") active_cost_item_value_id: IntProperty(name="Active Cost Item Value Id") cost_value_attributes: CollectionProperty(name="Cost Value Attributes", type=Attribute) + cost_column: StringProperty(name="Cost Column") + should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False) + columns: CollectionProperty(name="Columns", type=StrProperty) + active_column_index: IntProperty(name="Active Column Index") diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 64cdd14fd9..658a387b47 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -35,6 +35,7 @@ class BIM_PT_cost_schedules(Panel): if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule_id: op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="") op.cost_schedule = cost_schedule_id + row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY") if self.props.is_editing == "COST_SCHEDULE": row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK") elif self.props.is_editing == "COST_ITEMS": @@ -53,8 +54,16 @@ class BIM_PT_cost_schedules(Panel): if self.props.is_editing == "COST_SCHEDULE": self.draw_editable_cost_schedule_ui() elif self.props.is_editing == "COST_ITEMS": + if self.props.should_show_column_ui: + self.draw_column_ui() self.draw_editable_cost_item_ui(cost_schedule_id) + def draw_column_ui(self): + row = self.layout.row(align=True) + row.prop(self.props, "cost_column", text="") + row.operator("bim.add_cost_column", text="", icon="ADD").name = self.props.cost_column + self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index") + def draw_editable_cost_schedule_ui(self): for attribute in self.props.cost_schedule_attributes: row = self.layout.row(align=True) @@ -266,6 +275,10 @@ class BIM_UL_cost_items(UIList): op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC") op.cost_item = item.ifc_definition_id row.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"])) + + for column in props.columns: + row.label(text=str(cost_item["CategoryValues"].get(column.name, "-"))) + row.label(text="{0:.2f}".format(cost_item["TotalCostValue"]), icon="CON_TRANSLIKE") if context.active_object: @@ -297,3 +310,12 @@ class BIM_UL_cost_items(UIList): ).cost_item = item.ifc_definition_id row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = item.ifc_definition_id row.operator("bim.remove_cost_item", text="", icon="X").cost_item = item.ifc_definition_id + + +class BIM_UL_cost_columns(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + props = context.scene.BIMCostProperties + if item: + row = layout.row(align=True) + row.prop(item, "name", emboss=False, text="") + row.operator("bim.remove_cost_column", text="", icon="X").name = item.name diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py index 6c5a846d93..3e08f7ad47 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/data.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/data.py @@ -8,6 +8,7 @@ class Data: cost_items = {} physical_quantities = {} cost_values = {} + categories = [] @classmethod def purge(cls): @@ -16,6 +17,11 @@ class Data: cls.cost_items = {} cls.physical_quantities = {} cls.cost_values = {} + cls.categories = [] + + @classmethod + def set_categories(cls, categories): + cls.categories = categories @classmethod def load(cls, file): @@ -81,14 +87,15 @@ class Data: data["CostValues"] = [] data["TotalCostValue"] = 0.0 data["TotalAppliedValue"] = 0.0 + data["CategoryValues"] = {} for cost_value in cost_item.CostValues or []: - cls.load_cost_item_value(cost_item, cost_value) + cls.load_cost_item_value(data, cost_item, cost_value) data["CostValues"].append(cost_value.id()) data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"] data["TotalCostValue"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] @classmethod - def load_cost_item_value(cls, cost_item, cost_value): + def load_cost_item_value(cls, cost_item_data, cost_item, cost_value): value_data = cost_value.get_info() del value_data["AppliedValue"] del value_data["UnitBasis"] @@ -98,9 +105,14 @@ class Data: value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"]) value_data["Components"] = [c.id() for c in value_data["Components"] or []] value_data["AppliedValue"] = cls.calculate_applied_value(cost_item, cost_value) + + if cost_value.Category not in [None, "*"]: + cost_item_data["CategoryValues"].setdefault(cost_value.Category, 0) + cost_item_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"] + cls.cost_values[cost_value.id()] = value_data for component in cost_value.Components or []: - cls.load_cost_item_value(cost_item, component) + cls.load_cost_item_value(cost_item_data, cost_item, component) @classmethod def calculate_applied_value(cls, cost_item, cost_value, category_filter=None):