mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +00:00
910 lines
38 KiB
Python
910 lines
38 KiB
Python
# Bonsai - OpenBIM Blender Add-on
|
|
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
|
#
|
|
# This file is part of Bonsai.
|
|
#
|
|
# Bonsai 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.
|
|
#
|
|
# Bonsai 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 Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import bpy
|
|
from bpy.types import Panel, UIList
|
|
|
|
import bonsai.bim.helper
|
|
import bonsai.bim.module.cost.prop as CostProp
|
|
import bonsai.tool as tool
|
|
from bonsai.bim.module.cost.data import CostItem, CostSchedulesData
|
|
|
|
if TYPE_CHECKING:
|
|
from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity
|
|
from bonsai.bim.prop import StrProperty
|
|
|
|
|
|
class BIM_PT_cost_schedules(Panel):
|
|
bl_label = "Cost Schedules"
|
|
bl_idname = "BIM_PT_cost_schedules"
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "scene"
|
|
bl_parent_id = "BIM_PT_tab_cost"
|
|
bl_options = {"HIDE_HEADER"}
|
|
|
|
layout: bpy.types.UILayout
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
file = tool.Ifc.get()
|
|
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
|
|
|
def draw(self, context):
|
|
if not CostSchedulesData.is_loaded:
|
|
CostSchedulesData.load()
|
|
|
|
self.props = tool.Cost.get_cost_props()
|
|
if not self.props.active_cost_schedule_id:
|
|
row = self.layout.row(align=True)
|
|
if CostSchedulesData.data["total_cost_schedules"]:
|
|
row.alignment = "RIGHT"
|
|
row.operator("bim.export_cost_schedules", icon="EXPORT", text="Export All Schedules")
|
|
row.operator("bim.export_cost_schedules_to_pdf", icon="OUTPUT", text="")
|
|
row = self.layout.row(align=True)
|
|
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
|
else:
|
|
row.label(text="No Cost Schedules found.", icon="TEXT")
|
|
|
|
row.operator("bim.add_cost_schedule", icon="ADD", text="")
|
|
row.operator("bim.import_cost_schedule_csv", icon="IMPORT", text="")
|
|
|
|
for schedule in CostSchedulesData.data["schedules"]:
|
|
self.draw_cost_schedule_ui(schedule)
|
|
|
|
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",
|
|
)
|
|
|
|
row0 = self.layout.row(align=True)
|
|
col = row0.column()
|
|
col.label(text="Linked CSV:")
|
|
row_1 = col.row(align=True)
|
|
|
|
csv_filepaths = CostSchedulesData.data["csv_filepaths"]
|
|
file_path = csv_filepaths.get(self.props.active_cost_schedule_id)
|
|
|
|
if file_path:
|
|
row_1.label(text=file_path)
|
|
row_1.operator("bim.refresh_cost_schedule_csv", icon="FILE_REFRESH", text="")
|
|
row_1.operator("bim.remove_cost_schedule_csv_link", icon="X", text="")
|
|
else:
|
|
row_1.label(text="No CSV file found")
|
|
row_1.operator("bim.import_cost_schedule_csv", icon="IMPORT", text="")
|
|
|
|
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
|
col = grid.column()
|
|
row1 = col.row(align=True)
|
|
row1.alignment = "LEFT"
|
|
row1.label(text="Schedule tools")
|
|
row1 = col.row(align=True)
|
|
row1.alignment = "RIGHT"
|
|
row1.operator("bim.export_cost_schedules", text="Export spreadsheet", icon="EXPORT").cost_schedule = (
|
|
cost_schedule["id"]
|
|
)
|
|
row1.operator(
|
|
"bim.generate_cost_schedule_browser", text="Generate spreadsheet browser", icon="URL"
|
|
).cost_schedule = cost_schedule["id"]
|
|
row2 = col.row(align=True)
|
|
row2.alignment = "RIGHT"
|
|
op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
|
|
op.cost_schedule = cost_schedule["id"]
|
|
row2.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
|
|
|
|
col = grid.column()
|
|
row1 = col.row(align=True)
|
|
row1.alignment = "LEFT"
|
|
row1.label(text="Settings")
|
|
row1 = col.row(align=True)
|
|
row1.alignment = "RIGHT"
|
|
row1.prop(self.props, "should_show_currency_ui", text="Project Currency", icon="COPY_ID")
|
|
row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY")
|
|
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
|
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
|
|
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
|
|
else:
|
|
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").cost_schedule = (
|
|
cost_schedule["id"]
|
|
)
|
|
row.operator("bim.copy_cost_schedule", text="", icon="DUPLICATE").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.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
|
self.draw_editable_cost_schedule_ui()
|
|
elif self.props.is_editing == "COST_ITEMS":
|
|
if self.props.should_show_column_ui:
|
|
self.draw_column_ui()
|
|
if self.props.should_show_currency_ui:
|
|
self.draw_currency_ui()
|
|
self.draw_editable_cost_item_ui()
|
|
|
|
def draw_column_ui(self) -> None:
|
|
row = self.layout.row(align=True)
|
|
row.prop(self.props, "cost_column", text="")
|
|
row.operator("bim.add_cost_column", text="", icon="ADD").name = self.props.cost_column
|
|
self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
|
|
|
|
def draw_currency_ui(self) -> None:
|
|
row = self.layout.row(align=True)
|
|
if CostSchedulesData.data["currency"]:
|
|
text = "Currency used: {}".format(CostSchedulesData.data["currency"]["name"])
|
|
row.label(text=text)
|
|
row.operator("bim.remove_unit", text="", icon="X").unit = CostSchedulesData.data["currency"]["id"]
|
|
else:
|
|
row.label(text="No currency set")
|
|
row.prop(self.props, "currency", text="")
|
|
if self.props.currency == "CUSTOM":
|
|
row.alignment = "RIGHT"
|
|
row.prop(self.props, "custom_currency", text="")
|
|
row.operator("bim.add_currency", text="", icon="ADD")
|
|
|
|
def draw_editable_cost_schedule_ui(self):
|
|
bonsai.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout)
|
|
|
|
def draw_editable_cost_item_ui(self) -> None:
|
|
row = self.layout.row(align=True)
|
|
row.alignment = "RIGHT"
|
|
ifc_definition_id = None
|
|
row = self.layout.row(align=True)
|
|
row.label(text="Cost Item Tools")
|
|
row = self.layout.row(align=True)
|
|
row.alignment = "RIGHT"
|
|
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
|
|
row.operator("bim.expand_cost_items", text="Expand All")
|
|
row.operator("bim.contract_cost_items", text="Contract All")
|
|
row = self.layout.row(align=True)
|
|
row.alignment = "RIGHT"
|
|
if self.props.cost_items and 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
|
|
if ifc_definition_id:
|
|
row.prop(self.props, "show_cost_item_operators", text="Edit", icon="DOWNARROW_HLT")
|
|
row.operator("bim.add_cost_item", text="Add", icon="ADD").cost_item = ifc_definition_id
|
|
row.operator("bim.copy_cost_item", text="Copy", icon="ADD")
|
|
row.operator("bim.remove_cost_item", text="Delete", icon="X").cost_item = ifc_definition_id
|
|
if self.props.show_cost_item_operators:
|
|
row = self.layout.row(align=True)
|
|
row.alignment = "RIGHT"
|
|
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
|
|
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
|
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
|
|
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
|
|
op.cost_item = ifc_definition_id
|
|
if self.props.active_cost_item_id == ifc_definition_id:
|
|
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
|
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
|
|
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
|
else:
|
|
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
|
|
op.cost_item = ifc_definition_id
|
|
|
|
BIM_UL_cost_items_trait.draw_header(self.layout)
|
|
self.layout.template_list(
|
|
"BIM_UL_cost_items",
|
|
"",
|
|
self.props,
|
|
"cost_items",
|
|
self.props,
|
|
"active_cost_item_index",
|
|
)
|
|
if self.props.active_cost_item_id:
|
|
cost_item = CostSchedulesData.data["cost_items"][ifc_definition_id]
|
|
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
|
self.draw_editable_cost_item_attributes_ui()
|
|
elif self.props.cost_item_editing_type == "QUANTITIES":
|
|
self.draw_editable_cost_item_quantities_ui(cost_item)
|
|
elif self.props.cost_item_editing_type == "VALUES":
|
|
self.draw_editable_cost_item_values_ui()
|
|
|
|
def draw_editable_cost_item_attributes_ui(self) -> None:
|
|
bonsai.bim.helper.draw_attributes(self.props.cost_item_attributes, self.layout)
|
|
|
|
def draw_editable_cost_item_quantities_ui(self, cost_item: dict[str, Any]):
|
|
quantities = CostSchedulesData.data["cost_quantities"]
|
|
row = self.layout.row(align=True)
|
|
# In IFC, all quantities of IfcCostTime should have 1 type.
|
|
if quantities and cost_item.get("QuantityType"):
|
|
quantity_class = cost_item["QuantityType"]
|
|
row.label(text=quantity_class)
|
|
else:
|
|
row.prop(self.props, "quantity_types", text="")
|
|
quantity_class = self.props.quantity_types
|
|
|
|
op = row.operator("bim.add_cost_item_quantity", text="", icon="ADD")
|
|
op.cost_item = self.props.active_cost_item_id
|
|
op.ifc_class = quantity_class
|
|
|
|
for quantity in quantities:
|
|
row = self.layout.row(align=True)
|
|
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"]
|
|
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"]
|
|
else:
|
|
op = row.operator("bim.enable_editing_cost_item_quantity", text="", icon="GREASEPENCIL")
|
|
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"]
|
|
|
|
if self.props.active_cost_item_quantity_id and self.props.active_cost_item_quantity_id == quantity["id"]:
|
|
bonsai.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)
|
|
row.prop(self.props, "cost_types", text="")
|
|
if self.props.cost_types == "FIXED":
|
|
row.prop(self.props, "fixed_cost_value", text="")
|
|
if self.props.cost_types == "CATEGORY":
|
|
row.prop(self.props, "cost_category", text="")
|
|
op = row.operator("bim.add_cost_value", text="Add Value", icon="ADD")
|
|
op.parent = self.props.active_cost_item_id
|
|
op.cost_type = self.props.cost_types
|
|
if self.props.cost_types == "CATEGORY":
|
|
op.cost_category = self.props.cost_category
|
|
|
|
for cost_value in CostSchedulesData.data["cost_values"]:
|
|
row = self.layout.row(align=True)
|
|
self.draw_readonly_cost_value_ui(row, cost_value)
|
|
|
|
if self.props.cost_value_editing_type == "ATTRIBUTES":
|
|
bonsai.bim.helper.draw_attributes(self.props.cost_value_attributes, self.layout.box())
|
|
|
|
def draw_readonly_cost_value_ui(self, layout, cost_value):
|
|
if cost_value["name"]:
|
|
layout.label(text=f"{cost_value['name']}: ")
|
|
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:
|
|
layout.label(text=cost_value["label"], icon="DISC")
|
|
|
|
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:
|
|
if self.props.cost_value_editing_type == "ATTRIBUTES":
|
|
op = layout.operator("bim.edit_cost_value", text="", icon="CHECKMARK")
|
|
op.cost_value = cost_value_id
|
|
elif self.props.cost_value_editing_type == "FORMULA":
|
|
op = layout.operator("bim.edit_cost_value_formula", text="", icon="CHECKMARK")
|
|
op.cost_value = cost_value_id
|
|
layout.operator("bim.disable_editing_cost_item_value", text="", icon="CANCEL")
|
|
elif self.props.active_cost_value_id:
|
|
op = layout.operator("bim.remove_cost_value", text="", icon="X")
|
|
op.parent = parent_id
|
|
op.cost_value = cost_value_id
|
|
else:
|
|
op = layout.operator("bim.enable_editing_cost_item_value_formula", text="", icon="CON_TRANSLIKE")
|
|
op.cost_value = cost_value_id
|
|
op = layout.operator("bim.enable_editing_cost_item_value", text="", icon="GREASEPENCIL")
|
|
op.cost_value = cost_value_id
|
|
op = layout.operator("bim.remove_cost_value", text="", icon="X")
|
|
op.parent = parent_id
|
|
op.cost_value = cost_value_id
|
|
|
|
|
|
class BIM_PT_cost_item_types(Panel):
|
|
bl_label = "Cost Item Types"
|
|
bl_idname = "BIM_PT_cost_item_types"
|
|
bl_options = {"DEFAULT_CLOSED"}
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "scene"
|
|
bl_parent_id = "BIM_PT_cost_schedules"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
props = tool.Cost.get_cost_props()
|
|
total_cost_items = len(props.cost_items)
|
|
if not props.active_cost_schedule_id:
|
|
return False
|
|
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
|
|
return False
|
|
|
|
def draw(self, context):
|
|
self.props = tool.Cost.get_cost_props()
|
|
cost_item = self.props.cost_items[self.props.active_cost_item_index]
|
|
grid = self.layout.grid_flow(columns=3, even_columns=True)
|
|
|
|
# Column1
|
|
col = grid.column()
|
|
|
|
row2 = col.row(align=True)
|
|
row2.label(text="Elements")
|
|
op = row2.operator("bim.assign_cost_item_type", text="", icon="ADD")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op = row2.operator("bim.unassign_cost_item_type", text="", icon="REMOVE")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.related_object = 0
|
|
op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
|
|
row2 = col.row()
|
|
row2.template_list(
|
|
"BIM_UL_cost_item_types",
|
|
"",
|
|
self.props,
|
|
"cost_item_type_products",
|
|
self.props,
|
|
"active_cost_item_type_product_index",
|
|
)
|
|
|
|
# Column2
|
|
# TODO
|
|
col = grid.column()
|
|
|
|
row2 = col.row(align=True)
|
|
row2.label(text="Tasks")
|
|
|
|
row2 = col.row()
|
|
row2.template_list(
|
|
BIM_UL_cost_item_quantities.__name__,
|
|
BIM_UL_cost_item_quantities.__name__ + "_cost_item_processes",
|
|
self.props,
|
|
"cost_item_processes",
|
|
self.props,
|
|
"active_cost_item_process_index",
|
|
)
|
|
|
|
# Column3
|
|
# TODO
|
|
col = grid.column()
|
|
|
|
has_quantity_names = CostProp.get_resource_quantity_names(self, context)
|
|
|
|
row2 = col.row(align=True)
|
|
# row2.label(text="Resources")
|
|
total_cost_item_resources = len(self.props.cost_item_resources)
|
|
row2.label(text="Resources({})".format(total_cost_item_resources))
|
|
|
|
op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
|
|
rprops = tool.Resource.get_resource_props()
|
|
rtprops = rprops.tree
|
|
if rtprops.resources and rprops.active_resource_index < len(rtprops.resources):
|
|
if has_quantity_names:
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
|
op.related_object_type = "RESOURCE"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = self.props.resource_quantity_names
|
|
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
|
|
op.related_object_type = "RESOURCE"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = ""
|
|
|
|
row2 = col.row()
|
|
row2.template_list(
|
|
BIM_UL_cost_item_quantities.__name__,
|
|
BIM_UL_cost_item_quantities.__name__ + "_cost_item_resources",
|
|
self.props,
|
|
"cost_item_resources",
|
|
self.props,
|
|
"active_cost_item_resource_index",
|
|
)
|
|
|
|
|
|
class BIM_PT_cost_item_quantities(Panel):
|
|
bl_label = "Cost Item Quantities"
|
|
bl_idname = "BIM_PT_cost_item_quantities"
|
|
bl_options = {"DEFAULT_CLOSED"}
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "scene"
|
|
bl_parent_id = "BIM_PT_cost_schedules"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
props = tool.Cost.get_cost_props()
|
|
total_cost_items = len(props.cost_items)
|
|
if not props.active_cost_schedule_id:
|
|
return False
|
|
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):
|
|
self.props = tool.Cost.get_cost_props()
|
|
|
|
cost_item = self.props.cost_items[self.props.active_cost_item_index]
|
|
|
|
grid = self.layout.grid_flow(columns=3, even_columns=True)
|
|
|
|
# Column1
|
|
col = grid.column()
|
|
|
|
has_quantity_names = CostProp.get_product_quantity_names(self, context)
|
|
|
|
row2 = col.row(align=True)
|
|
total_cost_item_products = len(self.props.cost_item_products)
|
|
row2.label(text="Elements ({})".format(total_cost_item_products))
|
|
op = row2.operator("bim.select_cost_item_products", icon="RESTRICT_SELECT_OFF", text="")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
|
|
if context.selected_objects:
|
|
if has_quantity_names:
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
|
op.related_object_type = "PRODUCT"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = self.props.product_quantity_names
|
|
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
|
|
op.related_object_type = "PRODUCT"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = ""
|
|
|
|
op = row2.operator("bim.unassign_cost_item_quantity", text="", icon="REMOVE")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.related_object = 0
|
|
|
|
row2 = col.row()
|
|
row2.prop(self.props, "show_nested_elements", text="Show nested")
|
|
row2 = col.row()
|
|
row2.template_list(
|
|
BIM_UL_cost_item_quantities.__name__,
|
|
BIM_UL_cost_item_quantities.__name__ + "_cost_item_products",
|
|
self.props,
|
|
"cost_item_products",
|
|
self.props,
|
|
"active_cost_item_product_index",
|
|
)
|
|
|
|
row2 = col.row()
|
|
op = row2.operator("bim.clear_cost_item_assignments", text="Clear assignments", icon="X")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.related_object_type = "PRODUCT"
|
|
|
|
if has_quantity_names:
|
|
row2 = col.row()
|
|
row2.prop(self.props, "product_quantity_names", text="")
|
|
|
|
# Column2
|
|
col = grid.column()
|
|
|
|
has_quantity_names = CostProp.get_process_quantity_names(self, context)
|
|
|
|
row2 = col.row(align=True)
|
|
total_cost_item_processes = len(self.props.cost_item_processes)
|
|
row2.label(text="Tasks ({})".format(total_cost_item_processes))
|
|
|
|
tprops = tool.Sequence.get_task_tree_props()
|
|
wprops = tool.Sequence.get_work_schedule_props()
|
|
if tprops.tasks and wprops.active_task_index < len(tprops.tasks):
|
|
if has_quantity_names:
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
|
op.related_object_type = "PROCESS"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = self.props.process_quantity_names
|
|
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
|
|
op.related_object_type = "PROCESS"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = ""
|
|
|
|
row2 = col.row()
|
|
row2.prop(self.props, "show_nested_tasks", text="Show nested")
|
|
|
|
row2 = col.row()
|
|
row2.template_list(
|
|
BIM_UL_cost_item_quantities.__name__,
|
|
BIM_UL_cost_item_quantities.__name__ + "_cost_item_processes",
|
|
self.props,
|
|
"cost_item_processes",
|
|
self.props,
|
|
"active_cost_item_process_index",
|
|
)
|
|
|
|
row2 = col.row()
|
|
op = row2.operator("bim.clear_cost_item_assignments", text="Clear assignments", icon="X")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.related_object_type = "PROCESS"
|
|
|
|
if has_quantity_names:
|
|
row2 = col.row()
|
|
row2.prop(self.props, "process_quantity_names", text="")
|
|
|
|
# Column3
|
|
col = grid.column()
|
|
|
|
has_quantity_names = CostProp.get_resource_quantity_names(self, context)
|
|
|
|
row2 = col.row(align=True)
|
|
total_cost_item_resources = len(self.props.cost_item_resources)
|
|
row2.label(text="Resources ({})".format(total_cost_item_resources))
|
|
|
|
op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
|
|
active_resource = tool.Resource.get_resource_props().active_resource
|
|
if active_resource:
|
|
if has_quantity_names:
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES")
|
|
op.related_object_type = "RESOURCE"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = self.props.resource_quantity_names
|
|
|
|
op = row2.operator("bim.assign_cost_item_quantity", text="", icon="ADD")
|
|
op.related_object_type = "RESOURCE"
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.prop_name = ""
|
|
|
|
row2 = col.row()
|
|
row2.prop(self.props, "show_nested_resources", text="Show nested")
|
|
|
|
row2 = col.row()
|
|
row2.template_list(
|
|
BIM_UL_cost_item_quantities.__name__,
|
|
BIM_UL_cost_item_quantities.__name__ + "_cost_item_resources",
|
|
self.props,
|
|
"cost_item_resources",
|
|
self.props,
|
|
"active_cost_item_resource_index",
|
|
)
|
|
|
|
row2 = col.row()
|
|
op = row2.operator("bim.clear_cost_item_assignments", text="Clear assignments", icon="X")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.related_object_type = "RESOURCE"
|
|
|
|
if has_quantity_names:
|
|
row2 = col.row()
|
|
row2.prop(self.props, "resource_quantity_names", text="")
|
|
|
|
|
|
class BIM_PT_cost_item_rates(Panel):
|
|
bl_label = "Cost Item Rates"
|
|
bl_idname = "BIM_PT_cost_item_rates"
|
|
bl_options = {"DEFAULT_CLOSED"}
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "scene"
|
|
bl_parent_id = "BIM_PT_cost_schedules"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
props = tool.Cost.get_cost_props()
|
|
total_cost_items = len(props.cost_items)
|
|
if not props.active_cost_schedule_id:
|
|
return False
|
|
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):
|
|
self.props = tool.Cost.get_cost_props()
|
|
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 = cost_item.ifc_definition_id
|
|
op.cost_rate = cost_item_rate.ifc_definition_id
|
|
self.layout.template_list(
|
|
"BIM_UL_cost_item_rates",
|
|
"",
|
|
self.props,
|
|
"cost_item_rates",
|
|
self.props,
|
|
"active_cost_item_rate_index",
|
|
)
|
|
|
|
|
|
class BIM_UL_cost_items_trait:
|
|
@classmethod
|
|
def draw_header(cls, layout: bpy.types.UILayout):
|
|
row = layout.row(align=True)
|
|
|
|
split1 = row.split(factor=0.1)
|
|
split1.label(text="ID")
|
|
|
|
split2 = split1.split(factor=0.5)
|
|
split2.alignment = "RIGHT"
|
|
split2.label(text="Name")
|
|
if CostSchedulesData.data["is_editing_rates"]:
|
|
split2.label(text="Unit")
|
|
else:
|
|
split2.label(text="Quantity")
|
|
split2.label(text="Value")
|
|
|
|
props = tool.Cost.get_cost_props()
|
|
for column in props.columns:
|
|
split2.label(text=column.name)
|
|
split2.label(text="Total Cost")
|
|
split2.alignment = "LEFT"
|
|
split2.label(text="Rate")
|
|
|
|
def draw_item(
|
|
self,
|
|
context,
|
|
layout: bpy.types.UILayout,
|
|
data: BIMCostProperties,
|
|
item: CostProp.CostItem,
|
|
icon,
|
|
active_data,
|
|
active_propname,
|
|
) -> None:
|
|
if item:
|
|
self.props = data
|
|
cost_item = CostSchedulesData.data["cost_items"][item.ifc_definition_id]
|
|
row = layout.row(align=True)
|
|
|
|
self.draw_hierarchy(row, item)
|
|
|
|
split1 = row.split(factor=0.1)
|
|
split1.prop(item, "identification", emboss=False, text="")
|
|
split2 = split1.split(factor=0.5)
|
|
split2.alignment = "RIGHT"
|
|
split2.prop(item, "name", emboss=False, text="")
|
|
|
|
self.draw_quantity_column(split2, cost_item)
|
|
self.draw_value_column(split2, cost_item)
|
|
if self.props.active_cost_item_id == item.ifc_definition_id:
|
|
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
|
|
|
for column in self.props.columns:
|
|
split2.label(text=str(cost_item["CategoryValues"].get(column.name, "-")))
|
|
|
|
self.draw_total_cost_column(split2, cost_item)
|
|
if self.props.enable_reorder:
|
|
self.draw_order_operator(row, item.ifc_definition_id, cost_item)
|
|
|
|
if self.props.change_cost_item_parent:
|
|
self.draw_parent_operator(row, item.ifc_definition_id)
|
|
|
|
self.draw_assigned_rate_column(split2, cost_item)
|
|
|
|
# TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX
|
|
|
|
def draw_parent_operator(self, row: bpy.types.UILayout, cost_item_id: int) -> None:
|
|
if self.props.active_cost_item_id:
|
|
if self.props.active_cost_item_id != cost_item_id:
|
|
op = row.operator("bim.change_parent_cost_item", text="", icon="LINKED", emboss=False).new_parent = (
|
|
cost_item_id
|
|
)
|
|
else:
|
|
row.label(text="", icon="BLANK1")
|
|
|
|
def draw_hierarchy(self, row: bpy.types.UILayout, item: CostProp.CostItem) -> None:
|
|
for i in range(0, item.level_index):
|
|
row.label(text="", icon="BLANK1")
|
|
if item.has_children:
|
|
if item.is_expanded:
|
|
op = row.operator(self.contract_operator, text="", emboss=False, icon="DISCLOSURE_TRI_DOWN")
|
|
else:
|
|
op = row.operator(self.expand_operator, text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
|
|
op.cost_item = item.ifc_definition_id
|
|
else:
|
|
row.label(text="", icon="DOT")
|
|
|
|
def draw_quantity_column(self, layout, cost_item):
|
|
if CostSchedulesData.data["is_editing_rates"]:
|
|
self.draw_uom_column(layout, cost_item)
|
|
else:
|
|
self.draw_total_quantity_column(layout, cost_item)
|
|
|
|
def draw_uom_column(self, layout, cost_item):
|
|
layout.label(text=cost_item["UnitBasisUnitSymbol"])
|
|
|
|
def draw_total_quantity_column(self, layout, cost_item):
|
|
if cost_item["TotalCostQuantity"] is not None:
|
|
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
|
layout.label(text=label)
|
|
else:
|
|
layout.label(text="-")
|
|
|
|
def draw_assigned_rate_column(self, layout, cost_item):
|
|
row = layout.row()
|
|
row.alignment = "LEFT"
|
|
if cost_item["AssignedCostRate"] is not None:
|
|
op = row.operator("bim.show_assigned_cost_rate", text="", emboss=False, icon="ZOOM_IN")
|
|
identification = cost_item["AssignedCostRate"].Identification
|
|
op.parent_cost_schedule_name = CostSchedulesData.data["cost_items"][cost_item["AssignedCostRate"].id()][
|
|
"ParentCostSchedule"
|
|
].Name
|
|
op.assigned_rate_identification = identification if identification is not None else "XXX"
|
|
op.assigned_rate_name = cost_item["AssignedCostRate"].Name or ""
|
|
op.assigned_rate_description = cost_item["AssignedCostRate"].Description or ""
|
|
op.assigned_rate_total_value = CostSchedulesData.data["cost_items"][cost_item["AssignedCostRate"].id()][
|
|
"TotalAppliedValue"
|
|
]
|
|
else:
|
|
row.label(text="-")
|
|
|
|
def draw_value_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
|
|
if cost_item["TotalAppliedValue"]:
|
|
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
|
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
|
|
text = "{} / {}".format(text, round(cost_item["UnitBasisValueComponent"], 2))
|
|
currency = CostSchedulesData.data["currency"]
|
|
text = "{} {}".format(text, currency["name"]) if currency else text
|
|
layout.label(text=text)
|
|
else:
|
|
layout.label(text="-")
|
|
|
|
def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
|
|
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
|
currency = CostSchedulesData.data["currency"]
|
|
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
|
layout.label(text=text)
|
|
|
|
def draw_order_operator(self, row: bpy.types.UILayout, ifc_definition_id: int, cost_item: CostItem) -> None:
|
|
if cost_item["NestingIndex"] is not None:
|
|
if cost_item["NestingIndex"] == 0:
|
|
op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_DOWN", text="")
|
|
op.cost_item = ifc_definition_id
|
|
op.new_index = cost_item["NestingIndex"] + 1
|
|
elif cost_item["NestingIndex"] > 0:
|
|
op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_UP", text="")
|
|
op.cost_item = ifc_definition_id
|
|
op.new_index = cost_item["NestingIndex"] - 1
|
|
|
|
|
|
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
|
|
contract_operator = "bim.contract_cost_item"
|
|
expand_operator = "bim.expand_cost_item"
|
|
|
|
|
|
class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList):
|
|
# A schedule of rates UIList is identical to a regular cost items UIList but
|
|
# we want a separate UIList instance so that you can browse both lists
|
|
# independently in Blender. So we use a trait.
|
|
contract_operator = "bim.contract_cost_item_rate"
|
|
expand_operator = "bim.expand_cost_item_rate"
|
|
|
|
def draw_quantity_column(self, layout, cost_item):
|
|
self.draw_uom_column(layout, cost_item)
|
|
|
|
def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None:
|
|
pass # No such thing as a total cost in a schedule of rates
|
|
|
|
|
|
class BIM_UL_cost_columns(UIList):
|
|
def draw_item(
|
|
self, context, layout: bpy.types.UILayout, data, item: StrProperty, icon, active_data, active_propname
|
|
) -> None:
|
|
if item:
|
|
row = layout.row(align=True)
|
|
row.prop(item, "name", emboss=False, text="")
|
|
row.operator("bim.remove_cost_column", text="", icon="X").name = item.name
|
|
|
|
|
|
class BIM_UL_cost_item_types(UIList):
|
|
def draw_item(
|
|
self,
|
|
context,
|
|
layout: bpy.types.UILayout,
|
|
data: BIMCostProperties,
|
|
item: CostProp.CostItemType,
|
|
icon,
|
|
active_data,
|
|
active_propname,
|
|
) -> None:
|
|
cost_item = data.cost_items[data.active_cost_item_index]
|
|
|
|
if item:
|
|
row = layout.row(align=True)
|
|
row.label(text=item.name)
|
|
op = row.operator("bim.unassign_cost_item_type", text="", icon="X")
|
|
op.cost_item = cost_item.ifc_definition_id
|
|
op.related_object = item.ifc_definition_id
|
|
|
|
|
|
class BIM_UL_cost_item_quantities(UIList):
|
|
def draw_item(
|
|
self,
|
|
context: bpy.types.Context,
|
|
layout: bpy.types.UILayout,
|
|
data: BIMCostProperties,
|
|
item: CostItemQuantity,
|
|
icon,
|
|
active_data,
|
|
active_propname,
|
|
):
|
|
cost_item = data.cost_items[data.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)
|
|
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
|
|
|
|
|
|
class BIM_UL_product_cost_items(UIList):
|
|
def draw_item(
|
|
self,
|
|
context,
|
|
layout: bpy.types.UILayout,
|
|
data,
|
|
item: CostItemQuantity,
|
|
icon,
|
|
active_data,
|
|
active_propname,
|
|
) -> None:
|
|
if item:
|
|
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.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):
|
|
bl_label = "5D Tools"
|
|
bl_idname = "BIM_PT_Costing_Tools"
|
|
bl_options = {"DEFAULT_CLOSED"}
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "scene"
|
|
bl_parent_id = "BIM_PT_tab_cost"
|
|
|
|
def draw(self, context):
|
|
props = tool.Cost.get_cost_props()
|
|
row = self.layout.row()
|
|
row.operator("bim.load_product_cost_items", icon="FILE_REFRESH")
|
|
row = self.layout.row()
|
|
row.template_list(
|
|
"BIM_UL_product_cost_items",
|
|
"",
|
|
props,
|
|
"product_cost_items",
|
|
props,
|
|
"active_product_cost_item_index",
|
|
)
|