diff --git a/src/blenderbim/blenderbim/bim/module/cost/data.py b/src/blenderbim/blenderbim/bim/module/cost/data.py index b9d4479103..1a370c801d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/data.py +++ b/src/blenderbim/blenderbim/bim/module/cost/data.py @@ -71,7 +71,7 @@ class CostSchedulesData: { "id": schedule.id(), "name": schedule.Name or "Unnamed", - "predefined_type":ifcopenshell.util.element.get_predefined_type(schedule), + "predefined_type": ifcopenshell.util.element.get_predefined_type(schedule), } ) return results @@ -109,6 +109,7 @@ class CostSchedulesData: data["UnitBasisUnitSymbol"] = None data["TotalAppliedValue"] = 0.0 data["TotalCost"] = 0.0 + has_unit_basis = False if root_element.is_a("IfcCostItem"): values = root_element.CostValues elif root_element.is_a("IfcConstructionResource"): @@ -121,10 +122,15 @@ class CostSchedulesData: 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"] + has_unit_basis = True + if has_unit_basis: + data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"] else: - data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"] + if data["TotalCostQuantity"] is not None: + data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"] + else: + data["TotalCost"] = data["TotalAppliedValue"] + data["TotalAppliedValue"] = None @classmethod def _load_cost_item_quantities(cls, cost_item, data): diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index eddc816e14..2976f45b89 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -128,6 +128,8 @@ class CostItemQuantity(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") total_quantity: FloatProperty(name="Total Quantity") + unit_symbol: StringProperty(name="Unit Symbol") + total_cost_quantity: FloatProperty(name="Total Quantity") class CostItemType(PropertyGroup): @@ -204,7 +206,7 @@ class BIMCostProperties(PropertyGroup): cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem) active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index") contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]") - product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItem) + product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItemQuantity) active_product_cost_item_index: IntProperty(name="Active Product Cost Item Index") enable_reorder: BoolProperty(name="Enable Reorder", default=False) show_nested_elements: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_elements) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 6456df8916..5d8a1b144d 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -59,7 +59,10 @@ class BIM_PT_cost_schedules(Panel): def draw_cost_schedule_ui(self, cost_schedule): row = self.layout.row(align=True) if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule["id"]: - row.label(text="Currently editing: {}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON") + row.label( + text="Currently editing: {}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), + icon="LINENUMBERS_ON", + ) grid = self.layout.grid_flow(columns=2, even_columns=True) col = grid.column() row1 = col.row(align=True) @@ -85,7 +88,9 @@ class BIM_PT_cost_schedules(Panel): row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK") row.operator("bim.disable_editing_cost_schedule", text="Disable Editing", icon="CANCEL") else: - row.label(text="{}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON") + row.label( + text="{}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON" + ) row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"] row.operator( "bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL" @@ -214,6 +219,7 @@ class BIM_PT_cost_schedules(Panel): blenderbim.bim.helper.draw_attributes(self.props.cost_value_attributes, self.layout.box()) def draw_readonly_cost_value_ui(self, layout, cost_value): + print(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: @@ -599,7 +605,7 @@ class BIM_UL_cost_items_trait: row.label(text="", icon="DOT") def draw_total_cost_column(self, layout, cost_item): - layout.label(text="{0:.2f}".format(cost_item["TotalCost"])) + layout.label(text="{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")) def draw_quantity_column(self, layout, cost_item): if CostSchedulesData.data["is_editing_rates"]: @@ -608,7 +614,11 @@ class BIM_UL_cost_items_trait: self.draw_total_quantity_column(layout, cost_item) def draw_value_column(self, layout, cost_item): - text = "{0:.2f}".format(cost_item["TotalAppliedValue"]) + text = ( + "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ") + if cost_item["TotalAppliedValue"] + else "-" + ) if cost_item["UnitBasisValueComponent"] not in [None, 1]: text += " / {}".format(round(cost_item["UnitBasisValueComponent"], 2)) layout.label(text=text) @@ -628,7 +638,12 @@ class BIM_UL_cost_items_trait: op.new_index = cost_item["NestingIndex"] - 1 def draw_total_quantity_column(self, layout, cost_item): - layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}") + label = ( + "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}" + if cost_item["TotalCostQuantity"] + else "-" + ) + layout.label(text=label) # if cost_item["DerivedTotalCostQuantity"] not in [None, 0]: # layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}") # else: @@ -685,14 +700,14 @@ class BIM_UL_cost_item_quantities(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): props = context.scene.BIMCostProperties cost_item = props.cost_items[props.active_cost_item_index] - if item: row = layout.row(align=True) op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF") op.product = item.ifc_definition_id row.split(factor=0.8) row.label(text=item.name) - row.label(text="{0:.2f}".format(item.total_quantity)) + formatted_quantity = "{0:.2f}".format(item.total_quantity) + row.label(text="{}{}".format(formatted_quantity, item.unit_symbol)) op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X") op.cost_item = cost_item.ifc_definition_id op.related_object = item.ifc_definition_id @@ -704,8 +719,11 @@ class BIM_UL_product_cost_items(UIList): row = layout.row(align=True) op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE") op.cost_item = item.ifc_definition_id - row.split(factor=0.8) + row.split(factor=0.5) row.label(text=item.name) + qty = "{0:.2f}".format(item.total_quantity) + formatted_total_quantity = "{0:.2f}".format(item.total_cost_quantity) + row.label(text="{}/{} {}".format(qty, formatted_total_quantity, item.unit_symbol)) class BIM_PT_Costing_Tools(Panel): diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index b32d9d84a7..e5d6139d4b 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -3,6 +3,7 @@ import bpy import blenderbim.tool as tool import ifcopenshell.util.date import ifcopenshell.util.cost +import ifcopenshell.util.unit import blenderbim.bim.helper import json @@ -188,8 +189,9 @@ class Cost(blenderbim.core.tool.Cost): new = collection.add() new.ifc_definition_id = product.id() new.name = product.Name or "Unnamed" - total_quantity = cls.calculate_parametric_quantity(cost_item, product) - new.total_quantity = total_quantity if total_quantity else 1 + total_quantity, unit = cls.calculate_parametric_quantity(cost_item, product) + new.total_quantity = total_quantity or 0 + new.unit_symbol = unit or "" if is_deep: for cost_item in ifcopenshell.util.cost.get_nested_cost_items(cost_item, is_deep): create_list_items(collection, cost_item, is_deep=False) @@ -210,16 +212,21 @@ class Cost(blenderbim.core.tool.Cost): @classmethod def calculate_parametric_quantity(cls, cost_item=None, product=None): - if not cost_item.CostQuantities: - return - total_quantity = sum( - quantity[3] - for quantities in ifcopenshell.util.element.get_psets(product, qtos_only=True).values() - for qto in (tool.Ifc.get().by_id(quantities["id"]).Quantities or []) - for quantity in cost_item.CostQuantities - if quantity == qto - ) - return total_quantity + quantities, unit = cls.get_assigned_quantities(cost_item, product) + return sum(quantity[3] for quantity in quantities), unit + + @classmethod + def get_assigned_quantities(cls, cost_item, product): + selected_quantitites = [] + unit = "" + for quantities in ifcopenshell.util.element.get_psets(product, qtos_only=True).values(): + for qto in (tool.Ifc.get().by_id(quantities["id"]).Quantities or []): + for quantity in cost_item.CostQuantities: + if quantity == qto: + selected_quantitites.append(quantity) + if not unit: + unit = cls.get_quantity_unit_symbol(quantity) + return selected_quantitites, unit @classmethod def get_products(cls, related_object_type=None): @@ -616,6 +623,18 @@ class Cost(blenderbim.core.tool.Cost): new = props.product_cost_items.add() new.name = cost_item.Name or "Unnamed" new.ifc_definition_id = cost_item.id() + quantity, unit = cls.calculate_parametric_quantity(cost_item, product) + new.total_quantity = quantity or 1 + new.unit_symbol = unit or "" + new.total_cost_quantity = ifcopenshell.util.cost.get_total_quantity(cost_item) + + @classmethod + def get_quantity_unit_symbol(cls, quantity): + unit = ifcopenshell.util.unit.get_property_unit(quantity, tool.Ifc.get()) + if unit: + return ifcopenshell.util.unit.get_unit_symbol(unit) + else: + return None @classmethod def is_root_cost_item(cls, cost_item): diff --git a/src/ifcopenshell-python/ifcopenshell/util/cost.py b/src/ifcopenshell-python/ifcopenshell/util/cost.py index bbdeb546b7..4d15ee8f33 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/cost.py +++ b/src/ifcopenshell-python/ifcopenshell/util/cost.py @@ -37,7 +37,7 @@ def get_primitive_applied_value(applied_value): 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 + return sum([q[3] for q in root_element.CostQuantities or []]) or None elif root_element.is_a("IfcConstructionResource"): return root_element.BaseQuantity[3] if root_element.BaseQuantity else 1.0 @@ -90,7 +90,7 @@ def sum_child_root_elements(root_element, category_filter=None): 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) + child_quantity = get_total_quantity(child_root_element) or 1.0 if child_cost_value.UnitBasis: value_component = child_cost_value.UnitBasis.ValueComponent.wrappedValue result += child_quantity / value_component * child_applied_value