From b92e3fcf01ae068165ffacd5e4897b65defd7672 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 12 Aug 2021 13:29:35 +1000 Subject: [PATCH] Implement task-based parametric cost item quantities --- .../blenderbim/bim/module/cost/__init__.py | 8 +- .../blenderbim/bim/module/cost/operator.py | 63 ++++++---- .../blenderbim/bim/module/cost/prop.py | 67 +++++++--- .../blenderbim/bim/module/cost/ui.py | 116 ++++++++++-------- src/blenderbim/blenderbim/bim/schema.py | 2 +- ...roduct.py => assign_cost_item_quantity.py} | 0 ...duct.py => unassign_cost_item_quantity.py} | 0 7 files changed, 158 insertions(+), 98 deletions(-) rename src/ifcopenshell-python/ifcopenshell/api/cost/{assign_cost_item_product.py => assign_cost_item_quantity.py} (100%) rename src/ifcopenshell-python/ifcopenshell/api/cost/{unassign_cost_item_product.py => unassign_cost_item_quantity.py} (100%) diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index 2ac4914181..fa370240d8 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -26,8 +26,8 @@ classes = ( operator.ExpandCostItem, operator.ContractCostItem, operator.RemoveCostItem, - operator.AssignCostItemProduct, - operator.UnassignCostItemProduct, + operator.AssignCostItemQuantity, + operator.UnassignCostItemQuantity, operator.AddCostItemQuantity, operator.RemoveCostItemQuantity, operator.AddCostValue, @@ -38,13 +38,13 @@ classes = ( operator.ImportCostScheduleCsv, operator.LoadCostItemQuantities, prop.CostItem, - prop.CostItemProduct, + prop.CostItemQuantity, prop.BIMCostProperties, ui.BIM_PT_cost_schedules, ui.BIM_PT_cost_item_quantities, ui.BIM_UL_cost_items, ui.BIM_UL_cost_columns, - ui.BIM_UL_cost_item_products, + ui.BIM_UL_cost_item_quantities, ) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index fc92f99292..0e124d81f8 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -297,41 +297,48 @@ class EditCostItem(bpy.types.Operator): return {"FINISHED"} -class AssignCostItemProduct(bpy.types.Operator): - bl_idname = "bim.assign_cost_item_product" - bl_label = "Assign Control" +class AssignCostItemQuantity(bpy.types.Operator): + bl_idname = "bim.assign_cost_item_quantity" + bl_label = "Assign Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - related_object: bpy.props.StringProperty() + related_object_type: bpy.props.StringProperty() + prop_name: bpy.props.StringProperty() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - related_objects = ( - [bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects - ) self.file = IfcStore.get_file() - self.props = context.scene.BIMCostProperties + if self.related_object_type == "PRODUCT": + products = [ + self.file.by_id(o.BIMObjectProperties.ifc_definition_id) + for o in context.selected_objects + if o.BIMObjectProperties.ifc_definition_id + ] + elif self.related_object_type == "PROCESS": + products = [ + self.file.by_id( + context.scene.BIMTaskTreeProperties.tasks[ + context.scene.BIMWorkScheduleProperties.active_task_index + ].ifc_definition_id + ) + ] ifcopenshell.api.run( - "cost.assign_cost_item_product", + "cost.assign_cost_item_quantity", self.file, cost_item=self.file.by_id(self.cost_item), - products=[ - self.file.by_id(o.BIMObjectProperties.ifc_definition_id) - for o in related_objects - if o.BIMObjectProperties.ifc_definition_id - ], - prop_name=self.props.quantity_names, + products=products, + prop_name=self.prop_name, ) Data.load(self.file) bpy.ops.bim.load_cost_item_quantities() return {"FINISHED"} -class UnassignCostItemProduct(bpy.types.Operator): - bl_idname = "bim.unassign_cost_item_product" - bl_label = "Unassign Control" +class UnassignCostItemQuantity(bpy.types.Operator): + bl_idname = "bim.unassign_cost_item_quantity" + bl_label = "Unassign Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() related_object: bpy.props.IntProperty() @@ -350,7 +357,7 @@ class UnassignCostItemProduct(bpy.types.Operator): if o.BIMObjectProperties.ifc_definition_id ] ifcopenshell.api.run( - "cost.unassign_cost_item_product", + "cost.unassign_cost_item_quantity", self.file, cost_item=self.file.by_id(self.cost_item), products=products, @@ -708,19 +715,21 @@ class LoadCostItemQuantities(bpy.types.Operator): self.file = IfcStore.get_file() while len(self.props.cost_item_products) > 0: self.props.cost_item_products.remove(0) + while len(self.props.cost_item_processes) > 0: + self.props.cost_item_processes.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(): related_object = self.file.by_id(control_id) if related_object.is_a("IfcProduct"): new = self.props.cost_item_products.add() - new.ifc_definition_id = control_id - new.name = related_object.Name or "Unnamed" - total_quantity = 0 - for quantity_id in quantity_ids: - total_quantity += self.file.by_id(quantity_id)[3] - new.total_quantity = total_quantity - elif related_object.is_a("IfcProduct"): - pass + elif related_object.is_a("IfcProcess"): + new = self.props.cost_item_processes.add() elif related_object.is_a("IfcResource"): pass + new.ifc_definition_id = control_id + new.name = related_object.Name or "Unnamed" + total_quantity = 0 + for quantity_id in quantity_ids: + total_quantity += self.file.by_id(quantity_id)[3] + new.total_quantity = total_quantity return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index ff43627408..2eda29124d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -18,17 +18,23 @@ from bpy.props import ( quantitytypes_enum = [] -quantitynames_enum = [] -quantitynames_count = [] +productquantitynames_enum = [] +productquantitynames_count = [] +processquantitynames_enum = [] +processquantitynames_id = 0 def purge(): global quantitytypes_enum - global quantitynames_enum - global quantitynames_count + global productquantitynames_enum + global productquantitynames_count + global processquantitynames_enum + global processquantitynames_id quantitytypes_enum = [] - quantitynames_enum = [] - quantitynames_count = [] + productquantitynames_enum = [] + productquantitynames_count = [] + processquantitynames_enum = [] + processquantitynames_id = 0 def getQuantityTypes(self, context): @@ -44,14 +50,14 @@ def getQuantityTypes(self, context): return quantitytypes_enum -def getQuantityNames(self, context): - global quantitynames_enum - global quantitynames_count +def getProductQuantityNames(self, context): + global productquantitynames_enum + global productquantitynames_count ifc_file = IfcStore.get_file() total_selected_objects = len(context.selected_objects) - if total_selected_objects != quantitynames_count or total_selected_objects == 1: - quantitynames_enum = [] - quantitynames_count = total_selected_objects + if total_selected_objects != productquantitynames_count or total_selected_objects == 1: + productquantitynames_enum = [] + productquantitynames_count = total_selected_objects names = set() for obj in context.selected_objects: element_id = obj.BIMObjectProperties.ifc_definition_id @@ -64,8 +70,32 @@ def getQuantityNames(self, context): qto = PsetData.qtos[qto_id] [potential_names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]] names = names.intersection(potential_names) if names else potential_names - quantitynames_enum.extend([(n, n, "") for n in names]) - return quantitynames_enum + productquantitynames_enum.extend([(n, n, "") for n in names]) + return productquantitynames_enum + + +def getProcessQuantityNames(self, context): + global processquantitynames_enum + global processquantitynames_id + ifc_file = IfcStore.get_file() + active_task_index = context.scene.BIMWorkScheduleProperties.active_task_index + if not active_task_index: + return [] + try: + ifc_definition_id = context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id + except: + return [] + if processquantitynames_id != ifc_definition_id: + processquantitynames_enum = [] + processquantitynames_id = ifc_definition_id + names = set() + if ifc_definition_id not in PsetData.products: + PsetData.load(ifc_file, ifc_definition_id) + for qto_id in PsetData.products[ifc_definition_id]["qtos"]: + qto = PsetData.qtos[qto_id] + [names.add(PsetData.properties[p]["Name"]) for p in qto["Properties"]] + processquantitynames_enum.extend([(n, n, "") for n in names]) + return processquantitynames_enum def update_cost_item_index(self, context): @@ -96,7 +126,7 @@ class CostItem(PropertyGroup): level_index: IntProperty(name="Level Index") -class CostItemProduct(PropertyGroup): +class CostItemQuantity(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") total_quantity: FloatProperty(name="Total Quantity") @@ -113,7 +143,8 @@ class BIMCostProperties(PropertyGroup): cost_item_attributes: CollectionProperty(name="Task Attributes", type=Attribute) contracted_cost_items: StringProperty(name="Contracted Cost Items", default="[]") quantity_types: EnumProperty(items=getQuantityTypes, name="Quantity Types") - quantity_names: EnumProperty(items=getQuantityNames, name="Quantity Names") + product_quantity_names: EnumProperty(items=getProductQuantityNames, name="Product Quantity Names") + process_quantity_names: EnumProperty(items=getProcessQuantityNames, name="Process Quantity Names") active_cost_item_quantity_id: IntProperty(name="Active Cost Item Quantity Id") quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute) cost_types: EnumProperty( @@ -131,5 +162,7 @@ class BIMCostProperties(PropertyGroup): 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") - cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemProduct) + cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity) active_cost_item_product_index: IntProperty(name="Active Cost Item Product Index") + cost_item_processes: CollectionProperty(name="Cost Item Processes", type=CostItemQuantity) + active_cost_item_process_index: IntProperty(name="Active Cost Item Process Index") diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 46779e868e..5ed59c6901 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -1,3 +1,4 @@ +import blenderbim.bim.module.cost.prop as CostProp from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.cost.data import Data @@ -108,8 +109,6 @@ class BIM_PT_cost_schedules(Panel): def draw_editable_cost_item_quantities_ui(self): row = self.layout.row(align=True) row.prop(self.props, "quantity_types", text="") - if self.props.quantity_types == "QTO": - row.prop(self.props, "quantity_names", text="") op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD") op.cost_item = self.props.active_cost_item_id op.ifc_class = self.props.quantity_types @@ -266,42 +265,19 @@ class BIM_PT_cost_item_quantities(Panel): cost_item = self.props.cost_items[self.props.active_cost_item_index] - row = self.layout.row(align=True) + grid = self.layout.grid_flow(columns=3, even_columns=True) - row2 = row.row(align=True) + # Column1 + col = grid.column() + + row2 = col.row(align=True) row2.label(text="Elements") - op = row.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") + op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") op.cost_item = cost_item.ifc_definition_id - row2 = row.row(align=True) - row2.label(text="Tasks") - op = row.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") - op.cost_item = cost_item.ifc_definition_id - - row2 = row.row(align=True) - row2.label(text="Resources") - op = row.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") - op.cost_item = cost_item.ifc_definition_id - - row = self.layout.row(align=True) - row.template_list( - "BIM_UL_cost_item_products", - "", - self.props, - "cost_item_products", - self.props, - "active_cost_item_product_index", - ) - row.template_list( - "BIM_UL_cost_item_products", - "", - self.props, - "cost_item_products", - self.props, - "active_cost_item_product_index", - ) - row.template_list( - "BIM_UL_cost_item_products", + row2 = col.row() + row2.template_list( + "BIM_UL_cost_item_quantities", "", self.props, "cost_item_products", @@ -309,23 +285,64 @@ class BIM_PT_cost_item_quantities(Panel): "active_cost_item_product_index", ) - row = self.layout.row() - - row2 = row.row(align=True) - row2.prop(self.props, "quantity_names", text="") - op = row2.operator("bim.unassign_cost_item_product", text="", icon="REMOVE") + row2 = col.row(align=True) + row2.prop(self.props, "product_quantity_names", text="") + op = row2.operator("bim.unassign_cost_item_quantity", text="", icon="REMOVE") op.cost_item = cost_item.ifc_definition_id op.related_object = 0 - op = row2.operator("bim.assign_cost_item_product", text="", icon="ADD") + if CostProp.productquantitynames_enum: + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") + op.related_object_type = "PRODUCT" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = self.props.product_quantity_names + + # Column2 + col = grid.column() + + row2 = col.row(align=True) + row2.label(text="Tasks") + op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") op.cost_item = cost_item.ifc_definition_id - row2 = row.row(align=True) - row2.prop(self.props, "quantity_names", text="") - row2.operator("bim.unassign_cost_item_product", text="", icon="ADD") + row2 = col.row() + row2.template_list( + "BIM_UL_cost_item_quantities", + "", + self.props, + "cost_item_processes", + self.props, + "active_cost_item_process_index", + ) - row2 = row.row(align=True) - row2.prop(self.props, "quantity_names", text="") - row2.operator("bim.unassign_cost_item_product", text="", icon="ADD") + row2 = col.row(align=True) + row2.prop(self.props, "process_quantity_names", text="") + if CostProp.processquantitynames_enum: + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") + op.related_object_type = "PROCESS" + op.cost_item = cost_item.ifc_definition_id + op.prop_name = self.props.process_quantity_names + + # Column3 + col = grid.column() + + row2 = col.row(align=True) + row2.label(text="Resources") + op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="") + op.cost_item = cost_item.ifc_definition_id + + row2 = col.row() + row2.template_list( + "BIM_UL_cost_item_quantities", + "", + self.props, + "cost_item_products", + self.props, + "active_cost_item_product_index", + ) + + row2 = col.row(align=True) + row2.prop(self.props, "process_quantity_names", text="") + op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD") class BIM_UL_cost_items(UIList): @@ -393,15 +410,16 @@ class BIM_UL_cost_columns(UIList): row.operator("bim.remove_cost_column", text="", icon="X").name = item.name -class BIM_UL_cost_item_products(UIList): +class BIM_UL_cost_item_quantities(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): props = context.scene.BIMCostProperties cost_item = props.cost_items[props.active_cost_item_index] if item: row = layout.row(align=True) - row.prop(item, "name", emboss=False, text="") + row.split(factor=0.8) + row.label(text=item.name) row.label(text="{0:.2f}".format(item.total_quantity)) - op = row.operator("bim.unassign_cost_item_product", text="", icon="X") + op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X") op.cost_item = cost_item.ifc_definition_id op.related_object = item.ifc_definition_id diff --git a/src/blenderbim/blenderbim/bim/schema.py b/src/blenderbim/blenderbim/bim/schema.py index 6fb784f561..0542d9ffe9 100644 --- a/src/blenderbim/blenderbim/bim/schema.py +++ b/src/blenderbim/blenderbim/bim/schema.py @@ -30,7 +30,7 @@ class IfcSchema: self.property_files = [] property_paths = self.data_dir.joinpath("pset").glob("*.ifc") # TODO: add IFC2X3 PsetQto template support - self.psetqto = ifcopenshell.util.pset.PsetQto("IFC4") + self.psetqto = ifcopenshell.util.pset.get_template("IFC4") for path in property_paths: self.psetqto.templates.append(ifcopenshell.open(path)) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product.py b/src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py similarity index 100% rename from src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_product.py rename to src/ifcopenshell-python/ifcopenshell/api/cost/assign_cost_item_quantity.py diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_product.py b/src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py similarity index 100% rename from src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_product.py rename to src/ifcopenshell-python/ifcopenshell/api/cost/unassign_cost_item_quantity.py