diff --git a/src/bonsai/bonsai/bim/module/cost/__init__.py b/src/bonsai/bonsai/bim/module/cost/__init__.py index 8f472b5488..9873bb1646 100644 --- a/src/bonsai/bonsai/bim/module/cost/__init__.py +++ b/src/bonsai/bonsai/bim/module/cost/__init__.py @@ -64,6 +64,7 @@ classes = ( operator.RefreshCostScheduleCsv, operator.LoadCostItemElementQuantities, operator.LoadCostItemQuantities, + operator.ShowAssignedCostRate, operator.LoadCostItemResourceQuantities, operator.LoadCostItemTaskQuantities, operator.LoadCostItemTypes, diff --git a/src/bonsai/bonsai/bim/module/cost/data.py b/src/bonsai/bonsai/bim/module/cost/data.py index 4d8842f926..e9692ed0bb 100644 --- a/src/bonsai/bonsai/bim/module/cost/data.py +++ b/src/bonsai/bonsai/bim/module/cost/data.py @@ -110,6 +110,7 @@ class CostSchedulesData: cls._load_cost_item_quantities(cost_item, data) cls._load_cost_values(cost_item, data) cls._load_nesting_index(cost_item, data) + cls._load_assigned_cost_rate(cost_item, data) results[cost_item.id()] = data return results @@ -154,7 +155,12 @@ class CostSchedulesData: else: data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity if is_sum: + #pass If it is None it doesn't allow me to assign a cost rate composed by sum data["TotalAppliedValue"] = None + + @classmethod + def _load_assigned_cost_rate(cls, cost_item: ifcopenshell.entity_instance, data: CostItem) -> None: + data["AssignedCostRate"] = tool.Cost.get_cost_item_rate_assignment(cost_item) @classmethod def _load_cost_item_quantities(cls, cost_item: ifcopenshell.entity_instance, data: CostItem) -> None: @@ -248,6 +254,8 @@ class CostSchedulesData: ) -> None: value_data = cost_value.get_info() del value_data["AppliedValue"] + if tool.Cost.get_assigned_rate_cost_item(root_element): + root_element = tool.Cost.get_assigned_rate_cost_item(root_element) if value_data["UnitBasis"]: data = cost_value.UnitBasis.get_info() data["ValueComponent"] = data["ValueComponent"].wrappedValue diff --git a/src/bonsai/bonsai/bim/module/cost/operator.py b/src/bonsai/bonsai/bim/module/cost/operator.py index dc61687704..ea0a4517d7 100644 --- a/src/bonsai/bonsai/bim/module/cost/operator.py +++ b/src/bonsai/bonsai/bim/module/cost/operator.py @@ -19,6 +19,7 @@ # pyright: reportUnnecessaryTypeIgnoreComment=error import bpy +import textwrap import ifcopenshell.api import bonsai.tool as tool from bpy_extras.io_utils import ImportHelper, ExportHelper @@ -607,6 +608,33 @@ class LoadCostItemQuantities(bpy.types.Operator): core.load_cost_item_quantities(tool.Cost) return {"FINISHED"} +class ShowAssignedCostRate(bpy.types.Operator): + bl_idname = "bim.show_assigned_cost_rate" + bl_label = "Info about the assigned cost item rate" + bl_options = {"REGISTER"} + assigned_rate_id: bpy.props.IntProperty() + assigned_rate_name: bpy.props.StringProperty() + assigned_rate_description: bpy.props.StringProperty() + assigned_rate_total_value: bpy.props.FloatProperty() + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=450) + + def execute(self, context): + #core.load_cost_item_quantities(tool.Cost) IS IT NECESSARY? + return {"FINISHED"} + + def draw(self, context): + layout = self.layout + wrapper = textwrap.TextWrapper(width=80) + layout.label(text=f"ID: {self.assigned_rate_id}") + layout.label(text=f"Name: {self.assigned_rate_name}") + layout.label(text="Description:") + for line in wrapper.wrap(str(self.assigned_rate_description)): + layout.label(text=line) + layout.label(text=f"Value: {self.assigned_rate_total_value}") + class LoadCostItemElementQuantities(bpy.types.Operator): bl_idname = "bim.load_cost_item_element_quantities" @@ -658,7 +686,10 @@ class AssignCostValue(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): core.assign_cost_value( - tool.Ifc, cost_item=tool.Ifc.get().by_id(self.cost_item), cost_rate=tool.Ifc.get().by_id(self.cost_rate) + tool.Ifc, + tool.Cost, + cost_item=tool.Ifc.get().by_id(self.cost_item), + cost_rate=tool.Ifc.get().by_id(self.cost_rate) ) diff --git a/src/bonsai/bonsai/bim/module/cost/ui.py b/src/bonsai/bonsai/bim/module/cost/ui.py index a1ddc67684..f5e9e2c29d 100644 --- a/src/bonsai/bonsai/bim/module/cost/ui.py +++ b/src/bonsai/bonsai/bim/module/cost/ui.py @@ -655,6 +655,8 @@ class BIM_UL_cost_items_trait: for column in props.columns: split2.label(text=column.name) split2.label(text="Total Cost") + split2.alignment = "LEFT" + split2.label(text="Rate") def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: @@ -684,6 +686,8 @@ class BIM_UL_cost_items_trait: if self.props.change_cost_item_parent: self.draw_parent_operator(row, item.ifc_definition_id) + + self.draw_assigned_rate_column(split2, cost_item) # TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX @@ -723,6 +727,18 @@ class BIM_UL_cost_items_trait: layout.label(text=label) else: layout.label(text="-") + + def draw_assigned_rate_column(self, layout, cost_item): + row=layout.row() + row.alignment= "LEFT" + if cost_item["AssignedCostRate"] is not None: + op = row.operator("bim.show_assigned_cost_rate", text="", emboss=False, icon="ZOOM_IN") + op.assigned_rate_id = cost_item["AssignedCostRate"].id() + op.assigned_rate_name = cost_item["AssignedCostRate"].Name or "" + op.assigned_rate_description = cost_item["AssignedCostRate"].Description or "" + op.assigned_rate_total_value = CostSchedulesData.data["cost_items"][cost_item["AssignedCostRate"].id()]["TotalAppliedValue"] + else: + row.label(text="-") def draw_value_column(self, layout, cost_item): if cost_item["TotalAppliedValue"]: diff --git a/src/bonsai/bonsai/core/cost.py b/src/bonsai/bonsai/core/cost.py index 197922806f..78db06f54b 100644 --- a/src/bonsai/bonsai/core/cost.py +++ b/src/bonsai/bonsai/core/cost.py @@ -193,9 +193,18 @@ def load_cost_item_resource_quantities(cost: tool.Cost) -> None: def assign_cost_value( - ifc: tool.Ifc, cost_item: ifcopenshell.entity_instance, cost_rate: ifcopenshell.entity_instance + ifc: tool.Ifc, + cost: tool.Cost, + cost_item: ifcopenshell.entity_instance, + cost_rate: ifcopenshell.entity_instance ) -> None: ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate) + existing_cost_rate = cost.get_cost_item_rate_assignment(cost_item) + if existing_cost_rate is None: + ifc.run("control.assign_control", relating_control=cost_rate, related_object=cost_item) + else: + ifc.run("control.unassign_control", relating_control=existing_cost_rate, related_object=cost_item) + ifc.run("control.assign_control", relating_control=cost_rate, related_object=cost_item) def load_schedule_of_rates(cost: tool.Cost, schedule_of_rates: ifcopenshell.entity_instance) -> None: diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index 6135be093c..d8ccba3c28 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -228,6 +228,7 @@ class Cost: def get_active_cost_schedule(cls): pass def get_active_cost_schedule(cls): pass def get_attributes_for_cost_value(cls, cost_type, cost_category): pass + def get_assigned_rate_cost_item(cls, cost_item): pass def get_cost_item_attributes(cls, cost_item): pass def get_cost_item_products(cls): pass def get_cost_item_quantity_attributes(cls): pass diff --git a/src/bonsai/bonsai/tool/cost.py b/src/bonsai/bonsai/tool/cost.py index 3b8c7aae01..0bac42a09d 100644 --- a/src/bonsai/bonsai/tool/cost.py +++ b/src/bonsai/bonsai/tool/cost.py @@ -835,6 +835,12 @@ class Cost(bonsai.core.tool.Cost): ] return bool(cost_items) + @classmethod + def get_assigned_rate_cost_item(cls, cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: + for assignment in cost_item.HasAssignments: + if assignment.RelatingControl.is_a() == "IfcCostItem": + return assignment.RelatingControl + @classmethod def load_product_cost_items(cls, product: ifcopenshell.entity_instance) -> None: props = cls.get_cost_props()