diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 9a617b481d..405d22d20c 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -1,4 +1,3 @@ - # BlenderBIM Add-on - OpenBIM Blender Add-on # Copyright (C) 2020, 2021 Dion Moult # @@ -610,6 +609,7 @@ class RemoveCostItemValue(bpy.types.Operator): bl_idname = "bim.remove_cost_item_value" bl_label = "Add Cost Item Value" bl_options = {"REGISTER", "UNDO"} + parent: bpy.props.IntProperty() cost_value: bpy.props.IntProperty() def execute(self, context): @@ -617,7 +617,12 @@ class RemoveCostItemValue(bpy.types.Operator): def _execute(self, context): self.file = IfcStore.get_file() - ifcopenshell.api.run("cost.remove_cost_item_value", self.file, cost_value=self.file.by_id(self.cost_value)) + ifcopenshell.api.run( + "cost.remove_cost_item_value", + self.file, + parent=self.file.by_id(self.parent), + cost_value=self.file.by_id(self.cost_value), + ) Data.load(self.file) return {"FINISHED"} @@ -660,6 +665,7 @@ class EnableEditingCostItemValue(bpy.types.Operator): prop.float_value = data["UnitBasis"]["ValueComponent"] or 0 else: prop.float_value = 0 + prop = self.props.cost_value_attributes.add() prop.name = "UnitBasisUnit" prop.data_type = "enum" @@ -897,9 +903,9 @@ class LoadCostItemTypes(bpy.types.Operator): while len(self.props.cost_item_type_products) > 0: self.props.cost_item_type_products.remove(0) # TODO implement process and resource types - #while len(self.props.cost_item_processes) > 0: + # while len(self.props.cost_item_processes) > 0: # self.props.cost_item_processes.remove(0) - #while len(self.props.cost_item_resources) > 0: + # while len(self.props.cost_item_resources) > 0: # self.props.cost_item_resources.remove(0) ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items(): @@ -907,9 +913,9 @@ class LoadCostItemTypes(bpy.types.Operator): if related_object.is_a("IfcTypeProduct"): new = self.props.cost_item_type_products.add() # TODO implement process and resource types - #elif related_object.is_a("IfcProcess"): + # elif related_object.is_a("IfcProcess"): # new = self.props.cost_item_processes.add() - #elif related_object.is_a("IfcResource"): + # elif related_object.is_a("IfcResource"): # new = self.props.cost_item_resources.add() new.ifc_definition_id = control_id new.name = related_object.Name or "Unnamed" @@ -920,11 +926,21 @@ class AssignCostValue(bpy.types.Operator): bl_idname = "bim.assign_cost_value" bl_label = "Assign Cost Value" bl_options = {"REGISTER", "UNDO"} + cost_item: bpy.props.IntProperty() + cost_rate: bpy.props.IntProperty() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): + self.file = IfcStore.get_file() + ifcopenshell.api.run( + "cost.assign_cost_value", + self.file, + cost_item=self.file.by_id(self.cost_item), + cost_rate=self.file.by_id(self.cost_rate), + ) + Data.load(self.file) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index 82880f430a..00a5ef18af 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -160,7 +160,10 @@ def getResourceQuantityNames(self, context): def update_cost_item_index(self, context): - bpy.ops.bim.load_cost_item_quantities() + if Data.cost_schedules[self.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + bpy.ops.bim.load_cost_item_types() + else: + bpy.ops.bim.load_cost_item_quantities() def updateCostItemIdentification(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index cc88f37e4d..2b9626fc0f 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -21,6 +21,7 @@ from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes from ifcopenshell.api.cost.data import Data +from ifcopenshell.api.unit.data import Data as UnitData class BIM_PT_cost_schedules(Panel): @@ -42,6 +43,9 @@ class BIM_PT_cost_schedules(Panel): if not Data.is_loaded: Data.load(IfcStore.get_file()) + if not UnitData.is_loaded: + UnitData.load(IfcStore.get_file()) + row = self.layout.row() row.operator("bim.add_cost_schedule", icon="ADD") @@ -193,19 +197,20 @@ class BIM_PT_cost_schedules(Panel): self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_item_value_id]) def draw_readonly_cost_value_ui(self, layout, cost_value_id): + # This UI is really poor. Delete and start again. cost_value = Data.cost_values[cost_value_id] cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"]) if cost_value["Category"]: cost_value_label += " ({})".format(cost_value["Category"]) layout.label(text="", icon="DISC") - self.draw_cost_value_operator_ui(layout, cost_value_id) + self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_cost_item_id) layout.label(text=cost_value_label) for component_id in cost_value["Components"] or []: - self.draw_readonly_component_cost_value_ui(layout, component_id) + self.draw_readonly_component_cost_value_ui(layout, component_id, cost_value["id"]) - def draw_readonly_component_cost_value_ui(self, layout, cost_value_id, level=1): - self.draw_cost_value_operator_ui(layout, cost_value_id) + def draw_readonly_component_cost_value_ui(self, layout, cost_value_id, parent_id, level=1): + self.draw_cost_value_operator_ui(layout, cost_value_id, parent_id) cost_value = Data.cost_values[cost_value_id] cost_value_label = ">" * level cost_value_label += "{0:.2f}".format(cost_value["AppliedValue"]) @@ -214,9 +219,9 @@ class BIM_PT_cost_schedules(Panel): layout.label(text=cost_value_label) for component_id in cost_value["Components"] or []: - self.draw_readonly_component_cost_value_ui(layout, component_id, level + 1) + self.draw_readonly_component_cost_value_ui(layout, component_id, cost_value["id"], level + 1) - def draw_cost_value_operator_ui(self, layout, cost_value_id): + def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id): if self.props.active_cost_item_value_id and self.props.active_cost_item_value_id == cost_value_id: op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK") op.cost_value = cost_value_id @@ -233,6 +238,7 @@ class BIM_PT_cost_schedules(Panel): if self.props.cost_types == "CATEGORY": op.cost_category = self.props.cost_category op = layout.operator("bim.remove_cost_item_value", text="", icon="X") + op.parent = parent_id op.cost_value = cost_value_id else: op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL") @@ -243,6 +249,7 @@ class BIM_PT_cost_schedules(Panel): if self.props.cost_types == "CATEGORY": op.cost_category = self.props.cost_category op = layout.operator("bim.remove_cost_item_value", text="", icon="X") + op.parent = parent_id op.cost_value = cost_value_id def draw_editable_cost_value_ui(self, layout, cost_value): @@ -467,7 +474,9 @@ class BIM_PT_cost_item_rates(Panel): 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") + op = row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") + op.cost_item = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id + op.cost_rate = self.props.cost_item_rates[self.props.active_cost_item_rate_index].ifc_definition_id self.layout.template_list( "BIM_UL_cost_item_rates", "", @@ -493,10 +502,7 @@ class BIM_UL_cost_items_trait(): split2.alignment = "RIGHT" split2.prop(item, "name", emboss=False, text="") - if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": - self.draw_uom_column(split2, cost_item) - else: - self.draw_quantity_column(split2, cost_item) + self.draw_quantity_column(split2, cost_item) split2.label(text="{0:.2f}".format(cost_item["TotalAppliedValue"])) @@ -518,6 +524,12 @@ class BIM_UL_cost_items_trait(): else: row.label(text="", icon="DOT") + def draw_quantity_column(self, layout, cost_item): + if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + self.draw_uom_column(layout, cost_item) + else: + self.draw_total_quantity_column(layout, cost_item) + def draw_uom_column(self, layout, cost_item): text = "-" if cost_item["CostValues"]: @@ -526,7 +538,7 @@ class BIM_UL_cost_items_trait(): text = "{0:.2f}".format(unit_basis["ValueComponent"]) + f" ({unit_basis['UnitSymbol'] or '?'})" layout.label(text=text) - def draw_quantity_column(self, layout, cost_item): + def draw_total_quantity_column(self, layout, cost_item): layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" ({cost_item['UnitSymbol'] or '?'})") @@ -544,6 +556,9 @@ class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList): self.contract_operator = "bim.contract_cost_item_rate" self.expand_operator = "bim.expand_cost_item_rate" + def draw_quantity_column(self, layout, cost_item): + self.draw_uom_column(layout, cost_item) + class BIM_UL_cost_columns(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_value.py new file mode 100644 index 0000000000..af3aab55ba --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_value.py @@ -0,0 +1,17 @@ +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"cost_item": None, "cost_rate": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for cost_value in self.settings["cost_item"].CostValues: + ifcopenshell.api.run( + "cost.remove_cost_item_value", self.file, parent=self.settings["cost_item"], cost_value=cost_value + ) + # This is an assumption, and not part of the official IFC documentation + self.settings["cost_item"].CostValues = self.settings["cost_rate"].CostValues diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py index 05d166790c..58eb0b9675 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/remove_cost_item_value.py @@ -1,9 +1,19 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"cost_value": None} + self.settings = {"parent": None, "cost_value": None} for key, value in settings.items(): self.settings[key] = value def execute(self): - self.file.remove(self.settings["cost_value"]) + if len(self.file.get_inverse(self.settings["cost_value"])) == 1: + self.file.remove(self.settings["cost_value"]) + # TODO deep purge + elif self.settings["parent"].is_a("IfcCostItem"): + values = list(self.settings["parent"].CostValues) + values.remove(self.settings["cost_value"]) + self.settings["parent"].CostValues = values if values else None + elif self.settings["parent"].is_a("IfcCostValue"): + components = list(self.settings["parent"].Components) + components.remove(self.settings["cost_value"]) + self.settings["parent"].Components = components if components else None