diff --git a/src/blenderbim/blenderbim/bim/module/cost/data.py b/src/blenderbim/blenderbim/bim/module/cost/data.py new file mode 100644 index 0000000000..f4ece5e022 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/cost/data.py @@ -0,0 +1,290 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2023 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell +import ifcopenshell.util.cost +import ifcopenshell.util.element +import blenderbim.tool as tool + + +def refresh(): + CostSchedulesData.is_loaded = False + CostItemRatesData.is_loaded = False + CostItemQuantitiesData.is_loaded = False + + +class CostSchedulesData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "schedules": cls.schedules(), + "is_editing_rates": cls.is_editing_rates(), + "cost_items": cls.cost_items(), + "cost_quantities": cls.cost_quantities(), + "cost_values": cls.cost_values(), + "quantity_types": cls.quantity_types(), + } + cls.is_loaded = True + + @classmethod + def schedules(cls): + results = [] + if bpy.context.scene.BIMCostProperties.active_cost_schedule_id: + schedule = tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id) + results.append( + { + "id": schedule.id(), + "name": schedule.Name or "Unnamed", + } + ) + else: + for schedule in tool.Ifc.get().by_type("IfcCostSchedule"): + results.append( + { + "id": schedule.id(), + "name": schedule.Name or "Unnamed", + } + ) + return results + + @classmethod + def is_editing_rates(cls): + ifc_id = bpy.context.scene.BIMCostProperties.active_cost_schedule_id + if not ifc_id: + return + return tool.Ifc.get().by_id(ifc_id).PredefinedType == "SCHEDULEOFRATES" + + @classmethod + def cost_items(cls): + cls._cost_values = {} + results = {} + for cost_item in tool.Ifc.get().by_type("IfcCostItem"): + data = {} + cls._load_cost_item_quantities(cost_item, data) + cls._load_cost_values(cost_item, data) + results[cost_item.id()] = data + return results + + @classmethod + def _load_cost_values(cls, root_element, data): + # data["CostValues"] = [] + data["CategoryValues"] = {} + data["UnitBasisValueComponent"] = None + data["UnitBasisUnitSymbol"] = None + data["TotalAppliedValue"] = 0.0 + data["TotalCost"] = 0.0 + if root_element.is_a("IfcCostItem"): + values = root_element.CostValues + elif root_element.is_a("IfcConstructionResource"): + values = root_element.BaseCosts + for cost_value in values or []: + cls._load_cost_value(root_element, data, cost_value) + # data["CostValues"].append(cost_value.id()) + data["TotalAppliedValue"] += cls._cost_values[cost_value.id()]["AppliedValue"] + if cost_value.UnitBasis: + cost_value_data = cls._cost_values[cost_value.id()] + data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"] + data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"] + if data["UnitBasisValueComponent"]: + data["TotalCost"] = data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"] + else: + data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] + + @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"] = [] + 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"] = "?" + 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 + + @classmethod + def _get_object_quantities(cls, cost_item, element): + if not element.is_a("IfcObject"): + return [] + results = [] + for relationship in element.IsDefinedBy: + if not relationship.is_a("IfcRelDefinesByProperties"): + continue + qto = relationship.RelatingPropertyDefinition + if not qto.is_a("IfcElementQuantity"): + continue + for prop in qto.Quantities: + if prop in cost_item.CostQuantities or []: + results.append(prop.id()) + return results + + @classmethod + def _load_cost_value(cls, root_element, root_element_data, cost_value): + value_data = cost_value.get_info() + del value_data["AppliedValue"] + if value_data["UnitBasis"]: + data = cost_value.UnitBasis.get_info() + data["ValueComponent"] = data["ValueComponent"].wrappedValue + data["UnitComponent"] = data["UnitComponent"].id() + data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent) + value_data["UnitBasis"] = data + if value_data["ApplicableDate"]: + value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"]) + if value_data["FixedUntilDate"]: + value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"]) + value_data["Components"] = [c.id() for c in value_data["Components"] or []] + value_data["AppliedValue"] = ifcopenshell.util.cost.calculate_applied_value(root_element, cost_value) + + if cost_value.Category not in [None, "*"]: + root_element_data["CategoryValues"].setdefault(cost_value.Category, 0) + root_element_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"] + + value_data["Formula"] = ifcopenshell.util.cost.serialise_cost_value(cost_value) + + cls._cost_values[cost_value.id()] = value_data + for component in cost_value.Components or []: + cls._load_cost_value(root_element, root_element_data, component) + + @classmethod + def cost_quantities(cls): + results = [] + ifc_id = bpy.context.scene.BIMCostProperties.active_cost_item_id + if not ifc_id: + return results + for quantity in tool.Ifc.get().by_id(ifc_id).CostQuantities or []: + results.append({"id": quantity.id(), "name": quantity.Name, "value": "{0:.2f}".format(quantity[3])}) + return results + + @classmethod + def cost_values(cls): + results = [] + ifc_id = bpy.context.scene.BIMCostProperties.active_cost_item_id + if not ifc_id: + return results + cost_item = tool.Ifc.get().by_id(ifc_id) + for cost_value in cost_item.CostValues or []: + label = "{0:.2f}".format(ifcopenshell.util.cost.calculate_applied_value(cost_item, cost_value)) + label += " = {}".format(ifcopenshell.util.cost.serialise_cost_value(cost_value)) + results.append({"id": cost_value.id(), "label": label}) + return results + + @classmethod + def quantity_types(cls): + return [ + (t.name(), t.name(), "") + for t in tool.Ifc.schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes() + ] + + +class CostItemRatesData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "schedule_of_rates": cls.schedule_of_rates(), + } + cls.is_loaded = True + + @classmethod + def schedule_of_rates(cls): + return [ + (str(s.id()), s.Name or "Unnamed", "") + for s in tool.Ifc.get().by_type("IfcCostSchedule") + if s.PredefinedType == "SCHEDULEOFRATES" + ] + + +class CostItemQuantitiesData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "product_quantity_names": cls.product_quantity_names(), + "process_quantity_names": cls.process_quantity_names(), + "resource_quantity_names": cls.resource_quantity_names(), + } + cls.is_loaded = True + + @classmethod + def product_quantity_names(cls): + total_selected_objects = len(bpy.context.selected_objects) + names = set() + for obj in bpy.context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + continue + potential_names = set() + qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True) + for qset, quantities in qtos.items(): + potential_names.update(quantities.keys()) + names = names.intersection(potential_names) if names else potential_names + return [(n, n, "") for n in names if n != "id"] + + @classmethod + def process_quantity_names(cls): + active_task_index = bpy.context.scene.BIMWorkScheduleProperties.active_task_index + total_tasks = len(bpy.context.scene.BIMTaskTreeProperties.tasks) + if not total_tasks or active_task_index >= total_tasks: + return [] + ifc_definition_id = bpy.context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id + element = tool.Ifc.get().by_id(ifc_definition_id) + names = set() + qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True) + for qset, quantities in qtos.items(): + names = set(quantities.keys()) + return [(n, n, "") for n in names if n != "id"] + + @classmethod + def resource_quantity_names(cls): + active_resource_index = bpy.context.scene.BIMResourceProperties.active_resource_index + total_resources = len(bpy.context.scene.BIMResourceTreeProperties.resources) + if not total_resources or active_resource_index >= total_resources: + return [] + ifc_definition_id = bpy.context.scene.BIMResourceTreeProperties.resources[active_resource_index].ifc_definition_id + element = tool.Ifc.get().by_id(ifc_definition_id) + names = set() + qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True) + for qset, quantities in qtos.items(): + names = set(quantities.keys()) + return [(n, n, "") for n in names if n != "id"] diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 0d8d0233de..2769e559a4 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -22,36 +22,26 @@ import json import time import ifcopenshell.api import blenderbim.bim.helper -from blenderbim.bim.module.cost.prop import purge +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from bpy_extras.io_utils import ImportHelper -from ifcopenshell.api.cost.data import Data -from ifcopenshell.api.unit.data import Data as UnitData -from blenderbim.bim.module.resource.data import ResourceData -class AddCostSchedule(bpy.types.Operator): +class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_cost_schedule" bl_label = "Add Cost Schedule" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): ifcopenshell.api.run("cost.add_cost_schedule", IfcStore.get_file()) - Data.load(IfcStore.get_file()) return {"FINISHED"} -class EditCostSchedule(bpy.types.Operator): +class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_cost_schedule" bl_label = "Edit Cost Schedule" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties attributes = blenderbim.bim.helper.export_attributes(props.cost_schedule_attributes) @@ -61,38 +51,32 @@ class EditCostSchedule(bpy.types.Operator): self.file, **{"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"} -class RemoveCostSchedule(bpy.types.Operator): +class RemoveCostSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_cost_schedule" bl_label = "Remove Cost Schedule" bl_options = {"REGISTER", "UNDO"} cost_schedule: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): ifcopenshell.api.run( "cost.remove_cost_schedule", IfcStore.get_file(), cost_schedule=IfcStore.get_file().by_id(self.cost_schedule), ) - Data.load(IfcStore.get_file()) return {"FINISHED"} -class EnableEditingCostSchedule(bpy.types.Operator): +class EnableEditingCostSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_schedule" bl_label = "Enable Editing Cost Schedule" bl_options = {"REGISTER", "UNDO"} cost_schedule: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties self.props.active_cost_schedule_id = self.cost_schedule self.props.cost_schedule_attributes.clear() @@ -101,24 +85,25 @@ class EnableEditingCostSchedule(bpy.types.Operator): return {"FINISHED"} def enable_editing_cost_schedule(self): - data = Data.cost_schedules[self.cost_schedule] - blenderbim.bim.helper.import_attributes( - "IfcCostSchedule", self.props.cost_schedule_attributes, data, self.import_attributes + blenderbim.bim.helper.import_attributes2( + tool.Ifc.get().by_id(self.cost_schedule), + self.props.cost_schedule_attributes, + callback=self.import_attributes, ) def import_attributes(self, name, prop, data): if name in ["SubmittedOn", "UpdateDate"]: - prop.string_value = "" if prop.is_null else data[name].isoformat() + prop.string_value = "" if prop.is_null else ifcopenshell.util.date.ifc2datetime(data[name]).isoformat() return True -class EnableEditingCostItems(bpy.types.Operator): +class EnableEditingCostItems(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_items" bl_label = "Enable Editing Cost Items" bl_options = {"REGISTER", "UNDO"} cost_schedule: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): if context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound: self.play_chaching_sound() # lol self.props = context.scene.BIMCostProperties @@ -127,8 +112,9 @@ class EnableEditingCostItems(bpy.types.Operator): 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"]: - self.create_new_cost_item_li(related_object_id, 0) + for rel in tool.Ifc.get().by_id(self.cost_schedule).Controls or []: + for cost_item in rel.RelatedObjects: + self.create_new_cost_item_li(cost_item, 0) self.props.is_editing = "COST_ITEMS" self.props.is_cost_update_enabled = True return {"FINISHED"} @@ -149,74 +135,66 @@ class EnableEditingCostItems(bpy.types.Operator): except: pass # ah well - def create_new_cost_item_li(self, related_object_id, level_index): - cost_item = Data.cost_items[related_object_id] + def create_new_cost_item_li(self, cost_item, level_index): new = self.props.cost_items.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_items + new.ifc_definition_id = cost_item.id() + new.name = cost_item.Name or "Unnamed" + new.identification = cost_item.Identification or "XXX" + new.is_expanded = cost_item.id() not in self.contracted_cost_items new.level_index = level_index - if cost_item["IsNestedBy"]: + 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) + for rel in cost_item.IsNestedBy: + for sub_cost_item in rel.RelatedObjects: + self.create_new_cost_item_li(sub_cost_item, level_index + 1) -class DisableEditingCostSchedule(bpy.types.Operator): +class DisableEditingCostSchedule(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.disable_editing_cost_schedule" bl_label = "Disable Editing Cost Schedule" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): + def _execute(self, context): context.scene.BIMCostProperties.active_cost_schedule_id = 0 return {"FINISHED"} -class AddSummaryCostItem(bpy.types.Operator): +class AddSummaryCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_summary_cost_item" bl_label = "Add Cost Item" bl_options = {"REGISTER", "UNDO"} cost_schedule: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_schedule": self.file.by_id(self.cost_schedule)}) - Data.load(self.file) bpy.ops.bim.enable_editing_cost_items(cost_schedule=self.cost_schedule) return {"FINISHED"} -class AddCostItem(bpy.types.Operator): +class AddCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_cost_item" bl_label = "Add Cost Item" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() ifcopenshell.api.run("cost.add_cost_item", self.file, **{"cost_item": self.file.by_id(self.cost_item)}) - Data.load(self.file) bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id) return {"FINISHED"} -class ExpandCostItem(bpy.types.Operator): +class ExpandCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.expand_cost_item" bl_label = "Expand Cost Item" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() contracted_cost_items = json.loads(props.contracted_cost_items) @@ -226,13 +204,13 @@ class ExpandCostItem(bpy.types.Operator): return {"FINISHED"} -class ContractCostItem(bpy.types.Operator): +class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.contract_cost_item" bl_label = "Contract Cost Item" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() contracted_cost_items = json.loads(props.contracted_cost_items) @@ -242,15 +220,12 @@ class ContractCostItem(bpy.types.Operator): return {"FINISHED"} -class RemoveCostItem(bpy.types.Operator): +class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_cost_item" bl_label = "Remove Cost item" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() @@ -263,46 +238,41 @@ class RemoveCostItem(bpy.types.Operator): if props.active_cost_item_index in contracted_cost_items: contracted_cost_items.remove(props.active_cost_item_index) props.contracted_cost_items = json.dumps(contracted_cost_items) - Data.load(self.file) bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id) return {"FINISHED"} -class EnableEditingCostItem(bpy.types.Operator): +class EnableEditingCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_item" bl_label = "Enable Editing Cost Item" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): props = context.scene.BIMCostProperties props.cost_item_attributes.clear() - data = Data.cost_items[self.cost_item] - blenderbim.bim.helper.import_attributes("IfcCostItem", props.cost_item_attributes, data) + blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.cost_item), props.cost_item_attributes) props.active_cost_item_id = self.cost_item props.cost_item_editing_type = "ATTRIBUTES" return {"FINISHED"} -class DisableEditingCostItem(bpy.types.Operator): +class DisableEditingCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.disable_editing_cost_item" bl_label = "Disable Editing Cost Item" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): + def _execute(self, context): context.scene.BIMCostProperties.active_cost_item_id = 0 return {"FINISHED"} -class EditCostItem(bpy.types.Operator): +class EditCostItem(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_cost_item" bl_label = "Edit Cost Item" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties attributes = blenderbim.bim.helper.export_attributes(props.cost_item_attributes) @@ -312,22 +282,18 @@ class EditCostItem(bpy.types.Operator): self.file, **{"cost_item": self.file.by_id(props.active_cost_item_id), "attributes": attributes}, ) - Data.load(IfcStore.get_file()) bpy.ops.bim.disable_editing_cost_item() bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id) return {"FINISHED"} -class AssignCostItemType(bpy.types.Operator): +class AssignCostItemType(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_cost_item_type" bl_label = "Assign Cost Item Type Product" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() prop_name: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): self.file = IfcStore.get_file() cost_item = self.file.by_id(self.cost_item) @@ -339,21 +305,17 @@ class AssignCostItemType(bpy.types.Operator): ifcopenshell.api.run( "control.assign_control", self.file, relating_control=cost_item, related_object=product ) - Data.load(self.file) bpy.ops.bim.load_cost_item_types() return {"FINISHED"} -class UnassignCostItemType(bpy.types.Operator): +class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unassign_cost_item_type" bl_label = "Unassign Cost Item Type" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() related_object: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): self.file = IfcStore.get_file() cost_item = self.file.by_id(self.cost_item) @@ -370,12 +332,11 @@ class UnassignCostItemType(bpy.types.Operator): ifcopenshell.api.run( "control.unassign_control", self.file, relating_control=cost_item, related_object=product ) - Data.load(self.file) bpy.ops.bim.load_cost_item_types() return {"FINISHED"} -class AssignCostItemQuantity(bpy.types.Operator): +class AssignCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_cost_item_quantity" bl_label = "Assign Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} @@ -383,20 +344,16 @@ class AssignCostItemQuantity(bpy.types.Operator): 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): - self.file = IfcStore.get_file() if self.related_object_type == "PRODUCT": products = [ - self.file.by_id(o.BIMObjectProperties.ifc_definition_id) + tool.Ifc.get().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( + tool.Ifc.get().by_id( context.scene.BIMTaskTreeProperties.tasks[ context.scene.BIMWorkScheduleProperties.active_task_index ].ifc_definition_id @@ -404,7 +361,7 @@ class AssignCostItemQuantity(bpy.types.Operator): ] elif self.related_object_type == "RESOURCE": products = [ - self.file.by_id( + tool.Ifc.get().by_id( context.scene.BIMResourceTreeProperties.resources[ context.scene.BIMResourceProperties.active_resource_index ].ifc_definition_id @@ -412,26 +369,22 @@ class AssignCostItemQuantity(bpy.types.Operator): ] ifcopenshell.api.run( "cost.assign_cost_item_quantity", - self.file, - cost_item=self.file.by_id(self.cost_item), + tool.Ifc.get(), + cost_item=tool.Ifc.get().by_id(self.cost_item), products=products, prop_name=self.prop_name, ) - Data.load(self.file) bpy.ops.bim.load_cost_item_quantities() return {"FINISHED"} -class UnassignCostItemQuantity(bpy.types.Operator): +class UnassignCostItemQuantity(bpy.types.Operator, tool.Ifc.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() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): self.file = IfcStore.get_file() if self.related_object: @@ -448,32 +401,30 @@ class UnassignCostItemQuantity(bpy.types.Operator): cost_item=self.file.by_id(self.cost_item), products=products, ) - Data.load(self.file) bpy.ops.bim.load_cost_item_quantities() return {"FINISHED"} -class EnableEditingCostItemQuantities(bpy.types.Operator): +class EnableEditingCostItemQuantities(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_item_quantities" bl_label = "Enable Editing Cost Item Quantities" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): props = context.scene.BIMCostProperties props.active_cost_item_id = self.cost_item props.cost_item_editing_type = "QUANTITIES" - purge() return {"FINISHED"} -class EnableEditingCostItemValues(bpy.types.Operator): +class EnableEditingCostItemValues(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_item_values" bl_label = "Enable Editing Cost Item Values" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): props = context.scene.BIMCostProperties props.active_cost_item_id = self.cost_item props.cost_item_editing_type = "VALUES" @@ -481,21 +432,17 @@ class EnableEditingCostItemValues(bpy.types.Operator): return {"FINISHED"} -class AddCostItemQuantity(bpy.types.Operator): +class AddCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_cost_item_quantity" bl_label = "Add Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() ifc_class: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): self.file = IfcStore.get_file() self.props = context.scene.BIMCostProperties self.add_manual_quantity() - Data.load(self.file) return {"FINISHED"} def add_manual_quantity(self): @@ -507,16 +454,13 @@ class AddCostItemQuantity(bpy.types.Operator): ) -class RemoveCostItemQuantity(bpy.types.Operator): +class RemoveCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_cost_item_quantity" bl_label = "Remove Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() physical_quantity: 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( @@ -525,45 +469,42 @@ class RemoveCostItemQuantity(bpy.types.Operator): cost_item=self.file.by_id(self.cost_item), physical_quantity=self.file.by_id(self.physical_quantity), ) - Data.load(self.file) return {"FINISHED"} -class EnableEditingCostItemQuantity(bpy.types.Operator): +class EnableEditingCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_item_quantity" bl_label = "Enable Editing Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} physical_quantity: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties self.props.quantity_attributes.clear() self.props.active_cost_item_quantity_id = self.physical_quantity - data = Data.physical_quantities[self.physical_quantity] - blenderbim.bim.helper.import_attributes(data["type"], self.props.quantity_attributes, data) + blenderbim.bim.helper.import_attributes2( + tool.Ifc.get().by_id(self.physical_quantity), self.props.quantity_attributes + ) return {"FINISHED"} -class DisableEditingCostItemQuantity(bpy.types.Operator): +class DisableEditingCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.disable_editing_cost_item_quantity" bl_label = "Disable Editing Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): + def _execute(self, context): props = context.scene.BIMCostProperties props.active_cost_item_quantity_id = 0 return {"FINISHED"} -class EditCostItemQuantity(bpy.types.Operator): +class EditCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_cost_item_quantity" bl_label = "Edit Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} physical_quantity: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties attributes = blenderbim.bim.helper.export_attributes(props.quantity_attributes) @@ -573,12 +514,11 @@ class EditCostItemQuantity(bpy.types.Operator): self.file, **{"physical_quantity": self.file.by_id(self.physical_quantity), "attributes": attributes}, ) - Data.load(IfcStore.get_file()) bpy.ops.bim.disable_editing_cost_item_quantity() return {"FINISHED"} -class AddCostValue(bpy.types.Operator): +class AddCostValue(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_cost_value" bl_label = "Add Cost Value" bl_options = {"REGISTER", "UNDO"} @@ -586,9 +526,6 @@ class AddCostValue(bpy.types.Operator): cost_type: bpy.props.StringProperty() cost_category: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): self.file = IfcStore.get_file() parent = self.file.by_id(self.parent) @@ -603,23 +540,16 @@ class AddCostValue(bpy.types.Operator): attributes = {"Category": category} value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=parent) ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes=attributes) - if parent.is_a("IfcConstructionResource"): - ResourceData.load() - else: - Data.load(self.file) return {"FINISHED"} -class RemoveCostItemValue(bpy.types.Operator): +class RemoveCostItemValue(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_cost_value" bl_label = "Add Cost Item Value" bl_options = {"REGISTER", "UNDO"} parent: bpy.props.IntProperty() cost_value: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): self.file = IfcStore.get_file() parent = self.file.by_id(self.parent) @@ -629,48 +559,43 @@ class RemoveCostItemValue(bpy.types.Operator): parent=parent, cost_value=self.file.by_id(self.cost_value), ) - if parent.is_a("IfcConstructionResource"): - ResourceData.load() - else: - Data.load(self.file) return {"FINISHED"} -class EnableEditingCostItemValue(bpy.types.Operator): +class EnableEditingCostItemValue(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_item_value" bl_label = "Enable Editing Cost Item Value" bl_options = {"REGISTER", "UNDO"} cost_value: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties self.props.cost_value_attributes.clear() self.props.active_cost_value_id = self.cost_value self.props.cost_value_editing_type = "ATTRIBUTES" - data = Data.cost_values[self.cost_value] - - blenderbim.bim.helper.import_attributes( - data["type"], - self.props.cost_value_attributes, - data, - lambda name, prop, data: self.import_attributes(name, prop, data, context), - ) + schedule = tool.Ifc.get().by_id(context.scene.BIMCostProperties.active_cost_schedule_id) + is_rates = schedule.PredefinedType == "SCHEDULEOFRATES" + cost_value = tool.Ifc.get().by_id(self.cost_value) + callback = lambda name, prop, data: self.import_attributes(name, prop, data, cost_value, is_rates) + blenderbim.bim.helper.import_attributes2(cost_value, self.props.cost_value_attributes, callback=callback) return {"FINISHED"} - def import_attributes(self, name, prop, data, context): + def import_attributes(self, name, prop, data, cost_value, is_rates): if name == "AppliedValue": # TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure) prop = self.props.cost_value_attributes.add() prop.data_type = "float" prop.name = "AppliedValue" prop.is_optional = True - prop.float_value = 0.0 if prop.is_null else data[name] + prop.float_value = ( + 0.0 + if prop.is_null + else ifcopenshell.util.cost.calculate_applied_value( + tool.Ifc.get().by_id(self.props.active_cost_item_id), cost_value + ) + ) return True - if ( - name == "UnitBasis" - and Data.cost_schedules[context.scene.BIMCostProperties.active_cost_schedule_id]["PredefinedType"] - == "SCHEDULEOFRATES" - ): + if name == "UnitBasis" and is_rates: prop = self.props.cost_value_attributes.add() prop.name = "UnitBasisValue" prop.data_type = "float" @@ -686,7 +611,7 @@ class EnableEditingCostItemValue(bpy.types.Operator): prop.data_type = "enum" prop.is_null = prop.is_optional = False units = {} - for unit_id, unit in UnitData.units.items(): + for unit in tool.Ifc.get().by_type("IfcNamedUnit"): if unit.get("UnitType", None) in [ "AREAUNIT", "LENGTHUNIT", @@ -695,55 +620,54 @@ class EnableEditingCostItemValue(bpy.types.Operator): "MASSUNIT", "USERDEFINED", ]: - if unit["type"] == "IfcContextDependentUnit": - units[unit_id] = f"{unit['UnitType']} / {unit['Name']}" + if unit.is_a("IfcContextDependentUnit"): + units[unit.id()] = f"{unit.UnitType} / {unit.Name}" else: - name = unit["Name"] + name = unit.Name if unit.get("Prefix", None): - name = f"(unit['Prefix']) {name}" - units[unit_id] = f"{unit['UnitType']} / {name}" + name = f"{unit.Prefix} {name}" + units[unit.id()] = f"{unit.UnitType} / {name}" prop.enum_items = json.dumps(units) if data["UnitBasis"] and data["UnitBasis"]["UnitComponent"]: prop.enum_value = str(data["UnitBasis"]["UnitComponent"]) return True -class DisableEditingCostItemValue(bpy.types.Operator): +class DisableEditingCostItemValue(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.disable_editing_cost_item_value" bl_label = "Disable Editing Cost Item Value" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): + def _execute(self, context): props = context.scene.BIMCostProperties props.active_cost_value_id = 0 props.cost_value_editing_type = "" return {"FINISHED"} -class EnableEditingCostItemValueFormula(bpy.types.Operator): +class EnableEditingCostItemValueFormula(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_cost_item_value_formula" bl_label = "Enable Editing Cost Item Value Formula" bl_options = {"REGISTER", "UNDO"} cost_value: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties self.props.cost_value_attributes.clear() self.props.active_cost_value_id = self.cost_value self.props.cost_value_editing_type = "FORMULA" - self.props.cost_value_formula = Data.cost_values[self.cost_value]["Formula"] + self.props.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value( + tool.Ifc.get().by_id(self.cost_value) + ) return {"FINISHED"} -class EditCostItemValueFormula(bpy.types.Operator): +class EditCostItemValueFormula(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_cost_value_formula" bl_label = "Edit Cost Value Formula" bl_options = {"REGISTER", "UNDO"} cost_value: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() @@ -752,20 +676,16 @@ class EditCostItemValueFormula(bpy.types.Operator): self.file, **{"cost_value": self.file.by_id(self.cost_value), "formula": props.cost_value_formula}, ) - Data.load(IfcStore.get_file()) bpy.ops.bim.disable_editing_cost_item_value() return {"FINISHED"} -class EditCostItemValue(bpy.types.Operator): +class EditCostItemValue(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_cost_value" bl_label = "Edit Cost Value" bl_options = {"REGISTER", "UNDO"} cost_value: bpy.props.IntProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): props = context.scene.BIMCostProperties attributes = blenderbim.bim.helper.export_attributes( @@ -777,7 +697,6 @@ class EditCostItemValue(bpy.types.Operator): self.file, **{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes}, ) - Data.load(IfcStore.get_file()) bpy.ops.bim.disable_editing_cost_item_value() return {"FINISHED"} @@ -797,16 +716,13 @@ class EditCostItemValue(bpy.types.Operator): return True -class CopyCostItemValues(bpy.types.Operator): +class CopyCostItemValues(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.copy_cost_item_values" bl_label = "Copy Cost Item Values" bl_options = {"REGISTER", "UNDO"} source: bpy.props.IntProperty() destination: 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( @@ -814,37 +730,37 @@ class CopyCostItemValues(bpy.types.Operator): self.file, **{"source": self.file.by_id(self.source), "destination": self.file.by_id(self.destination)}, ) - Data.load(IfcStore.get_file()) return {"FINISHED"} -class SelectCostItemProducts(bpy.types.Operator): +class SelectCostItemProducts(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.select_cost_item_products" bl_label = "Select Cost Item Products" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() - def execute(self, context): - self.file = IfcStore.get_file() - related_products = Data.cost_items[self.cost_item]["Controls"].keys() + def _execute(self, context): + related_objects = [] + [related_objects.extend(r.RelatedObjects) for r in tool.Ifc.get().by_id(self.cost_item).Controls or []] + related_objects = set([r.id() for r in related_objects]) for obj in context.visible_objects: obj.select_set(False) - if obj.BIMObjectProperties.ifc_definition_id in related_products: + if obj.BIMObjectProperties.ifc_definition_id in related_objects: obj.select_set(True) return {"FINISHED"} -class SelectCostScheduleProducts(bpy.types.Operator): +class SelectCostScheduleProducts(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.select_cost_schedule_products" bl_label = "Select Cost Schedule Products" bl_options = {"REGISTER", "UNDO"} cost_schedule: bpy.props.IntProperty() - def execute(self, context): + def _execute(self, context): self.file = IfcStore.get_file() self.related_products = [] - for cost_item_id in Data.cost_schedules[self.cost_schedule]["Controls"]: - self.get_related_products(Data.cost_items[cost_item_id]) + for cost_item in tool.Ifc.get().by_id(self.cost_schedule).Controls or []: + self.get_related_products(cost_item) self.related_products = set(self.related_products) for obj in context.visible_objects: obj.select_set(False) @@ -853,12 +769,13 @@ class SelectCostScheduleProducts(bpy.types.Operator): return {"FINISHED"} def get_related_products(self, cost_item): - self.related_products.extend(cost_item["Controls"].keys()) - for child_id in cost_item["IsNestedBy"]: - self.get_related_products(Data.cost_items[child_id]) + [self.related_products.extend(r.RelatedObjects) for r in cost_item.Controls or []] + for rel in cost_item.IsNestedBy or []: + for sub_cost_item in rel.RelatedObjects: + self.get_related_products(sub_cost_item) -class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): +class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator): bl_idname = "import_cost_schedule_csv.bim" bl_label = "Import Cost Schedule CSV" bl_options = {"REGISTER", "UNDO"} @@ -866,9 +783,6 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"}) 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 @@ -879,79 +793,78 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper): csv2ifc.file = self.file csv2ifc.is_schedule_of_rates = self.is_schedule_of_rates 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"} -class AddCostColumn(bpy.types.Operator): +class AddCostColumn(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_cost_column" bl_label = "Add Cost Column" bl_options = {"REGISTER", "UNDO"} name: bpy.props.StringProperty() - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties new = self.props.columns.add() new.name = self.name - Data.set_categories([c.name for c in self.props.columns]) - Data.load(IfcStore.get_file()) return {"FINISHED"} -class RemoveCostColumn(bpy.types.Operator): +class RemoveCostColumn(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_cost_column" bl_label = "Remove Cost Column" bl_options = {"REGISTER", "UNDO"} name: bpy.props.StringProperty() - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties self.props.columns.remove(self.props.columns.find(self.name)) - Data.set_categories([c.name for c in self.props.columns]) - Data.load(IfcStore.get_file()) return {"FINISHED"} -class LoadCostItemQuantities(bpy.types.Operator): +class LoadCostItemQuantities(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.load_cost_item_quantities" bl_label = "Load Cost Item Quantities" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties self.file = IfcStore.get_file() - Data.load(self.file) self.props.cost_item_products.clear() self.props.cost_item_processes.clear() self.props.cost_item_resources.clear() if 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 - for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items() or {}: - related_object = self.file.by_id(control_id) - if related_object.is_a("IfcProduct"): - new = self.props.cost_item_products.add() - elif related_object.is_a("IfcProcess"): - new = self.props.cost_item_processes.add() - 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" - total_quantity = 0 - for quantity_id in quantity_ids: - total_quantity += self.file.by_id(quantity_id)[3] - new.total_quantity = total_quantity + cost_item = tool.Ifc.get().by_id(ifc_definition_id) + # for control_id, quantity_ids in cost_item.Controls.items() or {}: + for rel in cost_item.Controls or []: + # control_id, quantity_ids + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcProduct"): + new = self.props.cost_item_products.add() + elif related_object.is_a("IfcProcess"): + new = self.props.cost_item_processes.add() + elif related_object.is_a("IfcResource"): + new = self.props.cost_item_resources.add() + new.ifc_definition_id = related_object.id() + new.name = related_object.Name or "Unnamed" + total_quantity = 0 + qtos = ifcopenshell.util.element.get_psets(related_object, qtos_only=True) + for qset_name, quantities in qtos.items(): + qto = tool.Ifc.get().by_id(quantities["id"]) + for quantity in qto.Quantities: + if quantity in cost_item.CostQuantities: + total_quantity += quantity[3] + new.total_quantity = total_quantity return {"FINISHED"} -class LoadCostItemTypes(bpy.types.Operator): +class LoadCostItemTypes(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.load_cost_item_types" bl_label = "Load Cost Item Types" bl_options = {"REGISTER", "UNDO"} - def execute(self, context): + def _execute(self, context): self.props = context.scene.BIMCostProperties self.file = IfcStore.get_file() self.props.cost_item_type_products.clear() @@ -959,30 +872,28 @@ class LoadCostItemTypes(bpy.types.Operator): # self.props.cost_item_processes.clear() # self.props.cost_item_resources.clear() 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("IfcTypeProduct"): - new = self.props.cost_item_type_products.add() - # TODO implement process and resource types - # elif related_object.is_a("IfcProcess"): - # new = self.props.cost_item_processes.add() - # 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" + cost_item = tool.Ifc.get().by_id(ifc_definition_id) + for rel in cost_item.Controls or []: + for related_object in rel.RelatedObjects: + 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"): + # new = self.props.cost_item_processes.add() + # elif related_object.is_a("IfcResource"): + # new = self.props.cost_item_resources.add() + new.ifc_definition_id = related_object.id() + new.name = related_object.Name or "Unnamed" return {"FINISHED"} -class AssignCostValue(bpy.types.Operator): +class AssignCostValue(bpy.types.Operator, tool.Ifc.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( @@ -991,48 +902,48 @@ class AssignCostValue(bpy.types.Operator): cost_item=self.file.by_id(self.cost_item), cost_rate=self.file.by_id(self.cost_rate), ) - Data.load(self.file) return {"FINISHED"} -class LoadScheduleOfRates(bpy.types.Operator): +class LoadScheduleOfRates(bpy.types.Operator, tool.Ifc.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): + 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) + for rel in tool.Ifc.get().by_id(self.cost_schedule).Controls or []: + for cost_item in rel.RelatedObjects: + self.create_new_cost_item_li(cost_item, 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] + def create_new_cost_item_li(self, cost_item, level_index): 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.ifc_definition_id = cost_item.id() + new.name = cost_item.Name or "Unnamed" + new.identification = cost_item.Identification or "XXX" + new.is_expanded = cost_item.id() not in self.contracted_cost_item_rates new.level_index = level_index - if cost_item["IsNestedBy"]: + 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) + for rel in cost_item.IsNestedBy: + for sub_cost_item in rel.RelatedObjects: + self.create_new_cost_item_li(sub_cost_item, level_index + 1) -class ExpandCostItemRate(bpy.types.Operator): +class ExpandCostItemRate(bpy.types.Operator, tool.Ifc.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): + def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) @@ -1042,13 +953,13 @@ class ExpandCostItemRate(bpy.types.Operator): return {"FINISHED"} -class ContractCostItemRate(bpy.types.Operator): +class ContractCostItemRate(bpy.types.Operator, tool.Ifc.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): + def _execute(self, context): props = context.scene.BIMCostProperties self.file = IfcStore.get_file() contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) @@ -1058,19 +969,15 @@ class ContractCostItemRate(bpy.types.Operator): return {"FINISHED"} -class CalculateCostItemResourceValue(bpy.types.Operator): +class CalculateCostItemResourceValue(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.calculate_cost_item_resource_value" bl_label = "Calculate Cost Item Resource Value" bl_options = {"REGISTER", "UNDO"} cost_item: 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.calculate_cost_item_resource_value", self.file, cost_item=self.file.by_id(self.cost_item) ) - 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 9ed9dc65de..e2fcf32e9e 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -18,10 +18,10 @@ import bpy import ifcopenshell.api +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.classification.data import CostClassificationsData -from ifcopenshell.api.cost.data import Data -from ifcopenshell.api.pset.data import Data as PsetData +from blenderbim.bim.module.cost.data import CostSchedulesData, CostItemRatesData, CostItemQuantitiesData from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( @@ -36,44 +36,10 @@ from bpy.props import ( ) -quantitytypes_enum = [] -productquantitynames_enum = [] -productquantitynames_count = [] -processquantitynames_enum = [] -processquantitynames_id = 0 -resourcequantitynames_enum = [] -resourcequantitynames_id = 0 -scheduleofrates_enum = [] - - -def purge(): - global quantitytypes_enum - global productquantitynames_enum - global productquantitynames_count - global processquantitynames_enum - global processquantitynames_id - global resourcequantitynames_enum - global resourcequantitynames_id - global scheduleofrates_enum - quantitytypes_enum = [] - productquantitynames_enum = [] - productquantitynames_count = [] - processquantitynames_enum = [] - 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 + if not CostItemRatesData.is_loaded: + CostItemRatesData.load() + return CostItemRatesData.data["schedule_of_rates"] def update_schedule_of_rates(self, context): @@ -81,87 +47,32 @@ def update_schedule_of_rates(self, context): def get_quantity_types(self, context): - global quantitytypes_enum - if len(quantitytypes_enum) == 0 and IfcStore.get_schema(): - quantitytypes_enum.extend( - [ - (t.name(), t.name(), "") - for t in IfcStore.get_schema().declaration_by_name("IfcPhysicalSimpleQuantity").subtypes() - ] - ) - return quantitytypes_enum + if not CostSchedulesData.is_loaded: + CostSchedulesData.load() + return CostSchedulesData.data["quantity_types"] def get_product_quantity_names(self, context): - global productquantitynames_enum - global productquantitynames_count - ifc_file = IfcStore.get_file() - total_selected_objects = len(context.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 - if not element_id: - continue - potential_names = set() - if element_id not in PsetData.products: - PsetData.load(ifc_file, element_id) - for qto_id in PsetData.products[element_id]["qtos"]: - 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 - productquantitynames_enum.extend([(n, n, "") for n in names]) - return productquantitynames_enum + if not CostItemQuantitiesData.is_loaded: + CostItemQuantitiesData.load() + return CostItemQuantitiesData.data["product_quantity_names"] def get_process_quantity_names(self, context): - global processquantitynames_enum - global processquantitynames_id - ifc_file = IfcStore.get_file() - active_task_index = context.scene.BIMWorkScheduleProperties.active_task_index - total_tasks = len(context.scene.BIMTaskTreeProperties.tasks) - if not total_tasks or active_task_index >= total_tasks: - return [] - ifc_definition_id = context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id - 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 + if not CostItemQuantitiesData.is_loaded: + CostItemQuantitiesData.load() + return CostItemQuantitiesData.data["process_quantity_names"] def get_resource_quantity_names(self, context): - global resourcequantitynames_enum - global resourcequantitynames_id - ifc_file = IfcStore.get_file() - active_resource_index = context.scene.BIMResourceProperties.active_resource_index - total_resources = len(context.scene.BIMResourceTreeProperties.resources) - if not total_resources or active_resource_index >= total_resources: - return [] - ifc_definition_id = context.scene.BIMResourceTreeProperties.resources[active_resource_index].ifc_definition_id - if resourcequantitynames_id != ifc_definition_id: - resourcequantitynames_enum = [] - resourcequantitynames_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"]] - resourcequantitynames_enum.extend([(n, n, "") for n in names]) - return resourcequantitynames_enum + if not CostItemQuantitiesData.is_loaded: + CostItemQuantitiesData.load() + return CostItemQuantitiesData.data["resource_quantity_names"] def update_active_cost_item_index(self, context): - if Data.cost_schedules[self.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + schedule = tool.Ifc.get().by_id(self.active_cost_schedule_id) + if schedule.PredefinedType == "SCHEDULEOFRATES": bpy.ops.bim.load_cost_item_types() else: bpy.ops.bim.load_cost_item_quantities() @@ -178,7 +89,6 @@ def update_cost_item_identification(self, context): self.file, **{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}}, ) - Data.load(self.file) if props.active_cost_item_id == self.ifc_definition_id: attribute = props.cost_item_attributes.get("Identification") attribute.string_value = self.identification @@ -194,7 +104,6 @@ def update_cost_item_name(self, context): self.file, **{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}}, ) - Data.load(IfcStore.get_file()) if props.active_cost_item_id == self.ifc_definition_id: attribute = props.cost_item_attributes.get("Name") attribute.string_value = self.name diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 2a47e3e5e2..305edf219c 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -20,8 +20,7 @@ import blenderbim.bim.helper 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 -from ifcopenshell.api.unit.data import Data as UnitData +from blenderbim.bim.module.cost.data import CostSchedulesData class BIM_PT_cost_schedules(Panel): @@ -39,54 +38,46 @@ class BIM_PT_cost_schedules(Panel): return file and hasattr(file, "schema") and file.schema != "IFC2X3" def draw(self, context): + if not CostSchedulesData.is_loaded: + CostSchedulesData.load() + self.props = context.scene.BIMCostProperties - 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") - if self.props.active_cost_schedule_id: - self.draw_cost_schedule_ui( - self.props.active_cost_schedule_id, Data.cost_schedules[self.props.active_cost_schedule_id] - ) - else: - for cost_schedule_id, cost_schedule in Data.cost_schedules.items(): - self.draw_cost_schedule_ui(cost_schedule_id, cost_schedule) + for schedule in CostSchedulesData.data["schedules"]: + self.draw_cost_schedule_ui(schedule) - def draw_cost_schedule_ui(self, cost_schedule_id, cost_schedule): + def draw_cost_schedule_ui(self, cost_schedule): row = self.layout.row(align=True) - row.label(text=cost_schedule["Name"] or "Unnamed", icon="LINENUMBERS_ON") + row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON") - if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule_id: + if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule["id"]: op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="") - op.cost_schedule = cost_schedule_id + op.cost_schedule = cost_schedule["id"] row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY") if self.props.is_editing == "COST_SCHEDULE": row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK") elif self.props.is_editing == "COST_ITEMS": - row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule_id + row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule["id"] row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL") elif self.props.active_cost_schedule_id: - row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id + row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"] else: - row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule_id + row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"] row.operator( "bim.enable_editing_cost_schedule", text="", icon="GREASEPENCIL" - ).cost_schedule = cost_schedule_id - row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule_id + ).cost_schedule = cost_schedule["id"] + row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"] - if self.props.active_cost_schedule_id == cost_schedule_id: + if self.props.active_cost_schedule_id == cost_schedule["id"]: if self.props.is_editing == "COST_SCHEDULE": self.draw_editable_cost_schedule_ui() elif self.props.is_editing == "COST_ITEMS": if self.props.should_show_column_ui: self.draw_column_ui() - self.draw_editable_cost_item_ui(cost_schedule_id) + self.draw_editable_cost_item_ui() def draw_column_ui(self): row = self.layout.row(align=True) @@ -97,7 +88,7 @@ class BIM_PT_cost_schedules(Panel): def draw_editable_cost_schedule_ui(self): blenderbim.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout) - def draw_editable_cost_item_ui(self, cost_schedule_id): + def draw_editable_cost_item_ui(self): row = self.layout.row(align=True) row.alignment = "RIGHT" ifc_definition_id = None @@ -105,7 +96,7 @@ class BIM_PT_cost_schedules(Panel): ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id if ifc_definition_id: - if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] != "SCHEDULEOFRATES": + 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 @@ -150,33 +141,27 @@ class BIM_PT_cost_schedules(Panel): op.cost_item = self.props.active_cost_item_id op.ifc_class = self.props.quantity_types - for quantity_id in Data.cost_items[self.props.active_cost_item_id]["CostQuantities"]: - quantity = Data.physical_quantities[quantity_id] - value = quantity[[k for k in quantity.keys() if "Value" in k][0]] + for quantity in CostSchedulesData.data["cost_quantities"]: row = self.layout.row(align=True) - row.label(text=quantity["Name"]) - row.label(text="{0:.2f}".format(value)) - if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity_id: + row.label(text=quantity["name"]) + row.label(text=quantity["value"]) + if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity["id"]: op = row.operator("bim.edit_cost_item_quantity", text="", icon="CHECKMARK") - op.physical_quantity = quantity_id + op.physical_quantity = quantity["id"] row.operator("bim.disable_editing_cost_item_quantity", text="", icon="CANCEL") elif self.props.active_cost_item_quantity_id: op = row.operator("bim.remove_cost_item_quantity", text="", icon="X") op.cost_item = self.props.active_cost_item_id - op.physical_quantity = quantity_id + op.physical_quantity = quantity["id"] else: op = row.operator("bim.enable_editing_cost_item_quantity", text="", icon="GREASEPENCIL") - op.physical_quantity = quantity_id + op.physical_quantity = quantity["id"] op = row.operator("bim.remove_cost_item_quantity", text="", icon="X") op.cost_item = self.props.active_cost_item_id - op.physical_quantity = quantity_id + op.physical_quantity = quantity["id"] - if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity_id: - box = self.layout.box() - self.draw_editable_cost_item_quantity_ui(box) - - def draw_editable_cost_item_quantity_ui(self, layout): - blenderbim.bim.helper.draw_attributes(self.props.quantity_attributes, self.layout) + if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity["id"]: + blenderbim.bim.helper.draw_attributes(self.props.quantity_attributes, self.layout.box()) def draw_editable_cost_item_values_ui(self): row = self.layout.row(align=True) @@ -189,25 +174,20 @@ class BIM_PT_cost_schedules(Panel): if self.props.cost_types == "CATEGORY": op.cost_category = self.props.cost_category - for cost_value_id in Data.cost_items[self.props.active_cost_item_id]["CostValues"]: + for cost_value in CostSchedulesData.data["cost_values"]: row = self.layout.row(align=True) - self.draw_readonly_cost_value_ui(row, cost_value_id) + self.draw_readonly_cost_value_ui(row, cost_value) if self.props.cost_value_editing_type == "ATTRIBUTES": - box = self.layout.box() - self.draw_editable_cost_value_ui(box, Data.cost_values[self.props.active_cost_value_id]) + blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, self.layout.box()) - def draw_readonly_cost_value_ui(self, layout, cost_value_id): - cost_value = Data.cost_values[cost_value_id] - - if self.props.active_cost_value_id == cost_value_id and self.props.cost_value_editing_type == "FORMULA": + def draw_readonly_cost_value_ui(self, layout, cost_value): + if self.props.active_cost_value_id == cost_value["id"] and self.props.cost_value_editing_type == "FORMULA": layout.prop(self.props, "cost_value_formula", text="") else: - cost_value_label = "{0:.2f}".format(cost_value["AppliedValue"]) - cost_value_label += " = " + cost_value["Formula"] - layout.label(text=cost_value_label, icon="DISC") + layout.label(text=cost_value["label"], icon="DISC") - self.draw_cost_value_operator_ui(layout, cost_value_id, self.props.active_cost_item_id) + self.draw_cost_value_operator_ui(layout, cost_value["id"], self.props.active_cost_item_id) def draw_cost_value_operator_ui(self, layout, cost_value_id, parent_id): if self.props.active_cost_value_id and self.props.active_cost_value_id == cost_value_id: @@ -231,9 +211,6 @@ class BIM_PT_cost_schedules(Panel): op.parent = parent_id op.cost_value = cost_value_id - def draw_editable_cost_value_ui(self, layout, cost_value): - blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, layout) - class BIM_PT_cost_item_types(Panel): bl_label = "IFC Cost Item Types" @@ -250,7 +227,9 @@ class BIM_PT_cost_item_types(Panel): 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": + if not CostSchedulesData.is_loaded: + return False + if not CostSchedulesData.data["is_editing_rates"]: return False if total_cost_items > 0 and props.active_cost_item_index < total_cost_items: return True @@ -336,18 +315,15 @@ class BIM_PT_cost_item_quantities(Panel): 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": + if not CostSchedulesData.is_loaded: + return False + if CostSchedulesData.data["is_editing_rates"]: return False if total_cost_items > 0 and props.active_cost_item_index < total_cost_items: return True return False def draw(self, context): - if not Data.is_loaded: - Data.load(IfcStore.get_file()) - - if not UnitData.is_loaded: - UnitData.load(IfcStore.get_file()) self.props = context.scene.BIMCostProperties cost_item = self.props.cost_items[self.props.active_cost_item_index] @@ -485,7 +461,9 @@ class BIM_PT_cost_item_rates(Panel): 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": + if not CostSchedulesData.is_loaded: + return False + if CostSchedulesData.data["is_editing_rates"]: return False if total_cost_items > 0 and props.active_cost_item_index < total_cost_items: return True @@ -514,7 +492,7 @@ 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] + cost_item = CostSchedulesData.data["cost_items"][item.ifc_definition_id] row = layout.row(align=True) self.draw_hierarchy(row, item) @@ -550,7 +528,7 @@ class BIM_UL_cost_items_trait: layout.label(text="{0:.2f}".format(cost_item["TotalCost"])) def draw_quantity_column(self, layout, cost_item): - if Data.cost_schedules[self.props.active_cost_schedule_id]["PredefinedType"] == "SCHEDULEOFRATES": + if CostSchedulesData.data["is_editing_rates"]: self.draw_uom_column(layout, cost_item) else: self.draw_total_quantity_column(layout, cost_item) diff --git a/src/blenderbim/test/bim/feature/cost.feature b/src/blenderbim/test/bim/feature/cost.feature index c41d8157d1..4fc30b1e9a 100644 --- a/src/blenderbim/test/bim/feature/cost.feature +++ b/src/blenderbim/test/bim/feature/cost.feature @@ -6,17 +6,376 @@ Scenario: Add cost schedule When I press "bim.add_cost_schedule" Then nothing happens +Scenario: Enable editing cost schedule + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + When I press "bim.enable_editing_cost_schedule(cost_schedule={cost_schedule})" + Then nothing happens + +Scenario: Disable editing cost schedule + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_schedule(cost_schedule={cost_schedule})" + When I press "bim.disable_editing_cost_schedule" + Then nothing happens + +Scenario: Edit cost schedule + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_schedule(cost_schedule={cost_schedule})" + When I press "bim.edit_cost_schedule" + Then nothing happens + +Scenario: Remove cost schedule + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + When I press "bim.remove_cost_schedule(cost_schedule={cost_schedule})" + Then nothing happens + Scenario: Enable editing cost items Given an empty IFC project - When I press "bim.add_cost_schedule" + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + When I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + Then nothing happens + +Scenario: Disable editing cost schedule - after editing items + Given an empty IFC project + And I press "bim.add_cost_schedule" And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + When I press "bim.disable_editing_cost_schedule" Then nothing happens Scenario: Add summary cost item Given an empty IFC project - When I press "bim.add_cost_schedule" + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + When I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + Then nothing happens + +Scenario: Remove cost item + Given an empty IFC project + And I press "bim.add_cost_schedule" And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + When I press "bim.remove_cost_item(cost_item={cost_item})" + Then nothing happens + +Scenario: Enable editing cost item + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + When I press "bim.enable_editing_cost_item(cost_item={cost_item})" + Then nothing happens + +Scenario: Disable editing cost item + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item(cost_item={cost_item})" + When I press "bim.disable_editing_cost_item" + Then nothing happens + +Scenario: Edit cost item + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item(cost_item={cost_item})" + When I press "bim.edit_cost_item" + Then nothing happens + +Scenario: Add cost item + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + When I press "bim.add_cost_item(cost_item={cost_item})" + Then nothing happens + +Scenario: Enable editing cost item quantities + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + When I press "bim.enable_editing_cost_item_quantities(cost_item={cost_item})" + Then nothing happens + +Scenario: Add cost item quantity + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_quantities(cost_item={cost_item})" + When I press "bim.add_cost_item_quantity(cost_item={cost_item}, ifc_class='IfcQuantityArea')" + Then nothing happens + +Scenario: Remove cost item quantity + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_quantities(cost_item={cost_item})" + And I press "bim.add_cost_item_quantity(cost_item={cost_item}, ifc_class='IfcQuantityArea')" + And the variable "quantity" is "{ifc}.by_type('IfcQuantityArea')[0].id()" + When I press "bim.remove_cost_item_quantity(cost_item={cost_item}, physical_quantity={quantity})" + Then nothing happens + +Scenario: Enable editing cost item quantity + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_quantities(cost_item={cost_item})" + And I press "bim.add_cost_item_quantity(cost_item={cost_item}, ifc_class='IfcQuantityArea')" + And the variable "quantity" is "{ifc}.by_type('IfcQuantityArea')[0].id()" + When I press "bim.enable_editing_cost_item_quantity(physical_quantity={quantity})" + Then nothing happens + +Scenario: Disable editing cost item quantity + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_quantities(cost_item={cost_item})" + And I press "bim.add_cost_item_quantity(cost_item={cost_item}, ifc_class='IfcQuantityArea')" + And the variable "quantity" is "{ifc}.by_type('IfcQuantityArea')[0].id()" + And I press "bim.enable_editing_cost_item_quantity(physical_quantity={quantity})" + When I press "bim.disable_editing_cost_item_quantity" + Then nothing happens + +Scenario: Edit cost item quantity + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_quantities(cost_item={cost_item})" + And I press "bim.add_cost_item_quantity(cost_item={cost_item}, ifc_class='IfcQuantityArea')" + And the variable "quantity" is "{ifc}.by_type('IfcQuantityArea')[0].id()" + And I press "bim.enable_editing_cost_item_quantity(physical_quantity={quantity})" + And I set "scene.BIMCostProperties.quantity_attributes[2].float_value" to "3" + When I press "bim.edit_cost_item_quantity(physical_quantity={quantity})" + Then nothing happens + +Scenario: Enable editing cost item quantity + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + When I press "bim.enable_editing_cost_item_values(cost_item={cost_item})" + Then nothing happens + +Scenario: Add cost value - fixed + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_values(cost_item={cost_item})" + And I set "scene.BIMCostProperties.cost_types" to "FIXED" + When I press "bim.add_cost_value(parent={cost_item}, cost_type='FIXED')" + Then nothing happens + +Scenario: Enable editing cost item value + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_values(cost_item={cost_item})" + And I set "scene.BIMCostProperties.cost_types" to "FIXED" + And I press "bim.add_cost_value(parent={cost_item}, cost_type='FIXED')" + And the variable "cost_value" is "{ifc}.by_type('IfcCostValue')[0].id()" + When I press "bim.enable_editing_cost_item_value(cost_value={cost_value})" + Then nothing happens + +Scenario: Disable editing cost item value + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_values(cost_item={cost_item})" + And I set "scene.BIMCostProperties.cost_types" to "FIXED" + And I press "bim.add_cost_value(parent={cost_item}, cost_type='FIXED')" + And the variable "cost_value" is "{ifc}.by_type('IfcCostValue')[0].id()" + And I press "bim.enable_editing_cost_item_value(cost_value={cost_value})" + When I press "bim.disable_editing_cost_item_value" + Then nothing happens + +Scenario: Edit cost item value + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_values(cost_item={cost_item})" + And I set "scene.BIMCostProperties.cost_types" to "FIXED" + And I press "bim.add_cost_value(parent={cost_item}, cost_type='FIXED')" + And the variable "cost_value" is "{ifc}.by_type('IfcCostValue')[0].id()" + And I press "bim.enable_editing_cost_item_value(cost_value={cost_value})" + When I press "bim.edit_cost_value(cost_value={cost_value})" + Then nothing happens + +Scenario: Enable editing cost item value formula + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_values(cost_item={cost_item})" + And I set "scene.BIMCostProperties.cost_types" to "FIXED" + And I press "bim.add_cost_value(parent={cost_item}, cost_type='FIXED')" + And the variable "cost_value" is "{ifc}.by_type('IfcCostValue')[0].id()" + When I press "bim.enable_editing_cost_item_value_formula(cost_value={cost_value})" + Then nothing happens + +Scenario: Edit cost item value formula + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I press "bim.enable_editing_cost_item_values(cost_item={cost_item})" + And I set "scene.BIMCostProperties.cost_types" to "FIXED" + And I press "bim.add_cost_value(parent={cost_item}, cost_type='FIXED')" + And the variable "cost_value" is "{ifc}.by_type('IfcCostValue')[0].id()" + And I press "bim.enable_editing_cost_item_value_formula(cost_value={cost_value})" + And I set "scene.BIMCostProperties.cost_value_formula" to "3 + 2" + When I press "bim.edit_cost_value_formula(cost_value={cost_value})" + Then nothing happens + +Scenario: Add cost column + Given an empty IFC project + And I press "bim.add_cost_schedule" + And I set "scene.BIMCostProperties.should_show_column_ui" to "True" + And I set "scene.BIMCostProperties.cost_column" to "Foobar" + When I press "bim.add_cost_column(name='Foobar')" + Then nothing happens + +Scenario: Remove cost column + Given an empty IFC project + And I press "bim.add_cost_schedule" + And I set "scene.BIMCostProperties.should_show_column_ui" to "True" + And I set "scene.BIMCostProperties.cost_column" to "Foobar" + And I press "bim.add_cost_column(name='Foobar')" + When I press "bim.remove_cost_column(name='Foobar')" + Then nothing happens + +Scenario: Assign cost item quantity - count based + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + When I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='')" + Then nothing happens + +Scenario: Assign cost item quantity - quantity based + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.add_qto(obj='IfcWall/Cube', obj_type='Object')" + And I press "bim.calculate_all_quantities" + When I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='NetVolume')" + Then nothing happens + +Scenario: Unassign cost item quantity - selection based + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='')" + When I press "bim.unassign_cost_item_quantity(cost_item={cost_item}, related_object=0)" + Then nothing happens + +Scenario: Unassign cost item quantity - explicit object + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='')" + And the variable "wall" is "{ifc}.by_type('IfcWall')[0].id()" + When I press "bim.unassign_cost_item_quantity(cost_item={cost_item}, related_object={wall})" + Then nothing happens + +Scenario: Select cost item products + Given an empty IFC project + And I press "bim.add_cost_schedule" + And the variable "cost_schedule" is "{ifc}.by_type('IfcCostSchedule')[0].id()" + And I press "bim.enable_editing_cost_items(cost_schedule={cost_schedule})" + And I press "bim.add_summary_cost_item(cost_schedule={cost_schedule})" + And the variable "cost_item" is "{ifc}.by_type('IfcCostItem')[0].id()" + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And the object "IfcWall/Cube" is selected + And I press "bim.assign_cost_item_quantity(cost_item={cost_item}, related_object_type='PRODUCT', prop_name='')" + When I press "bim.select_cost_item_products(cost_item={cost_item})" Then nothing happens diff --git a/src/ifcopenshell-python/ifcopenshell/util/cost.py b/src/ifcopenshell-python/ifcopenshell/util/cost.py index bade3e8ae0..d0ff6c0482 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/cost.py +++ b/src/ifcopenshell-python/ifcopenshell/util/cost.py @@ -23,6 +23,82 @@ arithmetic_operator_symbols = {"ADD": "+", "DIVIDE": "/", "MULTIPLY": "*", "SUBT symbol_arithmetic_operators = {"+": "ADD", "/": "DIVIDE", "*": "MULTIPLY", "-": "SUBTRACT"} +def get_primitive_applied_value(applied_value): + if not applied_value: + return 0.0 + elif isinstance(applied_value, float): + return applied_value + elif hasattr(applied_value, "wrappedValue") and isinstance(applied_value.wrappedValue, float): + return applied_value.wrappedValue + elif applied_value.is_a("IfcMeasureWithUnit"): + return applied_value.ValueComponent + assert False, "Applied value {applied_value} not implemented" + + +def get_total_quantity(root_element): + if root_element.is_a("IfcCostItem"): + return sum([q[3] for q in root_element.CostQuantities or []]) or 1.0 + elif root_element.is_a("IfcConstructionResource"): + return root_element.BaseQuantity[3] if root_element.BaseQuantity else 1.0 + + +def calculate_applied_value(root_element, cost_value, category_filter=None): + if cost_value.ArithmeticOperator and cost_value.Components: + component_values = [] + for component in cost_value.Components: + component_values.append(calculate_applied_value(root_element, component, category_filter)) + if cost_value.ArithmeticOperator == "ADD": + return sum(component_values) + result = component_values.pop(0) + if cost_value.ArithmeticOperator == "DIVIDE": + for value in component_values: + try: + result /= value + except ZeroDivisionError: + pass + elif cost_value.ArithmeticOperator == "MULTIPLY": + for value in component_values: + result *= value + elif cost_value.ArithmeticOperator == "SUBTRACT": + for value in component_values: + result -= value + return result + if cost_value.Category is None: + return get_primitive_applied_value(cost_value.AppliedValue) + elif cost_value.Category == "*": + if root_element.IsNestedBy: + return sum_child_root_elements(root_element) + else: + return get_primitive_applied_value(cost_value.AppliedValue) + elif cost_value.Category: + if root_element.IsNestedBy: + return sum_child_root_elements(root_element, category_filter=cost_value.Category) + else: + return get_primitive_applied_value(cost_value.AppliedValue) + return 0 + + +def sum_child_root_elements(root_element, category_filter=None): + result = 0 + for rel in root_element.IsNestedBy: + for child_root_element in rel.RelatedObjects: + if root_element.is_a("IfcCostItem"): + values = child_root_element.CostValues + elif root_element.is_a("IfcConstructionResource"): + values = child_root_element.BaseCosts + for child_cost_value in values or []: + if category_filter and child_cost_value.Category != category_filter: + continue + child_applied_value = calculate_applied_value(child_root_element, child_cost_value) + child_quantity = get_total_quantity(child_root_element) + if child_cost_value.UnitBasis: + value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue + result += child_quantity / value_component * child_applied_value + else: + result += child_quantity * child_applied_value + return result + + def serialise_cost_value(cost_value): result = _serialise_cost_value(cost_value) if result and result[0] == "(" and result[-1] == ")":