From 899a0c6fb594174bd692d52bef98a2b74e89c569 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 17 Aug 2021 11:01:41 +1000 Subject: [PATCH] You can now browse a schedule of rates alongside a cost schedule --- .../blenderbim/bim/module/cost/__init__.py | 6 ++ .../blenderbim/bim/module/cost/operator.py | 85 +++++++++++++++++- .../blenderbim/bim/module/cost/prop.py | 28 +++++- .../blenderbim/bim/module/cost/ui.py | 88 ++++++++++++++----- 4 files changed, 178 insertions(+), 29 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index c4893f0c7f..c768025826 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -59,6 +59,10 @@ classes = ( operator.ImportCostScheduleCsv, operator.LoadCostItemQuantities, operator.LoadCostItemTypes, + operator.AssignCostValue, + operator.LoadScheduleOfRates, + operator.ExpandCostItemRate, + operator.ContractCostItemRate, prop.CostItem, prop.CostItemQuantity, prop.CostItemType, @@ -66,10 +70,12 @@ classes = ( ui.BIM_PT_cost_schedules, ui.BIM_PT_cost_item_quantities, ui.BIM_PT_cost_item_types, + ui.BIM_PT_cost_item_rates, ui.BIM_UL_cost_items, ui.BIM_UL_cost_columns, ui.BIM_UL_cost_item_quantities, ui.BIM_UL_cost_item_types, + ui.BIM_UL_cost_item_rates, ) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index d467bf4e7f..9a617b481d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -62,6 +62,7 @@ class EditCostSchedule(bpy.types.Operator): **{"cost_schedule": self.file.by_id(props.active_cost_schedule_id), "attributes": attributes}, ) Data.load(IfcStore.get_file()) + purge() bpy.ops.bim.disable_editing_cost_schedule() return {"FINISHED"} @@ -124,8 +125,7 @@ class EnableEditingCostItems(bpy.types.Operator): self.props = context.scene.BIMCostProperties self.props.is_cost_update_enabled = False self.props.active_cost_schedule_id = self.cost_schedule - while len(self.props.cost_items) > 0: - self.props.cost_items.remove(0) + self.props.cost_items.clear() self.contracted_cost_items = json.loads(self.props.contracted_cost_items) for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]: @@ -164,8 +164,6 @@ class EnableEditingCostItems(bpy.types.Operator): for related_object_id in cost_item["IsNestedBy"]: self.create_new_cost_item_li(related_object_id, level_index + 1) - return {"FINISHED"} - class DisableEditingCostSchedule(bpy.types.Operator): bl_idname = "bim.disable_editing_cost_schedule" @@ -808,6 +806,9 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False) def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): from ifc5d.csv2ifc import Csv2Ifc self.file = IfcStore.get_file() @@ -819,6 +820,7 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): csv2ifc.execute() Data.load(IfcStore.get_file()) UnitData.load(IfcStore.get_file()) + purge() print("Import finished in {:.2f} seconds".format(time.time() - start)) return {"FINISHED"} @@ -912,3 +914,78 @@ class LoadCostItemTypes(bpy.types.Operator): new.ifc_definition_id = control_id new.name = related_object.Name or "Unnamed" return {"FINISHED"} + + +class AssignCostValue(bpy.types.Operator): + bl_idname = "bim.assign_cost_value" + bl_label = "Assign Cost Value" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + return IfcStore.execute_ifc_operator(self, context) + + def _execute(self, context): + return {"FINISHED"} + + +class LoadScheduleOfRates(bpy.types.Operator): + bl_idname = "bim.load_schedule_of_rates" + bl_label = "Load Schedule of Rates" + bl_options = {"REGISTER", "UNDO"} + cost_schedule: bpy.props.IntProperty() + + def execute(self, context): + self.props = context.scene.BIMCostProperties + self.props.is_cost_update_enabled = False + self.props.cost_item_rates.clear() + self.contracted_cost_item_rates = json.loads(self.props.contracted_cost_item_rates) + for related_object_id in Data.cost_schedules[self.cost_schedule]["Controls"]: + self.create_new_cost_item_li(related_object_id, 0) + self.props.is_cost_update_enabled = True + return {"FINISHED"} + + def create_new_cost_item_li(self, related_object_id, level_index): + cost_item = Data.cost_items[related_object_id] + new = self.props.cost_item_rates.add() + new.ifc_definition_id = related_object_id + new.name = cost_item["Name"] or "Unnamed" + new.identification = cost_item["Identification"] or "XXX" + new.is_expanded = related_object_id not in self.contracted_cost_item_rates + new.level_index = level_index + if cost_item["IsNestedBy"]: + new.has_children = True + if new.is_expanded: + for related_object_id in cost_item["IsNestedBy"]: + self.create_new_cost_item_li(related_object_id, level_index + 1) + + +class ExpandCostItemRate(bpy.types.Operator): + bl_idname = "bim.expand_cost_item_rate" + bl_label = "Expand Cost Item Rate" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMCostProperties + self.file = IfcStore.get_file() + contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) + contracted_cost_item_rates.remove(self.cost_item) + props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) + bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates)) + return {"FINISHED"} + + +class ContractCostItemRate(bpy.types.Operator): + bl_idname = "bim.contract_cost_item_rate" + bl_label = "Contract Cost Item Rate" + bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMCostProperties + self.file = IfcStore.get_file() + contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) + contracted_cost_item_rates.append(self.cost_item) + props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) + bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates)) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index d0691be0f0..82880f430a 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -43,6 +42,7 @@ processquantitynames_enum = [] processquantitynames_id = 0 resourcequantitynames_enum = [] resourcequantitynames_id = 0 +scheduleofrates_enum = [] def purge(): @@ -53,6 +53,7 @@ def purge(): global processquantitynames_id global resourcequantitynames_enum global resourcequantitynames_id + global scheduleofrates_enum quantitytypes_enum = [] productquantitynames_enum = [] productquantitynames_count = [] @@ -60,12 +61,27 @@ def purge(): processquantitynames_id = 0 resourcequantitynames_enum = [] resourcequantitynames_id = 0 + scheduleofrates_enum = [] + + +def get_schedule_of_rates(self, context): + global scheduleofrates_enum + if len(scheduleofrates_enum) == 0: + scheduleofrates_enum.extend( + (str(ifc_definition_id), schedule["Name"] or "Unnamed", "") + for ifc_definition_id, schedule in Data.cost_schedules.items() + if schedule["PredefinedType"] == "SCHEDULEOFRATES" + ) + return scheduleofrates_enum + + +def update_schedule_of_rates(self, context): + bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(self.schedule_of_rates)) def getQuantityTypes(self, context): global quantitytypes_enum if len(quantitytypes_enum) == 0 and IfcStore.get_schema(): - quantitytypes_enum = [] quantitytypes_enum.extend( [ (t.name(), t.name(), "") @@ -238,4 +254,10 @@ class BIMCostProperties(PropertyGroup): cost_item_resources: CollectionProperty(name="Cost Item Resources", type=CostItemQuantity) active_cost_item_resource_index: IntProperty(name="Active Cost Item Resource Index") cost_item_type_products: CollectionProperty(name="Cost Item Type Products", type=CostItemType) - active_cost_item_type_product_index: IntProperty(name="Active Cost Item TYpe Product Index") + active_cost_item_type_product_index: IntProperty(name="Active Cost Item Type Product Index") + schedule_of_rates: EnumProperty( + items=get_schedule_of_rates, name="Schedule Of Rates", update=update_schedule_of_rates + ) + cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem) + active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index") + contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]") diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 4503551bfd..cc88f37e4d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -441,26 +441,51 @@ class BIM_PT_cost_item_quantities(Panel): op.prop_name = self.props.resource_quantity_names -class BIM_UL_cost_items(UIList): +class BIM_PT_cost_item_rates(Panel): + bl_label = "IFC Cost Item Rates" + bl_idname = "BIM_PT_cost_item_rates" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_cost_schedules" + + @classmethod + def poll(cls, context): + props = context.scene.BIMCostProperties + total_cost_items = len(props.cost_items) + if not props.active_cost_schedule_id: + return False + if Data.cost_schedules[props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + return False + if total_cost_items > 0 and props.active_cost_item_index < total_cost_items: + return True + return False + + def draw(self, context): + self.props = context.scene.BIMCostProperties + cost_item = self.props.cost_items[self.props.active_cost_item_index] + row = self.layout.row(align=True) + row.prop(self.props, "schedule_of_rates", text="") + row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") + self.layout.template_list( + "BIM_UL_cost_item_rates", + "", + self.props, + "cost_item_rates", + self.props, + "active_cost_item_rate_index", + ) + + +class BIM_UL_cost_items_trait(): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): if item: self.props = context.scene.BIMCostProperties cost_item = Data.cost_items[item.ifc_definition_id] row = layout.row(align=True) - for i in range(0, item.level_index): - row.label(text="", icon="BLANK1") - if item.has_children: - if item.is_expanded: - row.operator( - "bim.contract_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" - ).cost_item = item.ifc_definition_id - else: - row.operator( - "bim.expand_cost_item", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" - ).cost_item = item.ifc_definition_id - else: - row.label(text="", icon="DOT") + self.draw_hierarchy(row, item) split1 = row.split(factor=0.1) split1.prop(item, "identification", emboss=False, text="") @@ -479,7 +504,19 @@ class BIM_UL_cost_items(UIList): split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-"))) split2.label(text="{0:.2f}".format(cost_item["TotalCostValue"])) - self.draw_buttons(split2, item, cost_item) + # TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX + + def draw_hierarchy(self, row, item): + for i in range(0, item.level_index): + row.label(text="", icon="BLANK1") + if item.has_children: + if item.is_expanded: + op = row.operator(self.contract_operator, text="", emboss=False, icon="DISCLOSURE_TRI_DOWN") + else: + op = row.operator(self.expand_operator, text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT") + op.cost_item = item.ifc_definition_id + else: + row.label(text="", icon="DOT") def draw_uom_column(self, layout, cost_item): text = "-" @@ -492,13 +529,20 @@ class BIM_UL_cost_items(UIList): def draw_quantity_column(self, layout, cost_item): layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})") - def draw_buttons(self, row, item, cost_item): - pass # TODO: reimplement somewhere with better UX - # elif self.props.active_cost_item_id: - # if self.props.cost_item_editing_type == "VALUES": - # op = row.operator("bim.copy_cost_item_values", text="", icon="COPYDOWN") - # op.source = self.props.active_cost_item_id - # op.destination = item.ifc_definition_id + +class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList): + def __init__(self): + self.contract_operator = "bim.contract_cost_item" + self.expand_operator = "bim.expand_cost_item" + + +class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList): + # A schedule of rates UIList is identical to a regular cost items UIList but + # we want a separate UIList instance so that you can browse both lists + # independently in Blender. So we use a trait. + def __init__(self): + self.contract_operator = "bim.contract_cost_item_rate" + self.expand_operator = "bim.expand_cost_item_rate" class BIM_UL_cost_columns(UIList):