diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index c431d481b1..c90892500e 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -30,6 +30,7 @@ classes = ( operator.EnableEditingCostSchedule, operator.EnableEditingCostItems, operator.EnableEditingCostItem, + operator.ExpandCostItems, operator.EnableEditingCostItemQuantities, operator.EnableEditingCostItemQuantity, operator.EnableEditingCostItemValues, diff --git a/src/blenderbim/blenderbim/bim/module/cost/data.py b/src/blenderbim/blenderbim/bim/module/cost/data.py index f4ece5e022..55a47e143e 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/data.py +++ b/src/blenderbim/blenderbim/bim/module/cost/data.py @@ -281,7 +281,9 @@ class CostItemQuantitiesData: 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 + 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) diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index a57e2fb6c7..50cd7d16cf 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -62,15 +62,16 @@ class RemoveCostSchedule(bpy.types.Operator, tool.Ifc.Operator): core.remove_cost_schedule(tool.Ifc, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule)) -class EnableEditingCostSchedule(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingCostSchedule(bpy.types.Operator): bl_idname = "bim.enable_editing_cost_schedule_attributes" bl_label = "Enable Editing Cost Schedule" bl_options = {"REGISTER", "UNDO"} bl_description = "Enable editing cost schedule" cost_schedule: bpy.props.IntProperty() - def _execute(self, context): + def execute(self, context): core.enable_editing_cost_schedule_attributes(tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule)) + return {"FINISHED"} class EnableEditingCostItems(bpy.types.Operator, tool.Ifc.Operator): @@ -194,18 +195,13 @@ class AssignCostItemType(bpy.types.Operator, tool.Ifc.Operator): prop_name: bpy.props.StringProperty() def _execute(self, context): - self.file = IfcStore.get_file() - cost_item = self.file.by_id(self.cost_item) - for obj in context.selected_objects: - if not obj.BIMObjectProperties.ifc_definition_id: - continue - product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) - if product.is_a("IfcTypeProduct"): - ifcopenshell.api.run( - "control.assign_control", self.file, relating_control=cost_item, related_object=product - ) - bpy.ops.bim.load_cost_item_types() - return {"FINISHED"} + core.assign_cost_item_type( + tool.Ifc, + tool.Cost, + tool.Spatial, + cost_item=tool.Ifc.get().by_id(self.cost_item), + prop_name=self.prop_name, # TODO: REVIEW PROP_NAME USABILITY + ) class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator): @@ -216,22 +212,12 @@ class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator): related_object: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - cost_item = self.file.by_id(self.cost_item) - if self.related_object: - products = [self.file.by_id(self.related_object)] - else: - for obj in context.selected_objects: - if not obj.BIMObjectProperties.ifc_definition_id: - continue - product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) - if product.is_a("IfcTypeProduct"): - products.append(product) - for product in products: - ifcopenshell.api.run( - "control.unassign_control", self.file, relating_control=cost_item, related_object=product - ) - bpy.ops.bim.load_cost_item_types() + core.unassign_cost_item_type( + tool.Ifc, + tool.Cost, + self.cost_item, + products=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [], + ) return {"FINISHED"} @@ -244,157 +230,106 @@ class AssignCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): prop_name: bpy.props.StringProperty() def _execute(self, context): - if self.related_object_type == "PRODUCT": - products = [ - 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 = [ - tool.Ifc.get().by_id( - context.scene.BIMTaskTreeProperties.tasks[ - context.scene.BIMWorkScheduleProperties.active_task_index - ].ifc_definition_id - ) - ] - elif self.related_object_type == "RESOURCE": - products = [ - tool.Ifc.get().by_id( - context.scene.BIMResourceTreeProperties.resources[ - context.scene.BIMResourceProperties.active_resource_index - ].ifc_definition_id - ) - ] - ifcopenshell.api.run( - "cost.assign_cost_item_quantity", - tool.Ifc.get(), + core.assign_cost_item_quantity( + tool.Ifc, + tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item), - products=products, - prop_name=self.prop_name, + related_object_type=self.related_object_type, + prop_name=self.prop_name, # TODO: REVIEW PROP_NAME USABILITY ) - bpy.ops.bim.load_cost_item_quantities() - return {"FINISHED"} 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"} + bl_description = "Unassign cost item quantity" cost_item: bpy.props.IntProperty() related_object: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - if self.related_object: - products = [self.file.by_id(self.related_object)] - else: - products = [ - self.file.by_id(o.BIMObjectProperties.ifc_definition_id) - for o in context.selected_objects - if o.BIMObjectProperties.ifc_definition_id - ] - ifcopenshell.api.run( - "cost.unassign_cost_item_quantity", - self.file, - cost_item=self.file.by_id(self.cost_item), - products=products, + core.unassign_cost_item_quantity( + tool.Ifc, + tool.Cost, + cost_item=tool.Ifc.get().by_id(self.cost_item), + products=[tool.Ifc.get().by_id(self.related_object)] + if self.related_object + else tool.Spatial.get_selected_products(), ) - bpy.ops.bim.load_cost_item_quantities() - return {"FINISHED"} 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"} + bl_description = "Enable editing cost item quantities" cost_item: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMCostProperties - props.active_cost_item_id = self.cost_item - props.cost_item_editing_type = "QUANTITIES" - return {"FINISHED"} + core.enable_editing_cost_item_quantities(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item)) 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"} + bl_description = "Enable editing cost item values" cost_item: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMCostProperties - props.active_cost_item_id = self.cost_item - props.cost_item_editing_type = "VALUES" - bpy.ops.bim.disable_editing_cost_item_value() - return {"FINISHED"} + core.enable_editing_cost_item_values(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item)) 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"} + bl_description = "Add cost item quantity" cost_item: bpy.props.IntProperty() ifc_class: bpy.props.StringProperty() def _execute(self, context): - self.file = IfcStore.get_file() - self.props = context.scene.BIMCostProperties - self.add_manual_quantity() - return {"FINISHED"} - - def add_manual_quantity(self): - ifcopenshell.api.run( - "cost.add_cost_item_quantity", - self.file, - cost_item=self.file.by_id(self.cost_item), - ifc_class=self.ifc_class, - ) + core.add_cost_item_quantity(tool.Ifc, cost_item=tool.Ifc.get().by_id(self.cost_item), ifc_class=self.ifc_class) 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"} + bl_description = "Remove cost item quantity" cost_item: bpy.props.IntProperty() physical_quantity: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "cost.remove_cost_item_quantity", - self.file, - cost_item=self.file.by_id(self.cost_item), - physical_quantity=self.file.by_id(self.physical_quantity), + core.remove_cost_item_quantity( + tool.Ifc, + cost_item=tool.Ifc.get().by_id(self.cost_item), + physical_quantity=tool.Ifc.get().by_id(self.physical_quantity), ) - return {"FINISHED"} -class EnableEditingCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingCostItemQuantity(bpy.types.Operator): bl_idname = "bim.enable_editing_cost_item_quantity" bl_label = "Enable Editing Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} + bl_description = "Enable editing cost item quantity" physical_quantity: bpy.props.IntProperty() - def _execute(self, context): - self.props = context.scene.BIMCostProperties - self.props.quantity_attributes.clear() - self.props.active_cost_item_quantity_id = self.physical_quantity - blenderbim.bim.helper.import_attributes2( - tool.Ifc.get().by_id(self.physical_quantity), self.props.quantity_attributes + def execute(self, context): + core.enable_editing_cost_item_quantity( + tool.Cost, physical_quantity=tool.Ifc.get().by_id(self.physical_quantity) ) return {"FINISHED"} -class DisableEditingCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): +class DisableEditingCostItemQuantity(bpy.types.Operator): bl_idname = "bim.disable_editing_cost_item_quantity" bl_label = "Disable Editing Cost Item Quantity" bl_options = {"REGISTER", "UNDO"} + bl_description = "Disable editing cost item quantity" def _execute(self, context): - props = context.scene.BIMCostProperties - props.active_cost_item_quantity_id = 0 + core.disable_editing_cost_item_quantity(tool.Cost) return {"FINISHED"} @@ -405,16 +340,9 @@ class EditCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator): physical_quantity: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMCostProperties - attributes = blenderbim.bim.helper.export_attributes(props.quantity_attributes) - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "cost.edit_cost_item_quantity", - self.file, - **{"physical_quantity": self.file.by_id(self.physical_quantity), "attributes": attributes}, + core.edit_cost_item_quantity( + tool.Ifc, tool.Cost, physical_quantity=tool.Ifc.get().by_id(self.physical_quantity) ) - bpy.ops.bim.disable_editing_cost_item_quantity() - return {"FINISHED"} class AddCostValue(bpy.types.Operator, tool.Ifc.Operator): @@ -426,19 +354,13 @@ class AddCostValue(bpy.types.Operator, tool.Ifc.Operator): cost_category: bpy.props.StringProperty() def _execute(self, context): - self.file = IfcStore.get_file() - parent = self.file.by_id(self.parent) - if self.cost_type == "FIXED": - category = None - attributes = {"AppliedValue": 0.0} - elif self.cost_type == "SUM": - category = "*" - attributes = {"Category": category} - elif self.cost_type == "CATEGORY": - category = self.cost_category - 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) + core.add_cost_value( + tool.Ifc, + tool.Cost, + parent=tool.Ifc.get().by_id(self.parent), + cost_type=self.cost_type, + cost_category=self.cost_category, + ) return {"FINISHED"} @@ -450,114 +372,41 @@ class RemoveCostItemValue(bpy.types.Operator, tool.Ifc.Operator): cost_value: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - parent = self.file.by_id(self.parent) - ifcopenshell.api.run( - "cost.remove_cost_value", - self.file, - parent=parent, - cost_value=self.file.by_id(self.cost_value), + core.remove_cost_value( + tool.Ifc, parent=tool.Ifc.get().by_id(self.parent), cost_value=tool.Ifc.get().by_id(self.cost_value) ) return {"FINISHED"} -class EnableEditingCostItemValue(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingCostItemValue(bpy.types.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): - 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" - 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) + def execute(self, context): + core.enable_editing_cost_item_value(tool.Cost, cost_value=tool.Ifc.get().by_id(self.cost_value)) return {"FINISHED"} - 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 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 is_rates: - prop = self.props.cost_value_attributes.add() - prop.name = "UnitBasisValue" - prop.data_type = "float" - prop.is_null = data["UnitBasis"] is None - prop.is_optional = True - if data["UnitBasis"]: - prop.float_value = data["UnitBasis"]["ValueComponent"] or 0 - else: - prop.float_value = 0 - prop = self.props.cost_value_attributes.add() - prop.name = "UnitBasisUnit" - prop.data_type = "enum" - prop.is_null = prop.is_optional = False - units = {} - for unit in tool.Ifc.get().by_type("IfcNamedUnit"): - if unit.get("UnitType", None) in [ - "AREAUNIT", - "LENGTHUNIT", - "TIMEUNIT", - "VOLUMEUNIT", - "MASSUNIT", - "USERDEFINED", - ]: - if unit.is_a("IfcContextDependentUnit"): - units[unit.id()] = f"{unit.UnitType} / {unit.Name}" - else: - name = unit.Name - if unit.get("Prefix", None): - 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, tool.Ifc.Operator): +class DisableEditingCostItemValue(bpy.types.Operator): bl_idname = "bim.disable_editing_cost_item_value" bl_label = "Disable Editing Cost Item Value" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): - props = context.scene.BIMCostProperties - props.active_cost_value_id = 0 - props.cost_value_editing_type = "" + def execute(self, context): + core.disable_editing_cost_item_value(tool.Cost) return {"FINISHED"} -class EnableEditingCostItemValueFormula(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingCostItemValueFormula(bpy.types.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): - 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 = ifcopenshell.util.cost.serialise_cost_value( - tool.Ifc.get().by_id(self.cost_value) - ) + def execute(self, context): + core.enable_editing_cost_item_value_formula(tool.Cost, cost_value=tool.Ifc.get().by_id(self.cost_value)) return {"FINISHED"} @@ -568,14 +417,7 @@ class EditCostItemValueFormula(bpy.types.Operator, tool.Ifc.Operator): cost_value: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMCostProperties - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "cost.edit_cost_value_formula", - self.file, - **{"cost_value": self.file.by_id(self.cost_value), "formula": props.cost_value_formula}, - ) - bpy.ops.bim.disable_editing_cost_item_value() + core.edit_cost_item_value_formula(tool.Ifc, tool.Cost, cost_value=tool.Ifc.get().by_id(self.cost_value)) return {"FINISHED"} @@ -586,34 +428,9 @@ class EditCostItemValue(bpy.types.Operator, tool.Ifc.Operator): cost_value: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMCostProperties - attributes = blenderbim.bim.helper.export_attributes( - props.cost_value_attributes, lambda attributes, prop: self.export_attributes(attributes, prop, context) - ) - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "cost.edit_cost_value", - self.file, - **{"cost_value": self.file.by_id(self.cost_value), "attributes": attributes}, - ) - bpy.ops.bim.disable_editing_cost_item_value() + core.edit_cost_value(tool.Ifc, tool.Cost, cost_value=tool.Ifc.get().by_id(self.cost_value)) return {"FINISHED"} - def export_attributes(self, attributes, prop, context): - if prop.name == "UnitBasisValue": - if prop.is_null: - attributes["UnitBasis"] = None - return True - attributes["UnitBasis"] = { - "ValueComponent": prop.float_value or 1, - "UnitComponent": IfcStore.get_file().by_id( - int(context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value) - ), - } - return True - if prop.name == "UnitBasisUnit": - return True - class CopyCostItemValues(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.copy_cost_item_values" @@ -623,11 +440,11 @@ class CopyCostItemValues(bpy.types.Operator, tool.Ifc.Operator): destination: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "cost.copy_cost_item_values", - self.file, - **{"source": self.file.by_id(self.source), "destination": self.file.by_id(self.destination)}, + core.copy_cost_item_values( + tool.Ifc, + tool.Cost, + source=tool.Ifc.get().by_id(self.source), + destination=tool.Ifc.get().by_id(self.destination), ) return {"FINISHED"} @@ -639,40 +456,24 @@ class SelectCostItemProducts(bpy.types.Operator, tool.Ifc.Operator): cost_item: bpy.props.IntProperty() 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_objects: - obj.select_set(True) + core.select_cost_item_products( + tool.Cost, tool.Spatial, cost_item=tool.Ifc.get().by_id(self.cost_item), include_nested=False + ) return {"FINISHED"} -class SelectCostScheduleProducts(bpy.types.Operator, tool.Ifc.Operator): +class SelectCostScheduleProducts(bpy.types.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): - self.file = IfcStore.get_file() - self.related_products = [] - 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) - if obj.BIMObjectProperties.ifc_definition_id in self.related_products: - obj.select_set(True) + def execute(self, context): + core.select_cost_schedule_products( + tool.Cost, tool.Spatial, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule) + ) return {"FINISHED"} - def get_related_products(self, cost_item): - [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, tool.Ifc.Operator): bl_idname = "import_cost_schedule_csv.bim" @@ -683,158 +484,75 @@ class ImportCostScheduleCsv(bpy.types.Operator, ImportHelper, tool.Ifc.Operator) is_schedule_of_rates: bpy.props.BoolProperty(name="Is Schedule Of Rates", default=False) def _execute(self, context): - from ifc5d.csv2ifc import Csv2Ifc - - self.file = IfcStore.get_file() - start = time.time() - csv2ifc = Csv2Ifc() - csv2ifc.csv = self.filepath - csv2ifc.file = self.file - csv2ifc.is_schedule_of_rates = self.is_schedule_of_rates - csv2ifc.execute() - print("Import finished in {:.2f} seconds".format(time.time() - start)) + core.import_cost_schedule_csv(tool.Cost, self.filepath, self.is_schedule_of_rates) return {"FINISHED"} -class AddCostColumn(bpy.types.Operator, tool.Ifc.Operator): +class AddCostColumn(bpy.types.Operator): bl_idname = "bim.add_cost_column" bl_label = "Add Cost Column" bl_options = {"REGISTER", "UNDO"} name: bpy.props.StringProperty() def _execute(self, context): - self.props = context.scene.BIMCostProperties - new = self.props.columns.add() - new.name = self.name + core.add_cost_column(tool.Cost, self.name) return {"FINISHED"} -class RemoveCostColumn(bpy.types.Operator, tool.Ifc.Operator): +class RemoveCostColumn(bpy.types.Operator): bl_idname = "bim.remove_cost_column" bl_label = "Remove Cost Column" bl_options = {"REGISTER", "UNDO"} name: bpy.props.StringProperty() def _execute(self, context): - self.props = context.scene.BIMCostProperties - self.props.columns.remove(self.props.columns.find(self.name)) + core.remove_cost_column(tool.Cost, self.name) return {"FINISHED"} -class LoadCostItemQuantities(bpy.types.Operator, tool.Ifc.Operator): +class LoadCostItemQuantities(bpy.types.Operator): bl_idname = "bim.load_cost_item_quantities" bl_label = "Load Cost Item Quantities" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): - self.props = context.scene.BIMCostProperties - self.file = IfcStore.get_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 - 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 + def execute(self, context): + core.load_cost_item_quantities(tool.Cost) return {"FINISHED"} -class LoadCostItemTypes(bpy.types.Operator, tool.Ifc.Operator): +class LoadCostItemTypes(bpy.types.Operator): bl_idname = "bim.load_cost_item_types" bl_label = "Load Cost Item Types" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): - self.props = context.scene.BIMCostProperties - self.file = IfcStore.get_file() - self.props.cost_item_type_products.clear() - # TODO implement process and resource types - # 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 - 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" + def execute(self, context): + core.load_cost_item_types(tool.Cost) return {"FINISHED"} class AssignCostValue(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_cost_value" - bl_label = "Assign Cost Value" + bl_label = "Assign Cost Rate Value" bl_options = {"REGISTER", "UNDO"} cost_item: bpy.props.IntProperty() cost_rate: bpy.props.IntProperty() def _execute(self, context): - self.file = IfcStore.get_file() - ifcopenshell.api.run( - "cost.assign_cost_value", - self.file, - cost_item=self.file.by_id(self.cost_item), - cost_rate=self.file.by_id(self.cost_rate), + core.assign_cost_value( + tool.Ifc, cost_item=tool.Ifc.get().by_id(self.cost_item), cost_rate=tool.Ifc.get().by_id(self.cost_rate) ) - return {"FINISHED"} -class LoadScheduleOfRates(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.load_schedule_of_rates" +class LoadScheduleOfRates(bpy.types.Operator): + bl_idname = "bim.load_schedule_of_rates_tree" bl_label = "Load Schedule of Rates" bl_options = {"REGISTER", "UNDO"} cost_schedule: bpy.props.IntProperty() - def _execute(self, context): - self.props = context.scene.BIMCostProperties - self.props.is_cost_update_enabled = False - self.props.cost_item_rates.clear() - self.contracted_cost_item_rates = json.loads(self.props.contracted_cost_item_rates) - for 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 + def execute(self, context): + core.load_schedule_of_rates_tree(tool.Cost, schedule_of_rates=tool.Ifc.get().by_id(self.cost_schedule)) return {"FINISHED"} - def create_new_cost_item_li(self, cost_item, level_index): - new = self.props.cost_item_rates.add() - 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: - new.has_children = True - if new.is_expanded: - 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, tool.Ifc.Operator): bl_idname = "bim.expand_cost_item_rate" @@ -843,12 +561,7 @@ class ExpandCostItemRate(bpy.types.Operator, tool.Ifc.Operator): cost_item: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMCostProperties - self.file = IfcStore.get_file() - contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) - contracted_cost_item_rates.remove(self.cost_item) - props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) - bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates)) + core.expand_cost_item_rate(tool.Cost, self.cost_item) return {"FINISHED"} @@ -859,12 +572,7 @@ class ContractCostItemRate(bpy.types.Operator, tool.Ifc.Operator): cost_item: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMCostProperties - self.file = IfcStore.get_file() - contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) - contracted_cost_item_rates.append(self.cost_item) - props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) - bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(props.schedule_of_rates)) + core.contract_cost_item_rate(tool.Cost, self.cost_item) return {"FINISHED"} @@ -875,8 +583,6 @@ class CalculateCostItemResourceValue(bpy.types.Operator, tool.Ifc.Operator): cost_item: bpy.props.IntProperty() 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) - ) + core.calculate_cost_item_resource_value(tool.Ifc, cost_item=tool.Ifc.get().by_id(self.cost_item)) return {"FINISHED"} + diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index e2fcf32e9e..739a221b70 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -43,7 +43,7 @@ def get_schedule_of_rates(self, context): def update_schedule_of_rates(self, context): - bpy.ops.bim.load_schedule_of_rates(cost_schedule=int(self.schedule_of_rates)) + tool.Cost.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(self.schedule_of_rates))) def get_quantity_types(self, context): @@ -73,9 +73,9 @@ def get_resource_quantity_names(self, context): def update_active_cost_item_index(self, context): schedule = tool.Ifc.get().by_id(self.active_cost_schedule_id) if schedule.PredefinedType == "SCHEDULEOFRATES": - bpy.ops.bim.load_cost_item_types() + tool.Cost.load_cost_item_types() else: - bpy.ops.bim.load_cost_item_quantities() + tool.Cost.load_cost_item_quantities(cost_item=tool.Cost.get_highlighted_cost_item()) CostClassificationsData.load() diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 663b332aec..5cc10fd691 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -470,13 +470,14 @@ class BIM_PT_cost_item_rates(Panel): def draw(self, context): self.props = context.scene.BIMCostProperties - cost_item = self.props.cost_items[self.props.active_cost_item_index] row = self.layout.row(align=True) row.prop(self.props, "schedule_of_rates", text="") if self.props.active_cost_item_rate_index < len(self.props.cost_item_rates): + cost_item = self.props.cost_items[self.props.active_cost_item_index] + cost_item_rate = self.props.cost_item_rates[self.props.active_cost_item_rate_index] op = row.operator("bim.assign_cost_value", text="", icon="COPYDOWN") - op.cost_item = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id - op.cost_rate = self.props.cost_item_rates[self.props.active_cost_item_rate_index].ifc_definition_id + op.cost_item = cost_item.ifc_definition_id + op.cost_rate = cost_item_rate.ifc_definition_id self.layout.template_list( "BIM_UL_cost_item_rates", "", diff --git a/src/blenderbim/blenderbim/core/cost.py b/src/blenderbim/blenderbim/core/cost.py index 77c963c65e..8e5f0437db 100644 --- a/src/blenderbim/blenderbim/core/cost.py +++ b/src/blenderbim/blenderbim/core/cost.py @@ -1,65 +1,212 @@ def add_cost_schedule(ifc): ifc.run("cost.add_cost_schedule") + def edit_cost_schedule(ifc, cost, cost_schedule): attributes = cost.get_cost_schedule_attributes() ifc.run("cost.edit_cost_schedule", cost_schedule=cost_schedule, attributes=attributes) cost.disable_editing_cost_schedule() + def disable_editing_cost_schedule(cost): cost.disable_editing_cost_schedule() + def remove_cost_schedule(ifc, cost_schedule): ifc.run("cost.remove_cost_schedule", cost_schedule=cost_schedule) + def enable_editing_cost_schedule_attributes(cost, cost_schedule): + cost.load_cost_schedule_attributes(cost_schedule) cost.enable_editing_cost_schedule_attributes(cost_schedule) - cost.load_cost_items() - cost.play_chaching_sound() + cost.play_sound() def enable_editing_cost_items(cost, cost_schedule): cost.enable_editing_cost_items(cost_schedule) - cost.load_cost_items() + cost.load_cost_schedule_tree() + cost.play_sound() def add_summary_cost_item(ifc, cost, cost_schedule): ifc.run("cost.add_cost_item", cost_schedule=cost_schedule) - cost.load_cost_items() + cost.load_cost_schedule_tree() + # cost.play_sound() def add_cost_item(ifc, cost, cost_item): ifc.run("cost.add_cost_item", cost_item=cost_item) - cost.load_cost_items() + cost.load_cost_schedule_tree() # cost.enable_editing_cost_schedule_attributes(cost_schedule) + def expand_cost_item(cost, cost_item): cost.expand_cost_item(cost_item) - cost.load_cost_items() + cost.load_cost_schedule_tree() + def expand_cost_items(cost): cost.expand_cost_items() - cost.load_cost_items() + cost.load_cost_schedule_tree() + def contract_cost_item(cost, cost_item): cost.contract_cost_item(cost_item) - cost.load_cost_items() + cost.load_cost_schedule_tree() + def contract_cost_items(cost): cost.contract_cost_items() - cost.load_cost_items() + cost.load_cost_schedule_tree() + def remove_cost_item(ifc, cost, cost_item): ifc.run("cost.remove_cost_item", cost_item=cost_item) cost.clean_up_cost_item_tree(cost_item) - cost.load_cost_items() + cost.load_cost_schedule_tree() + def enable_editing_cost_item_attributes(cost, cost_item): cost.enable_editing_cost_item_attributes(cost_item) cost.load_cost_item_attributes(cost_item) + def disable_editing_cost_item(cost): cost.disable_editing_cost_item() + def edit_cost_item(ifc, cost): attributes = cost.get_cost_item_attributes() ifc.run("cost.edit_cost_item", cost_item=cost.get_active_cost_item(), attributes=attributes) cost.disable_editing_cost_item() - cost.load_cost_items() \ No newline at end of file + cost.load_cost_schedule_tree() + + +def assign_cost_item_type(ifc, cost, spatial, cost_item, prop_name): + product_types = spatial.get_selected_product_types() + [ + ifc.run("control.assign_control", relating_control=cost_item, related_object=product_type) + for product_type in product_types + ] + cost.load_cost_item_types(cost_item) + + +def unassign_cost_item_type(ifc, cost, spatial, cost_item, product_types): + if not product_types: + product_types = spatial.get_selected_product_types() + [ + ifc.run("control.unassign_control", relating_control=cost_item, related_object=product_type) + for product_type in product_types + ] + cost.load_cost_item_types(cost_item) + + +def load_cost_item_types(cost): + cost_item = cost.get_active_cost_item() + cost.load_cost_item_types(cost_item) + + +def assign_cost_item_quantity(ifc, cost, cost_item, related_object_type, prop_name): + products = cost.get_products(related_object_type) + if products: + ifc.run("cost.assign_cost_item_quantity", cost_item=cost_item, products=products, prop_name=prop_name) + cost.load_cost_item_quantities(cost_item) + + +def load_cost_item_quantities(cost): + cost_item = cost.get_highlighted_cost_item() + cost.load_cost_item_quantities(cost_item) + + +def assign_cost_value(ifc, cost_item, cost_rate): + ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate) + +def load_schedule_of_rates(cost, schedule_of_rates): + cost.load_schedule_of_rates(schedule_of_rates) + +def unassign_cost_item_quantity(ifc, cost, cost_item, products): + ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products) + cost.load_cost_item_quantities(cost_item) + +def enable_editing_cost_item_quantities(cost, cost_item): + cost.enable_editing_cost_item_quantities(cost_item) + +def enable_editing_cost_item_values(cost, cost_item): + cost.enable_editing_cost_item_values(cost_item) + +def add_cost_item_quantity(ifc, cost_item, ifc_class): + ifc.run("cost.add_cost_item_quantity", cost_item=cost_item, ifc_class=ifc_class) + +def remove_cost_item_quantity(ifc, cost_item, physical_quantity): + ifc.run("cost.remove_cost_item_quantity", cost_item=cost_item, physical_quantity=physical_quantity) + +def enable_editing_cost_item_quantity(cost, physical_quantity): + cost.load_cost_item_quantity_attributes(physical_quantity) + cost.enable_editing_cost_item_quantity(physical_quantity) + +def disable_editing_cost_item_quantity(cost): + cost.disable_editing_cost_item_quantity() + +def edit_cost_item_quantity(ifc, cost, physical_quantity): + attributes = cost.get_cost_item_quantity_attributes() + ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=attributes) + cost.disable_editing_cost_item_quantity() + # cost.load_cost_item_quantities(cost.get_active_cost_item()) + +def add_cost_value(ifc, cost, parent, cost_type, cost_category): + value = ifc.run("cost.add_cost_value", parent=parent) + ifc.run( + "cost.edit_cost_value", + cost_value=value, + attributes=cost.get_attributes_for_cost_value(cost_type, cost_category)) + +def remove_cost_value(ifc, parent, cost_value): + ifc.run("cost.remove_cost_value", parent=parent, cost_value=cost_value) + +def enable_editing_cost_item_value(cost, cost_value): + cost.load_cost_item_value_attributes(cost_value) + cost.enable_editing_cost_item_value(cost_value) + +def disable_editing_cost_item_value(cost): + cost.disable_editing_cost_item_value() + +def enable_editing_cost_item_value_formula(cost, cost_value): + cost.load_cost_item_value_formula_attributes(cost_value) + cost.enable_editing_cost_item_value_formula(cost_value) + +def edit_cost_item_value_formula(ifc, cost, cost_value): + formula = cost.get_cost_item_value_formula() + ifc.run("cost.edit_cost_value_formula", cost_value=cost_value, formula=formula) + cost.disable_editing_cost_item_value() + +def edit_cost_value(ifc, cost, cost_value): + attributes = cost.get_cost_value_attributes() + ifc.run("cost.edit_cost_value", cost_value=cost_value, attributes=attributes) + cost.disable_editing_cost_item_value() + #cost.load_cost_item_values(cost.get_active_cost_item()) + +def copy_cost_item_values(ifc, cost, source, destination): + ifc.run("cost.copy_cost_item_values", source=source, destination=destination) + +def select_cost_item_products(cost, spatial, cost_item, include_nested): + products = cost.get_cost_item_products(cost_item, include_nested) + spatial.select_products(products) + +def select_cost_schedule_products(cost, spatial, cost_schedule): + products = cost.get_cost_schedule_products(cost_schedule) + spatial.select_products(products) + +def import_cost_schedule_csv(cost, file_path, is_schedule_of_rates): + cost.import_cost_schedule_csv(file_path, is_schedule_of_rates) + +def add_cost_column(cost, name): + cost.add_cost_column(name) + +def remove_cost_column(cost, name): + cost.remove_cost_column(name) + +def expand_cost_item_rate(cost, cost_item): + cost.expand_cost_item_rate(cost_item) + +def contract_cost_item_rate(cost, cost_item): + cost.contract_cost_item_rate(cost_item) + +def calculate_cost_item_resource_value(ifc, cost_item): + ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 113c116ef7..4cde186194 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -127,23 +127,57 @@ class Context: @interface class Cost: + def add_cost_column(cls, name): pass def clean_up_cost_item_tree(cls, cost_item): pass + def contract_cost_item_rate(cls, cost_item): pass def contract_cost_item(cls, cost_item): pass def contract_cost_items(cls): pass - def create_new_cost_item_li(cls, props, cost_item, level_index): pass + def create_new_cost_item_li(props_collection, cost_item, level_index, type): pass + def disable_editing_cost_item_quantity(cls): pass + def disable_editing_cost_item_value(cls): pass def disable_editing_cost_item(cls): pass def disable_editing_cost_schedule(cls): pass def enable_editing_cost_item_attributes(cls, cost_item): pass + def enable_editing_cost_item_quantities(cls, cost_item): pass + def enable_editing_cost_item_quantity(cls, physical_quantity): pass + def enable_editing_cost_item_value_formula(cls, cost_value): pass + def enable_editing_cost_item_value(cls, cost_value): pass + def enable_editing_cost_item_values(cls, cost_item): pass def enable_editing_cost_items(cls, cost_schedule): pass def enable_editing_cost_schedule_attributes(cls, cost_schedule): pass + def expand_cost_item_rate(cls, cost_item): pass def expand_cost_item(cls, cost_item): pass def expand_cost_items(cls): pass def get_active_cost_item(cls): pass + def get_all_nested_cost_items(cls, cost_item): pass def get_cost_item_attributes(cls, cost_item): pass + def get_cost_item_products(cls): pass + def get_cost_item_quantity_attributes(cls): pass + def get_cost_item_value_formula(cls): pass def get_cost_schedule_attributes(cls): pass - def load_cost_items(cls): pass + def get_cost_schedule_products(cls, cost_schedule): pass + def get_attributes_for_cost_value(cls, cost_type, cost_category): pass + def get_cost_value_attributes(cls): pass + def get_cost_value_unit_component(cls): pass + def get_direct_cost_item_products(cls): pass + def get_highlighted_cost_item(cls): pass + def get_nested_cost_items(cls, cost_item): pass + def get_products(cls, related_object_type): pass + def get_root_cost_items(cls, cost_schedule): pass + def get_schedule_cost_items(cls, cost_schedule): pass + def import_cost_schedule_csv(cls, file_path, is_schedule_of_rates): pass + def is_active_schedule_of_rates(cls): pass def load_cost_item_attributes(cls): pass + def load_cost_item_quantities(cls, cost_item): pass + def load_cost_item_quantity_attributes(cls, physical_quantity): pass + def load_cost_item_types(cls, cost_item): pass + def load_cost_item_value_attributes(cls, cost_value): pass + def load_cost_item_value_formula_attributes(cls, cost_value): pass + def load_cost_schedule_tree(cls): pass + def load_schedule_of_rates_tree(cls, schedule_of_rates): pass def play_chaching_sound(cls): pass + def print_all_cost_items(cls, cost_schedule): pass + def remove_cost_column(cls, name): pass @interface class Debug: diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index 9ab08465a0..f28eae6163 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -2,6 +2,7 @@ import os import bpy import blenderbim.tool as tool import ifcopenshell.util.date +import ifcopenshell.util.cost import blenderbim.bim.helper import json @@ -15,22 +16,34 @@ class Cost(blenderbim.core.tool.Cost): @classmethod def disable_editing_cost_schedule(cls): bpy.context.scene.BIMCostProperties.active_cost_schedule_id = 0 - + cls.disable_editing_cost_item() + @classmethod def enable_editing_cost_schedule_attributes(cls, cost_schedule): + bpy.context.scene.BIMCostProperties.active_cost_schedule_id = cost_schedule.id() + bpy.context.scene.BIMCostProperties.is_editing = "COST_SCHEDULE_ATTRIBUTES" + + @classmethod + def load_cost_schedule_attributes(cls, cost_schedule): def special_import(name, prop, data): if name in ["SubmittedOn", "UpdateDate"]: prop.string_value = "" if prop.is_null else ifcopenshell.util.date.ifc2datetime(data[name]).isoformat() return True - - bpy.context.scene.BIMCostProperties.active_cost_schedule_id = cost_schedule.id() - bpy.context.scene.BIMCostProperties.is_editing = "COST_SCHEDULE_ATTRIBUTES" - - @classmethod - def load_cost_schedule_attributes(cls, cost_schedule): + props = bpy.context.scene.BIMCostProperties props.cost_schedule_attributes.clear() - blenderbim.bim.helper.import_attributes(cost_schedule, props.cost_schedule_attributes) + blenderbim.bim.helper.import_attributes2(cost_schedule, props.cost_schedule_attributes, callback=special_import) + + @classmethod + def enable_editing_cost_items(cls, cost_schedule): + props = bpy.context.scene.BIMCostProperties + props.active_cost_schedule_id = cost_schedule.id() + props.is_editing = "COST_ITEMS" + + @classmethod + def play_sound(cls): + if bpy.context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound: + cls.play_chaching_sound() # lol @classmethod def play_chaching_sound(cls): @@ -48,57 +61,35 @@ class Cost(blenderbim.core.tool.Cost): handle_buffered.stop() except: pass # ah well - + @classmethod - def enable_editing_cost_items(cls, cost_schedule): + def load_cost_schedule_tree(cls): props = bpy.context.scene.BIMCostProperties props.is_cost_update_enabled = False - props.active_cost_schedule_id = cost_schedule.id() - props.is_editing = "COST_ITEMS" - props.is_cost_update_enabled = True - - @classmethod - def play_chaching_sound(cls): - if bpy.context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound: - cls.play_chaching_sound() # lol - - @classmethod - def load_cost_items(cls): - props = bpy.context.scene.BIMCostProperties cost_schedule = tool.Ifc.get().by_id(props.active_cost_schedule_id) props.cost_items.clear() if not hasattr(cls, "contracted_cost_items"): cls.contracted_cost_items = json.loads(props.contracted_cost_items) - for rel in cost_schedule.Controls or []: - for cost_item in rel.RelatedObjects: - cls.create_new_cost_item_li(props, cost_item, 0) - - @classmethod - def create_new_cost_item_li(cls, props, cost_item, level_index): - new = props.cost_items.add() - 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 cls.contracted_cost_items - new.level_index = level_index - if cost_item.IsNestedBy: - new.has_children = True - if new.is_expanded: - for rel in cost_item.IsNestedBy: - for sub_cost_item in rel.RelatedObjects: - cls.create_new_cost_item_li(props, sub_cost_item, level_index + 1) + [ + cls.create_new_cost_item_li(props.cost_items, cost_item, 0, type="cost") + for rel in cost_schedule.Controls or [] + for cost_item in rel.RelatedObjects or [] + ] + props.is_cost_update_enabled = True @classmethod def expand_cost_item(cls, cost_item): props = bpy.context.scene.BIMCostProperties - cls.contracted_cost_items = json.loads(props.contracted_cost_items) + if not hasattr(cls, "contracted_cost_items"): + cls.contracted_cost_items = json.loads(props.contracted_cost_items) cls.contracted_cost_items.remove(cost_item.id()) props.contracted_cost_items = json.dumps(cls.contracted_cost_items) @classmethod def expand_cost_items(cls): props = bpy.context.scene.BIMCostProperties - cls.contracted_cost_items = json.loads(props.contracted_cost_items) + if not hasattr(cls, "contracted_cost_items"): + cls.contracted_cost_items = json.loads(props.contracted_cost_items) for cost_item in props.cost_items: if cost_item.ifc_definition_id in cls.contracted_cost_items: cls.contracted_cost_items.remove(cost_item.ifc_definition_id) @@ -107,45 +98,48 @@ class Cost(blenderbim.core.tool.Cost): @classmethod def contract_cost_item(cls, cost_item): props = bpy.context.scene.BIMCostProperties - cls.contracted_cost_items = json.loads(props.contracted_cost_items) + if not hasattr(cls, "contracted_cost_items"): + cls.contracted_cost_items = json.loads(props.contracted_cost_items) cls.contracted_cost_items.append(cost_item.id()) props.contracted_cost_items = json.dumps(cls.contracted_cost_items) @classmethod def contract_cost_items(cls): props = bpy.context.scene.BIMCostProperties - cls.contracted_cost_items = json.loads(props.contracted_cost_items) + if not hasattr(cls, "contracted_cost_items"): + cls.contracted_cost_items = json.loads(props.contracted_cost_items) for cost_item in props.cost_items: if cost_item.ifc_definition_id not in cls.contracted_cost_items: cls.contracted_cost_items.append(cost_item.ifc_definition_id) props.contracted_cost_items = json.dumps(cls.contracted_cost_items) - + @classmethod def clean_up_cost_item_tree(cls, cost_item): props = bpy.context.scene.BIMCostProperties - cls.contracted_cost_items = json.loads(props.contracted_cost_items) + if not hasattr(cls, "contracted_cost_items"): + cls.contracted_cost_items = json.loads(props.contracted_cost_items) + if props.active_cost_item_id == cost_item.id(): + props.active_cost_item_id = 0 if props.active_cost_item_index in cls.contracted_cost_items: cls.contracted_cost_items.remove(props.active_cost_item_index) props.contracted_cost_items = json.dumps(cls.contracted_cost_items) bpy.ops.bim.enable_editing_cost_items(cost_schedule=props.active_cost_schedule_id) - if props.active_cost_item_id == cost_item.id(): - props.active_cost_item_id = 0 @classmethod def enable_editing_cost_item_attributes(cls, cost_item): bpy.context.scene.BIMCostProperties.active_cost_item_id = cost_item.id() bpy.context.scene.BIMCostProperties.cost_item_editing_type = "ATTRIBUTES" - + @classmethod def load_cost_item_attributes(cls, cost_item): props = bpy.context.scene.BIMCostProperties props.cost_item_attributes.clear() blenderbim.bim.helper.import_attributes2(cost_item, props.cost_item_attributes) - + @classmethod def disable_editing_cost_item(cls): bpy.context.scene.BIMCostProperties.active_cost_item_id = 0 - + @classmethod def get_cost_item_attributes(cls): props = bpy.context.scene.BIMCostProperties @@ -153,6 +147,382 @@ class Cost(blenderbim.core.tool.Cost): @classmethod def get_active_cost_item(cls): - if bpy.context.scene.BIMCostProperties.active_cost_item_id == 0: + props = bpy.context.scene.BIMCostProperties + if props.active_cost_item_id == 0: return None - return tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_item_id) \ No newline at end of file + return tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_item_id) + + @classmethod + def get_highlighted_cost_item(cls): + props = bpy.context.scene.BIMCostProperties + if props.active_cost_item_index < len(props.cost_items): + return tool.Ifc.get().by_id(props.cost_items[props.active_cost_item_index].ifc_definition_id) + return None + + @classmethod + def load_cost_item_types(cls, cost_item=None): + if not cost_item: + return + props = bpy.context.scene.BIMCostProperties + props.cost_item_type_products.clear() + # TODO implement process and resource types + # props.cost_item_processes.clear() + # props.cost_item_resources.clear() + for rel in cost_item.Controls or []: + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcTypeProduct"): + new = props.cost_item_type_products.add() + # TODO implement process and resource types + # elif related_object.is_a("IfcProcess"): + # new = props.cost_item_processes.add() + # elif related_object.is_a("IfcResource"): + # new = props.cost_item_resources.add() + new.ifc_definition_id = related_object.id() + new.name = related_object.Name or "Unnamed" + + @classmethod + def load_cost_item_quantities(cls, cost_item=None): + if not cost_item: + cost_item = cls.get_active_cost_item() + if not cost_item: + return + props = bpy.context.scene.BIMCostProperties + props.cost_item_products.clear() + props.cost_item_processes.clear() + props.cost_item_resources.clear() + + # 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 = props.cost_item_products.add() + elif related_object.is_a("IfcProcess"): + new = props.cost_item_processes.add() + elif related_object.is_a("IfcResource"): + new = 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 + + @classmethod + def get_products(cls, related_object_type=None): + props = bpy.context.scene.BIMCostProperties + if related_object_type == "PRODUCT": + products = tool.Spatial.get_selected_products() + elif related_object_type == "PROCESS": + products = [tool.Sequence.get_highlighted_task()] + elif related_object_type == "RESOURCE": + products = [tool.Resource.get_highlighted_resource()] + return products or [] + + @classmethod + def enable_editing_cost_item_quantities(cls, cost_item=None): + props = bpy.context.scene.BIMCostProperties + props.active_cost_item_id = cost_item.id() + props.cost_item_editing_type = "QUANTITIES" + + @classmethod + def enable_editing_cost_item_quantity(cls, physical_quantity=None): + bpy.context.scene.BIMCostProperties.active_cost_item_quantity_id = physical_quantity.id() + + @classmethod + def load_cost_item_quantity_attributes(cls, physical_quantity=None): + props = bpy.context.scene.BIMCostProperties + props.quantity_attributes.clear() + blenderbim.bim.helper.import_attributes2(physical_quantity, props.quantity_attributes) + + @classmethod + def enable_editing_cost_item_values(cls, cost_item=None): + props = bpy.context.scene.BIMCostProperties + props.active_cost_item_id = cost_item.id() + props.cost_item_editing_type = "VALUES" + + @classmethod + def disable_editing_cost_item_quantity(cls): + bpy.context.scene.BIMCostProperties.active_cost_item_quantity_id = 0 + + @classmethod + def get_cost_item_quantity_attributes(cls): + props = bpy.context.scene.BIMCostProperties + return blenderbim.bim.helper.export_attributes(props.quantity_attributes) + + @classmethod + def get_attributes_for_cost_value(cls, cost_type, cost_category): + if cost_type == "FIXED": + category = None + attributes = {"AppliedValue": 0.0} + elif cost_type == "SUM": + category = "*" + attributes = {"Category": category} + elif cost_type == "CATEGORY": + category = cost_category + attributes = {"Category": category} + return attributes + + @classmethod + def load_cost_item_value_attributes(cls, cost_value=None): + def import_attributes(name, prop, data, cost_value, is_rates, props_collection): + if name == "AppliedValue": + # TODO: for now, only support simple IfcValues (which are effectively IfcMonetaryMeasure) + prop = props_collection.add() + prop.data_type = "float" + prop.name = "AppliedValue" + prop.is_optional = True + prop.float_value = ( + 0.0 + if prop.is_null + else ifcopenshell.util.cost.calculate_applied_value( + tool.Ifc.get().by_id(props.active_cost_item_id), cost_value + ) + ) + return True + if name == "UnitBasis" and is_rates: + prop = props_collection.add() + prop.name = "UnitBasisValue" + prop.data_type = "float" + prop.is_null = data["UnitBasis"] is None + prop.is_optional = True + if data["UnitBasis"] and data["UnitBasis"].ValueComponent: + prop.float_value = data["UnitBasis"].ValueComponent.wrappedValue or 0 + else: + prop.float_value = 0 + + prop = props_collection.add() + prop.name = "UnitBasisUnit" + prop.data_type = "enum" + prop.is_null = prop.is_optional = False + units = {} + + def format_unit(unit): + if unit.is_a("IfcContextDependentUnit"): + return f"{unit.UnitType} / {unit.Name}" + else: + name = unit.Name + if unit.get_info().get("Prefix", None): + name = f"{unit.Prefix} {name}" + return f"{unit.UnitType} / {name}" + + for unit in tool.Ifc.get().by_type("IfcNamedUnit"): + if unit.get_info().get("UnitType", None) in [ + "AREAUNIT", + "LENGTHUNIT", + "TIMEUNIT", + "VOLUMEUNIT", + "MASSUNIT", + "USERDEFINED", + ]: + units[unit.id()] = format_unit(unit) + prop.enum_items = json.dumps(units) + if data["UnitBasis"] and data["UnitBasis"].UnitComponent: + # prop.enum_value = prop.enum_items[data["UnitBasis"].UnitComponent.id()] + print(prop.enum_items) + for key, value in json.loads(prop.enum_items).items(): + if value == format_unit(data["UnitBasis"].UnitComponent): + prop.enum_value = key + break + return True + + props = bpy.context.scene.BIMCostProperties + props.cost_value_attributes.clear() + is_rates = cls.is_active_schedule_of_rates() + callback = lambda name, prop, data: import_attributes( + name, prop, data, cost_value, is_rates, props.cost_value_attributes + ) + blenderbim.bim.helper.import_attributes2(cost_value, props.cost_value_attributes, callback=callback) + + @classmethod + def is_active_schedule_of_rates(cls): + return ( + tool.Ifc.get().by_id(bpy.context.scene.BIMCostProperties.active_cost_schedule_id).PredefinedType + == "SCHEDULEOFRATES" + ) + + @classmethod + def enable_editing_cost_item_value(cls, cost_value=None): + props = bpy.context.scene.BIMCostProperties + props.active_cost_value_id = cost_value.id() + props.cost_value_editing_type = "ATTRIBUTES" + + @classmethod + def disable_editing_cost_item_value(cls): + props = bpy.context.scene.BIMCostProperties + props.active_cost_value_id = 0 + props.cost_value_editing_type = "" + + @classmethod + def load_cost_item_value_formula_attributes(cls, cost_value=None): + props = bpy.context.scene.BIMCostProperties + props.cost_value_attributes.clear() # why is this necessary? + bpy.context.scene.BIMCostProperties.cost_value_formula = ifcopenshell.util.cost.serialise_cost_value(cost_value) + + @classmethod + def enable_editing_cost_item_value_formula(cls, cost_value=None): + props = bpy.context.scene.BIMCostProperties + props.active_cost_value_id = cost_value.id() + props.cost_value_editing_type = "FORMULA" + + @classmethod + def get_cost_item_value_formula(cls): + return bpy.context.scene.BIMCostProperties.cost_value_formula + + @classmethod + def get_cost_value_attributes(cls): + def export_attributes(attributes, prop): + if prop.name == "UnitBasisValue": + if prop.is_null: + attributes["UnitBasis"] = None + return True + attributes["UnitBasis"] = { + "ValueComponent": prop.float_value or 1, + "UnitComponent": cls.get_cost_value_unit_component(), + } + print(attributes) + return True + if prop.name == "UnitBasisUnit": + return True + + props = bpy.context.scene.BIMCostProperties + callback = lambda attributes, prop: export_attributes(attributes, prop) + return blenderbim.bim.helper.export_attributes(props.cost_value_attributes, callback) + + @classmethod + def get_cost_value_unit_component(cls): + return tool.Ifc.get().by_id( + int(bpy.context.scene.BIMCostProperties.cost_value_attributes.get("UnitBasisUnit").enum_value) + ) + + @classmethod + def get_direct_cost_item_products(cls, cost_item): + return [related_object for r in cost_item.Controls or [] for related_object in r.RelatedObjects] + + @classmethod + def get_cost_item_products(cls, cost_item, include_nested=False): + if not include_nested: + return cls.get_direct_cost_item_products(cost_item) + else: + return [ + product + for nested_cost_item in cls.get_all_nested_cost_items(cost_item) + for product in cls.get_direct_cost_item_products(nested_cost_item) + ] + + @classmethod + def get_all_nested_cost_items(cls, cost_item): + for cost_item in cls.get_nested_cost_items(cost_item): + yield cost_item + yield from cls.get_all_nested_cost_items(cost_item) + + @classmethod + def get_nested_cost_items(cls, cost_item): + return [obj for rel in cost_item.IsNestedBy for obj in rel.RelatedObjects] + + @classmethod + def print_all_cost_items(cls, cost_schedule): + print(cls.get_schedule_cost_items(cost_schedule)) + + @classmethod + def get_schedule_cost_items(cls, cost_schedule): + for cost_item in cls.get_root_cost_items(cost_schedule): + yield cost_item + yield from cls.get_all_nested_cost_items(cost_item) + + @classmethod + def get_root_cost_items(cls, cost_schedule): + return [ + related_object + for rel in cost_schedule.Controls or [] + for related_object in rel.RelatedObjects + if related_object.is_a("IfcCostItem") + ] + + @classmethod + def get_cost_schedule_products(cls, cost_schedule): + products = [] + for cost_item in cls.get_root_cost_items(cost_schedule): + products.extend(cls.get_cost_item_products(cost_item, include_nested=True)) + return products + + @classmethod + def import_cost_schedule_csv(cls, file_path=None, is_schedule_of_rates=False): + if not file_path: + return + from ifc5d.csv2ifc import Csv2Ifc + import time + + start = time.time() + csv2ifc = Csv2Ifc() + csv2ifc.csv = file_path + csv2ifc.file = tool.Ifc.get() + csv2ifc.is_schedule_of_rates = is_schedule_of_rates + csv2ifc.execute() + print("Import finished in {:.2f} seconds".format(time.time() - start)) + + @classmethod + def add_cost_column(cls, name): + props = bpy.context.scene.BIMCostProperties + new = props.columns.add() + new.name = name + + @classmethod + def remove_cost_column(cls, name): + props = bpy.context.scene.BIMCostProperties + props.columns.remove(props.columns.find(name)) + + @classmethod + def expand_cost_item_rate(cls, cost_item): + props = bpy.context.scene.BIMCostProperties + contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) + contracted_cost_item_rates.remove(cost_item) + props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) + cls.load_schedule_of_rates(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates))) + + @classmethod + def contract_cost_item_rate(cls, cost_item): + props = bpy.context.scene.BIMCostProperties + contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) + contracted_cost_item_rates.append(cost_item) + props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates) + cls.load_schedule_of_rates(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates))) + + @classmethod + def create_new_cost_item_li(cls, props_collection, cost_item, level_index, type="cost_rate"): + new = props_collection.add() + 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 cls.contracted_cost_item_rates + if type == "cost_rate" + else cost_item.id() not in cls.contracted_cost_items + ) + new.level_index = level_index + if cost_item.IsNestedBy: + new.has_children = True + if new.is_expanded: + [ + cls.create_new_cost_item_li(props_collection, sub_cost_item, level_index + 1, type) + for rel in cost_item.IsNestedBy + for sub_cost_item in rel.RelatedObjects + ] + + @classmethod + def load_schedule_of_rates_tree(cls, schedule_of_rates): + props = bpy.context.scene.BIMCostProperties + props.is_cost_update_enabled = False + props.cost_item_rates.clear() + props.columns.clear() + cls.contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates) + for rel in schedule_of_rates.Controls or []: + [ + cls.create_new_cost_item_li(props.cost_item_rates, cost_item, 0, type="cost_rate") + for cost_item in rel.RelatedObjects + ] + props.is_cost_update_enabled = True