From fa261b32811bef9847c9d7d5592f49ef4a206032 Mon Sep 17 00:00:00 2001 From: Sigma Dimensions <79010126+myoualid@users.noreply.github.com> Date: Wed, 29 Mar 2023 01:49:55 +0000 Subject: [PATCH] Features to re-order cost items in a tree (changing parent & changing nesting order) --- .../blenderbim/bim/module/cost/__init__.py | 2 + .../blenderbim/bim/module/cost/data.py | 63 +++++++++++++------ .../blenderbim/bim/module/cost/operator.py | 35 ++++++++++- .../blenderbim/bim/module/cost/prop.py | 8 +++ .../blenderbim/bim/module/cost/ui.py | 29 ++++++++- src/blenderbim/blenderbim/core/cost.py | 13 +++- src/blenderbim/blenderbim/tool/cost.py | 18 ++++++ 7 files changed, 145 insertions(+), 23 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index 77f96e7c9c..b05e41a5ad 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -70,10 +70,12 @@ classes = ( operator.ClearCostItemAssignments, operator.HighlightProductCostItem, operator.LoadProductCostItems, + operator.ReorderCostItem, operator.SelectUnassignedProducts, operator.LoadCostItemElementQuantities, operator.LoadCostItemTaskQuantities, operator.LoadCostItemResourceQuantities, + operator.ChangeParentCostItem, prop.CostItem, prop.CostItemQuantity, prop.CostItemType, diff --git a/src/blenderbim/blenderbim/bim/module/cost/data.py b/src/blenderbim/blenderbim/bim/module/cost/data.py index 6016877c0c..3cbef496ec 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/data.py +++ b/src/blenderbim/blenderbim/bim/module/cost/data.py @@ -89,9 +89,16 @@ class CostSchedulesData: data = {} cls._load_cost_item_quantities(cost_item, data) cls._load_cost_values(cost_item, data) + cls._load_nesting_index(cost_item, data) results[cost_item.id()] = data return results + @classmethod + def _load_nesting_index(cls, cost_item, data): + data["NestingIndex"] = None + for rel in cost_item.Nests or []: + data["NestingIndex"] = rel.RelatedObjects.index(cost_item) + @classmethod def _load_cost_values(cls, root_element, data): # data["CostValues"] = [] @@ -119,34 +126,51 @@ class CostSchedulesData: @classmethod def _load_cost_item_quantities(cls, cost_item, data): - parametric_quantities = [] - for rel in cost_item.Controls: - for related_object in rel.RelatedObjects or []: - quantities = cls._get_object_quantities(cost_item, related_object) - # data["Controls"][related_object.id()] = quantities - parametric_quantities.extend(quantities) - - # data["CostQuantities"] = [] + # parametric_quantities = [] + # for rel in cost_item.Controls: + # for related_object in rel.RelatedObjects or []: + # quantities = cls._get_object_quantities(cost_item, related_object) + # parametric_quantities.extend(quantities) data["TotalCostQuantity"] = ifcopenshell.util.cost.get_total_quantity(cost_item) - for quantity in cost_item.CostQuantities or []: - if quantity.id() in parametric_quantities: - continue - # quantity_data = quantity.get_info() - # del quantity_data["Unit"] - # cls.physical_quantities[quantity.id()] = quantity_data - # data["CostQuantities"].append(quantity.id()) - # data["Unit"] = None - data["UnitSymbol"] = "?" + data["UnitSymbol"] = "-" if cost_item.CostQuantities: quantity = cost_item.CostQuantities[0] unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get()) if unit: - # data["Unit"] = unit.id() data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit) else: - # data["Unit"] = None data["UnitSymbol"] = None + # same_unit_nested_cost_item = set() + # data["DerivedTotalCostQuantity"] = None + # if cost_item.IsNestedBy: + # data["TotalCostQuantity"] = 0.0 + # derived_quantity = 0 + # derived_unit = None + # should_sum = False + # for rel in cost_item.IsNestedBy: + # for related_object in rel.RelatedObjects or []: + # for quantity in related_object.CostQuantities or []: + # nested_unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get()) + # if derived_unit is None: + # derived_unit = nested_unit + # elif nested_unit and nested_unit.id() == derived_unit.id(): + # same_unit_nested_cost_item.add(related_object) + # else: + # derived_unit = None + # break + # for cost in same_unit_nested_cost_item: + # qto = ifcopenshell.util.cost.get_total_quantity(cost) + # derived_quantity += qto + # print(derived_quantity) + # if derived_quantity not in [0, None]: + # data["DerivedTotalCostQuantity"] = derived_quantity + # if derived_unit: + # data["DerivedUnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(derived_unit) + # else: + # data["DerivedUnitSymbol"] = "?" + # print("Total Cost", data["DerivedTotalCostQuantity"], cost_item.Name) + @classmethod def _get_object_quantities(cls, cost_item, element): if not element.is_a("IfcObject"): @@ -236,6 +260,7 @@ class CostSchedulesData: break return results + class CostItemRatesData: data = {} is_loaded = False diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index fbff8b4ed5..3403e46456 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -23,7 +23,6 @@ import time import ifcopenshell.api import blenderbim.bim.helper import blenderbim.tool as tool -from blenderbim.bim.ifc import IfcStore from bpy_extras.io_utils import ImportHelper import blenderbim.tool as tool import blenderbim.core.cost as core @@ -36,7 +35,7 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Add a new cost schedule" def _execute(self, context): - core.add_cost_schedule(tool.Ifc, predefined_type = context.scene.BIMCostProperties.cost_schedule_predefined_types) + core.add_cost_schedule(tool.Ifc, predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types) class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator): @@ -685,6 +684,25 @@ class HighlightProductCostItem(bpy.types.Operator): return {"FINISHED"} +class ReorderCostItem(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.reorder_cost_item_nesting" + bl_label = "Reorder Nesting" + bl_options = {"REGISTER", "UNDO"} + new_index: bpy.props.IntProperty() + cost_item: bpy.props.IntProperty() + + def _execute(self, context): + ifcopenshell.api.run( + "nest.reorder_nesting", + tool.Ifc.get(), + **{ + "item": tool.Ifc.get().by_id(self.cost_item), + "new_index": self.new_index, + }, + ) + tool.Cost.load_cost_schedule_tree() + + class SelectUnassignedProducts(bpy.types.Operator): bl_idname = "bim.select_unassigned_products" bl_label = "Select Unassigned Products" @@ -693,3 +711,16 @@ class SelectUnassignedProducts(bpy.types.Operator): def execute(self, context): core.select_unassigned_products(tool.Ifc, tool.Cost, tool.Spatial) return {"FINISHED"} + + +class ChangeParentCostItem(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.change_parent_cost_item" + bl_label = "Change Parent Cost Item" + bl_options = {"REGISTER", "UNDO"} + new_parent: bpy.props.IntProperty() + + def _execute(self, context): + r = core.change_parent_cost_item(tool.Ifc, tool.Cost, new_parent=tool.Ifc.get().by_id(self.new_parent)) + if isinstance(r, str): + self.report({"WARNING"}, r) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index 94520e1146..ec88a54d39 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -134,6 +134,12 @@ class CostItemType(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") + +def update_cost_item_parent(self, context): + cost_item = tool.Cost.get_highlighted_cost_item() + tool.Cost.toggle_cost_item_parent(cost_item=cost_item) + + def update_active_cost_item_elements(self, context): bpy.ops.bim.load_cost_item_element_quantities() @@ -200,8 +206,10 @@ class BIMCostProperties(PropertyGroup): contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]") product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItem) active_product_cost_item_index: IntProperty(name="Active Product Cost Item Index") + enable_reorder: BoolProperty(name="Enable Reorder", default=False) show_nested_elements: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_elements) show_nested_tasks: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_tasks) show_nested_resources: BoolProperty( name="Show Nested Tasks", default=False, update=update_active_cost_item_resources ) + change_cost_item_parent: BoolProperty(name="Change Cost Item Parent", default=False, update=update_cost_item_parent) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 19ef2fb75f..545a39eb59 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -105,7 +105,8 @@ class BIM_PT_cost_schedules(Panel): if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items): ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id if ifc_definition_id: - + row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED") + row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA") if not CostSchedulesData.data["is_editing_rates"]: op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES") op.cost_item = ifc_definition_id @@ -549,8 +550,23 @@ class BIM_UL_cost_items_trait: split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-"))) self.draw_total_cost_column(split2, cost_item) + if self.props.enable_reorder: + self.draw_order_operator(row, item.ifc_definition_id, cost_item) + + if self.props.change_cost_item_parent: + self.draw_parent_operator(row, item.ifc_definition_id) + # TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX + def draw_parent_operator(self, row, cost_item_id): + if self.props.active_cost_item_id: + if self.props.active_cost_item_id != cost_item_id: + op = row.operator( + "bim.change_parent_cost_item", text="", icon="LINKED", emboss=False + ).new_parent = cost_item_id + else: + row.label(text="", icon="BLANK1") + def draw_hierarchy(self, row, item): for i in range(0, item.level_index): row.label(text="", icon="BLANK1") @@ -581,6 +597,17 @@ class BIM_UL_cost_items_trait: def draw_uom_column(self, layout, cost_item): layout.label(text=cost_item["UnitBasisUnitSymbol"] or "?" if cost_item["UnitBasisValueComponent"] else "-") + def draw_order_operator(self, row, ifc_definition_id, cost_item): + if cost_item["NestingIndex"] is not None: + if cost_item["NestingIndex"] == 0: + op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_DOWN", text="") + op.cost_item = ifc_definition_id + op.new_index = cost_item["NestingIndex"] + 1 + elif cost_item["NestingIndex"] > 0: + op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_UP", text="") + op.cost_item = ifc_definition_id + op.new_index = cost_item["NestingIndex"] - 1 + def draw_total_quantity_column(self, layout, cost_item): layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '?'}") diff --git a/src/blenderbim/blenderbim/core/cost.py b/src/blenderbim/blenderbim/core/cost.py index 9659bb30a0..3b8822c84a 100644 --- a/src/blenderbim/blenderbim/core/cost.py +++ b/src/blenderbim/blenderbim/core/cost.py @@ -19,7 +19,6 @@ def remove_cost_schedule(ifc, cost_schedule): def enable_editing_cost_schedule_attributes(cost, cost_schedule): cost.load_cost_schedule_attributes(cost_schedule) cost.enable_editing_cost_schedule_attributes(cost_schedule) - cost.play_sound() def enable_editing_cost_items(cost, cost_schedule): cost.enable_editing_cost_items(cost_schedule) @@ -258,3 +257,15 @@ def highlight_product_cost_item(spatial, cost, cost_item): cost.highlight_cost_item(cost_item) else: return "Cost schedule is not active" + +def toggle_cost_item_parent(cost, cost_item): + cost.toggle_cost_item_parent(cost_item) + +def change_parent_cost_item(ifc, cost, new_parent): + cost_item = cost.get_active_cost_item() + if cost_item and cost.is_root_cost_item(cost_item): + return "Cannot change root cost item" + if cost_item : + ifc.run("nest.change_nest", item=cost_item, new_parent=new_parent) + cost.disable_editing_cost_item_parent() + cost.load_cost_schedule_tree() \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index 02c5c3bdcf..e6bee05b4f 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -139,6 +139,7 @@ class Cost(blenderbim.core.tool.Cost): @classmethod def disable_editing_cost_item(cls): bpy.context.scene.BIMCostProperties.active_cost_item_id = 0 + bpy.context.scene.BIMCostProperties.change_cost_item_parent = False @classmethod def get_cost_item_attributes(cls): @@ -612,3 +613,20 @@ class Cost(blenderbim.core.tool.Cost): if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"): return True + @classmethod + def toggle_cost_item_parent(cls, cost_item=None): + props = bpy.context.scene.BIMCostProperties + if props.change_cost_item_parent: + props.active_cost_item_id = cost_item.id() + props.cost_item_editing_type = "PARENT" + else: + cls.disable_editing_cost_item_parent() + + @classmethod + def change_parent_cost_item(cls, cost_item, new_parent): + ifcopenshell.api.run("nest.change_nest", tool.Ifc.get(), item=cost_item, new_parent=new_parent) + + @classmethod + def disable_editing_cost_item_parent(cls): + bpy.context.scene.BIMCostProperties.active_cost_item_id = 0 + bpy.context.scene.BIMCostProperties.change_cost_item_parent = False