web ui cost module features:

- improve UI for editing cost quantites
- add, edit, delete manual quantities
This commit is contained in:
myoualid
2024-09-11 19:00:15 +01:00
parent 97393eb173
commit 1964def3ca
5 changed files with 683 additions and 144 deletions
@@ -20,6 +20,22 @@ body {
box-sizing: border-box;
}
.floating-form {
width: 50vw;
max-height: 80vh;
position: absolute;
background-color: var(--background-color);
border: 1px solid black;
z-index: 9999;
cursor: move;
user-select: none;
padding-bottom: var(--padding-medium);
}
[id^="enable-editing-quantities"].floating-form {
height: 70vh;
}
.form-header {
display: flex;
justify-content: flex-start;
@@ -30,36 +46,44 @@ body {
font-size: large;
border-bottom: 1px solid #ddd;
border-radius: 6px 6px 0 0;
height: 5%;
}
.floating-form span {
padding: var(--padding-small);
margin-left: var(--margin-small);
.form-container {
height: 95%;
padding-left: var(--padding-medium);
padding-right: var(--padding-medium);
}
.floating-form {
width: 50vw;
max-height: 80vh;
position: absolute;
background-color: var(--background-color);
border: 1px solid black;
z-index: 9999;
cursor: move;
user-select: none;
resize: both;
.form-section {
margin-bottom: 1em;
height: 65%;
overflow-y: auto;
overflow-x: hidden;
}
.form-section h3 {
margin-bottom: 0.5em;
}
.summary-section {
margin-top: 1em;
}
.summary-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
text-align: left;
}
.floating-form table tr {
margin-bottom: 10px;
}
.form-container {
padding: var(--padding-medium);
overflow-y: auto;
overflow-x: hidden;
}
.action-button {
position:relative;
background-color: var(--button-background);
border: none;
color: #fff;
@@ -72,6 +96,32 @@ body {
transition: background-color 0.3s ease;
width: fit-content;
margin: 0.5em;
z-index: 11000;
}
.action-button::after {
content: attr(data-tooltip);
position: absolute;
bottom: -30px;
background-color: #333;
color: #fff;
padding: 5px 10px;
border-radius: 5px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
font-size: 12px;
}
.action-button:hover::after {
opacity: 1;
}
.active-btn {
background-color: #887821;
/* make a nic shadow */
box-shadow: 0 0 10px #887821;
}
.action-button:hover {
@@ -141,7 +191,7 @@ body {
}
.floating-form .table-container {
max-height: 70%;
max-height: 80%;
}
#cost-items {
@@ -149,7 +199,6 @@ body {
}
[id^="cost-values-form"] table {
/* fixed */
max-height: 50%;
table-layout: fixed;
}
@@ -186,6 +235,7 @@ td,
th {
border-right: 1px solid #ddd;
text-align: center;
height: 100%;
}
th {
@@ -221,15 +271,7 @@ select {
color: var(--blender-button-text, var(--text-color));
border-color: var(--blender-button-border, var(--border-color));
transition: filter 0.2s ease;
border-radius: 6px;
padding: 6px 8px;
font-size: 14px;
line-height: 20px;
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
margin-left: 0.5em;
margin-right: 0.5em;
font-size: var(--fontSize);
min-width: 50px;
}
input:focus {
@@ -239,6 +281,31 @@ input:focus {
color: #ddd;
}
#cost-items input, #cost-items
select {
border-radius: 6px;
padding: 6px 8px;
font-size: 14px;
line-height: 20px;
margin-left: 0.5em;
margin-right: 0.5em;
font-size: var(--fontSize);
min-width: 50px;
}
form input {
border-radius: 6px;
padding: 3px 4px;
font-size: 12px;
line-height: 10px;
margin-left: 0.2em;
margin-right: 0.2em;
font-size: inherit;
height: inherit;
width: inherit;
box-sizing: border-box;
}
#cost-items tr:hover,
.highlighted {
background-color: #28a74657;
@@ -332,7 +399,7 @@ form table {
}
.clickable-cell:hover {
border: 1px solid #28a746;
border: 1px solid #94a728;
border-radius: 6px;
cursor: pointer;
}
}
@@ -36,19 +36,51 @@ function handleEditQuantities(data) {
const products = data.data["quantities"]["selected_products"];
const assigned_products = data.data["quantities"]["assigned_products"];
const quantityNames = data.data["quantities"]["product_quantity_names"];
const costQuantities = data.data["quantities"]["cost_quantities"];
CostUI.editQuantities({
CostUI.enableEditingQuantities({
costItemId: costItemId,
selectedProducts: products,
assignedProducts: assigned_products,
quantityNames: quantityNames,
costQuantities: costQuantities,
callbacks: {
addProductAssignments: addProductAssignments,
enableEditingQuantities: enableEditingQuantities,
addQuantity: addQuantity,
editQuantity: editQuantity,
deleteQuantity: deleteQuantity,
},
});
}
function addQuantity(costItemId, ifcClass) {
executeOperator({
type: "AddCostItemQuantity",
costItemId: costItemId,
ifcClass: ifcClass,
});
//CostUI.addQuantity(costItemId, quantityName);
}
function editQuantity(costItemId, quantityId, attributes) {
executeOperator({
type: "editCostItemQuantity",
costItemId: costItemId,
quantityId: quantityId,
attributes: attributes,
});
}
function deleteQuantity(costItemId, quantityId) {
executeOperator({
type: "deleteCostItemQuantity",
costItemId: costItemId,
quantityId: quantityId,
});
}
function handlePredefinedTypes(data) {
CostUI.enableAddingCostSchedule(
data.data["predefined_types"],
@@ -793,11 +793,15 @@ export class CostUI {
}
static getRowNameCell(costItemId) {
return document.getElementById(costItemId).querySelector("td input");
return document.getElementById(costItemId)
? document.getElementById(costItemId).querySelector("td input")
: null;
}
static getCostItemName(costItemId) {
return CostUI.getRowNameCell(costItemId).value;
return CostUI.getRowNameCell(costItemId)
? CostUI.getRowNameCell(costItemId).value
: "Unnamed";
}
static createCostValuesForm({ costItemId, costValues, callbacks }) {
@@ -840,7 +844,6 @@ export class CostUI {
static addNewCostValueRow(costItemId, costValueId, costValueCallbacks) {
const table = CostUI.getCostValuesTable(costItemId);
if (!table) {
console.log("Cost values table not found for cost item ID:", costItemId);
return;
}
const tr = CostUI.createCostvaluesRow(
@@ -908,8 +911,8 @@ export class CostUI {
} else if (costValue.applied_value) {
costType = "FIXED";
}
const typeCell = CostUI.createTableDropdown("type", costType);
const options = ["FIXED", "CATEGORY", "SUM"];
const typeCell = CostUI.createTableDropdown("type", options, costType);
const dropdown = typeCell.querySelector("select");
dropdown.addEventListener("change", function () {
const selectedType = this.value;
@@ -1012,12 +1015,11 @@ export class CostUI {
}
}
static createTableDropdown(name, value = "FIXED") {
static createTableDropdown(name, options, value = "FIXED") {
const cell = document.createElement("td");
const dropdown = document.createElement("select");
dropdown.name = name;
const options = ["FIXED", "CATEGORY", "SUM"];
options.forEach((optionValue) => {
const option = document.createElement("option");
option.value = optionValue;
@@ -1306,7 +1308,7 @@ export class CostUI {
}
static createProductTable({
form,
container,
products,
quantityNames,
costItemId,
@@ -1316,7 +1318,7 @@ export class CostUI {
const noProductsMessage = document.createElement("p");
noProductsMessage.textContent =
"Your Blender Selection is empty! Select objects first.";
form.appendChild(noProductsMessage);
container.appendChild(noProductsMessage);
return;
}
const { tableContainer, table } = CostUI.addTable({
@@ -1324,7 +1326,7 @@ export class CostUI {
className: "",
id: "cost-values-table-" + costItemId,
});
form.appendChild(tableContainer);
container.appendChild(tableContainer);
const tbody = document.createElement("tbody");
table.appendChild(tbody);
@@ -1432,7 +1434,7 @@ export class CostUI {
const productIds = products.map((product) => product.info.id);
callbacks.emptyForm = () => {
form.innerHTML = "";
container.innerHTML = "";
};
const addProductAssignmentsButton = CostUI.addProductAssignmentsButton(
costItemId,
@@ -1441,13 +1443,13 @@ export class CostUI {
callbacks
);
form.appendChild(quantitySelect);
form.appendChild(addProductAssignmentsButton);
container.appendChild(quantitySelect);
container.appendChild(addProductAssignmentsButton);
return table;
}
static createAssignmentsTable({
form,
container,
products,
costItemId,
quantityNames,
@@ -1459,7 +1461,7 @@ export class CostUI {
id: "cost-values-table-" + costItemId,
});
form.appendChild(tableContainer);
container.appendChild(tableContainer);
const tbody = document.createElement("tbody");
table.appendChild(tbody);
@@ -1512,8 +1514,10 @@ export class CostUI {
quantitySelect,
callbacks
) {
const addProductAssignmentsButton = document.createElement("button");
addProductAssignmentsButton.textContent = "Add Product Assignments";
const addProductAssignmentsButton = CostUI.createButton(
"Add Product Assignments",
"fa-solid fa-plus"
);
addProductAssignmentsButton.addEventListener("click", (event) => {
event.preventDefault();
let propName = quantitySelect.value;
@@ -1528,7 +1532,7 @@ export class CostUI {
})
: null;
callbacks.emptyForm ? callbacks.emptyForm() : null;
callbacks.getSelectedProducts(costItemId);
callbacks.enableEditingQuantities(costItemId);
});
addProductAssignmentsButton.classList.add("action-button");
return addProductAssignmentsButton;
@@ -1730,49 +1734,424 @@ export class CostUI {
});
}
static editQuantities({
static enableEditingQuantities({
costItemId,
selectedProducts,
quantityNames,
assignedProducts,
quantityNames,
costQuantities,
callbacks,
}) {
const formId = "selected-products-" + costItemId;
CostUI.highlightElement(costItemId);
const form = CostUI.Form({
id: formId,
name:
"Edit Product Assignments for: " + CostUI.getCostItemName(costItemId),
id: "enable-editing-quantities-" + costItemId,
name: "Editing Quantities for: " + CostUI.getCostItemName(costItemId),
icon: "fa-solid fa-box",
});
const numberOfProducts = CostUI.Text(
"Selection basket : " + selectedProducts.length + " products",
"fa-solid fa-cart-shopping",
"large"
);
form.appendChild(numberOfProducts);
CostUI.highlightElement(costItemId);
const selectedProductsTable = CostUI.createProductTable({
form,
const ribbonBar = CostUI.createRibbonBar();
form.appendChild(ribbonBar);
const selectedProductsSection = CostUI.selectedProductsSection({
products: selectedProducts,
quantityNames,
costItemId,
callbacks,
});
if (assignedProducts.length > 0) {
const assignedProductsSection = CostUI.assignedProductsSection({
products: assignedProducts,
quantityNames,
costItemId,
callbacks,
});
const manualQuantitiesSection = CostUI.manualQuantitiesSection({
costItemId,
costQuantities,
has_assigned_products: assignedProducts.length > 0,
callbacks,
});
form.appendChild(selectedProductsSection);
form.appendChild(assignedProductsSection);
form.appendChild(manualQuantitiesSection);
const summarySection = CostUI.createSummarySection({
selectedProducts,
assignedProducts,
costQuantities,
});
form.appendChild(summarySection);
CostUI.addEventListeners({
switchBar: ribbonBar,
costItemId,
selectedProductsSection,
assignedProductsSection,
manualQuantitiesSection,
});
const lastActiveSection =
localStorage.getItem("lastActiveSection") || "selected-products";
const lastActiveButton = document.getElementById(
`${lastActiveSection}-btn`
);
if (lastActiveButton) {
lastActiveButton.click();
}
}
static createRibbonBar() {
const ribbonBar = document.createElement("div");
ribbonBar.className = "switch-bar";
ribbonBar.innerHTML = `
<button class="action-button" id="selected-products-btn">Selected Products</button>
<button class="action-button" id="assigned-products-btn">Assigned Products</button>
<button class="action-button" id="manual-quantities-btn">Manual Quantities</button>
`;
return ribbonBar;
}
static createSummarySection({
selectedProducts,
assignedProducts,
costQuantities,
}) {
const summarySection = document.createElement("div");
summarySection.classList.add("summary-section");
const manualQuantities = costQuantities.quantities.filter(
(quantity) => !quantity.fromProduct
);
const paramaterQuantities = costQuantities.quantities.filter(
(quantity) => quantity.fromProduct
);
const table = document.createElement("table");
table.classList.add("summary-table");
const thead = document.createElement("thead");
const headerRow = document.createElement("tr");
const headers = ["Category", "Count"];
headers.forEach((headerText) => {
const th = document.createElement("th");
th.textContent = headerText;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
const tbody = document.createElement("tbody");
const data = [
{ category: "Assigned Products", count: assignedProducts.length },
{ category: "Manual Quantities", count: manualQuantities.length },
{
category: "Product derived Quantities",
count: paramaterQuantities.length,
},
];
data.forEach((item) => {
const row = document.createElement("tr");
const categoryCell = document.createElement("td");
categoryCell.textContent = item.category;
const countCell = document.createElement("td");
countCell.textContent = item.count;
row.appendChild(categoryCell);
row.appendChild(countCell);
tbody.appendChild(row);
});
table.appendChild(tbody);
summarySection.appendChild(table);
return summarySection;
}
static addEventListeners({
switchBar,
costItemId,
selectedProductsSection,
assignedProductsSection,
manualQuantitiesSection,
}) {
const buttons = switchBar.querySelectorAll(".action-button");
buttons.forEach((button) => {
button.addEventListener("click", (e) => {
e.preventDefault();
const sectionName = button.id.replace("-btn", "");
const sectionId = sectionName + "-section-" + costItemId;
if (selectedProductsSection)
selectedProductsSection.style.display = "none";
if (assignedProductsSection)
assignedProductsSection.style.display = "none";
if (manualQuantitiesSection)
manualQuantitiesSection.style.display = "none";
const section = document.getElementById(sectionId);
if (section) {
section.style.display = "block";
}
buttons.forEach((btn) => btn.classList.remove("active-btn"));
button.classList.add("active-btn");
localStorage.setItem("lastActiveSection", sectionName);
});
});
}
static getSection(costItemId, sectionName) {
return document.getElementById(`${sectionName}-${costItemId}`);
}
static selectedProductsSection({
products,
quantityNames,
costItemId,
callbacks,
}) {
const selectedProductsSection = document.createElement("div");
selectedProductsSection.id = "selected-products-section-" + costItemId;
selectedProductsSection.classList.add("form-section");
const numberOfProducts = CostUI.Text(
"Selection basket : " + products.length + " products",
"fa-solid fa-cart-shopping",
"medium"
);
selectedProductsSection.appendChild(numberOfProducts);
const selectedProductsTable = CostUI.createProductTable({
container: selectedProductsSection,
products: products,
quantityNames,
costItemId,
callbacks,
});
return selectedProductsSection;
}
static assignedProductsSection({
products,
quantityNames,
costItemId,
callbacks,
}) {
const assignedProductsSection = document.createElement("div");
assignedProductsSection.id = "assigned-products-section-" + costItemId;
assignedProductsSection.style.display = "none";
assignedProductsSection.classList.add("form-section");
if (products.length > 0) {
let text = " Assigned Products : " + products.length;
const assignedProductsText = CostUI.Text(
"Assigned Products",
text,
"fa-solid fa-solid fa-paperclip",
"large"
"medium"
);
form.appendChild(assignedProductsText);
assignedProductsSection.appendChild(assignedProductsText);
const assignmentsTable = CostUI.createAssignmentsTable({
form,
products: assignedProducts,
container: assignedProductsSection,
products: products,
quantityNames,
costItemId,
callbacks,
});
}
return assignedProductsSection;
}
static manualQuantitiesSection({
costItemId,
costQuantities,
has_assigned_products,
callbacks,
}) {
const manualQuantitiesSection = document.createElement("div");
manualQuantitiesSection.classList.add("form-section");
manualQuantitiesSection.id = "manual-quantities-section-" + costItemId;
manualQuantitiesSection.style.display = "none";
const title = CostUI.Text(
"Manual Quantities",
"fa-solid fa-ruler",
"medium"
);
manualQuantitiesSection.appendChild(title);
const unitSymbol = costQuantities.unit_symbol;
let quantityType = costQuantities.quantity_type;
if (quantityType) {
const text = CostUI.Text(
quantityType + " (" + unitSymbol + " )",
"fa-solid fa-ruler",
"small"
);
manualQuantitiesSection.appendChild(text);
} else {
// add dropdown to chose quantity type , from "IfcQuantityArea", "IfcQuantityLength", "IfcQuantityVolume", "IfcQuantityCount", "IfcQuantityWeight "
const quantityTypes = [
"IfcQuantityArea",
"IfcQuantityLength",
"IfcQuantityVolume",
"IfcQuantityCount",
"IfcQuantityWeight",
];
const dropdown = CostUI.createTableDropdown(
"type",
quantityTypes,
"IfcQuantityArea"
);
dropdown.id = "quantity-type-dropdown-" + costItemId;
manualQuantitiesSection.appendChild(dropdown);
}
// if quantity is ty IfcQuantityCount, and the costQuantities
if (quantityType === "IfcQuantityCount" && has_assigned_products) {
// write text that one of the quantities is assigned to the product selection
const text = CostUI.Text(
" One of the quantities is assigned to the product selection",
"fa-solid fa-warning",
"small"
);
manualQuantitiesSection.appendChild(text);
}
let paramaterQuantities = costQuantities.quantities.filter(
(quantity) => quantity.fromProduct
);
let manualQuantities = costQuantities.quantities.filter(
(quantity) => !quantity.fromProduct
);
let headerNames = ["Name", "Value" + " (" + unitSymbol + " )", "Actions"];
if (manualQuantities.length > 0) {
headerNames = [];
Object.entries(manualQuantities[0]).forEach(([key, value]) => {
if (key !== "id" && key !== "fromProduct") {
headerNames.push(key);
}
});
headerNames.push("Actions");
}
const { tableContainer, table } = CostUI.addTable({
headers: headerNames,
className: "manual-quantities-table",
id: "manual-quantities-table-" + costItemId,
});
manualQuantities.forEach((quantity) => {
const tr = CostUI.createManualQuantityRow(
costItemId,
quantity,
callbacks
);
table.appendChild(tr);
});
const addButton = CostUI.createAddManualQuantityButton(
costItemId,
quantityType,
callbacks
);
manualQuantitiesSection.appendChild(tableContainer);
// manualQuantitiesSection.appendChild(tableContainer2);
manualQuantitiesSection.appendChild(addButton);
return manualQuantitiesSection;
}
static createManualQuantityRow(costItemId, quantity, callbacks) {
const tr = document.createElement("tr");
tr.id = quantity.id;
// get quantity keys and values to create the table row cells
// Label cell
Object.entries(quantity).forEach(([key, value]) => {
if (key !== "id" && key !== "fromProduct") {
if (key === "Name") {
// create input
const cell = document.createElement("td");
const input = document.createElement("input");
input.type = "text";
input.value = value;
cell.appendChild(input);
tr.appendChild(cell);
input.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
e.preventDefault();
callbacks.editQuantity(costItemId, quantity.id, {
[key]: input.value,
});
}
});
}
// if key contains value
else if (key.toLowerCase().includes("value")) {
const cell = document.createElement("td");
const input = document.createElement("input");
input.type = "number";
input.value = value;
cell.appendChild(input);
input.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
e.preventDefault();
callbacks.editQuantity(costItemId, quantity.id, {
[key]: parseFloat(input.value), // Dynamically set the key and parse the value as a double
});
}
});
tr.appendChild(cell);
} else {
const cell = document.createElement("td");
cell.textContent = value;
tr.appendChild(cell);
}
}
});
// Add delete button
const deleteButton = CostUI.createButton("Delete", "fa-solid fa-trash");
const deleteCell = document.createElement("td");
deleteButton.addEventListener("click", function (e) {
e.preventDefault();
callbacks.deleteQuantity(costItemId, quantity.id);
tr.remove();
});
deleteCell.appendChild(deleteButton);
tr.appendChild(deleteCell);
return tr;
}
static createAddManualQuantityButton(costItemId, quantityType, callbacks) {
const addButton = CostUI.createButton(
"Add Manual Quantity",
"fa-solid fa-plus"
);
addButton.addEventListener("click", function (e) {
e.preventDefault();
let type = quantityType;
if (!type) {
// get quantityType from dropdown
const qtoSection = document.getElementById(
"manual-quantities-section-" + costItemId
);
const dropdown = qtoSection.querySelector("select");
type = dropdown ? dropdown.value : null;
}
callbacks.addQuantity(costItemId, type);
});
return addButton;
}
}
+36 -1
View File
@@ -295,6 +295,15 @@ class Cost(bonsai.core.tool.Cost):
unit = cls.get_quantity_unit_symbol(quantity)
return selected_quantitites, unit
@classmethod
def get_assigned_product(cls, cost_item, quantity):
assigned_products = cls.get_cost_item_assignments(cost_item, filter_by_type="PRODUCT", is_deep=False)
for product in assigned_products:
assigned_quantities, _ = cls.get_assigned_quantities(cost_item, product)
if quantity in assigned_quantities:
return product
@classmethod
def get_products(cls, related_object_type: RELATED_OBJECT_TYPE) -> list[ifcopenshell.entity_instance]:
if related_object_type == "PRODUCT":
@@ -859,7 +868,33 @@ class Cost(bonsai.core.tool.Cost):
bpy.ops.bim.connect_websocket_server(page="costing")
cost_schedule_data = cls.create_cost_schedule_json(cost_chedule)
tool.Web.send_webui_data(
data={"cost_items": cost_schedule_data, "cost_schedule_id": cost_chedule.id()},
data={
"cost_items": cost_schedule_data,
"cost_schedule_id": cost_chedule.id(),
"currency": cls.currency()
},
data_key="cost_items",
event="cost_items",
)
@classmethod
def get_cost_quantities(cls, cost_item: ifcopenshell.entity_instance) -> dict:
results = {
"quantities": [],
"unit_symbol": None,
}
if not cost_item:
return results
results["quantity_type"] = cost_item.CostQuantities[0].is_a() if cost_item.CostQuantities else None
unit = ifcopenshell.util.unit.get_property_unit(cost_item.CostQuantities[0], tool.Ifc.get()) if cost_item.CostQuantities else None
if unit:
results["unit_symbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
for quantity in cost_item.CostQuantities or []:
assigned_product = cls.get_assigned_product(cost_item, quantity)
info = quantity.get_info()
info["fromProduct"] = assigned_product.get_info(recursive=True) if assigned_product else None
results["quantities"].append(info)
if results["quantity_type"] == "IfcQuantityCount":
results["unit_symbol"] = "U"
return results
+98 -72
View File
@@ -344,52 +344,6 @@ class Web(bonsai.core.tool.Web):
operator_data (dict): A dictionary containing the operator data.
"""
def selection_data(cost_item, elements):
print(cost_item, elements)
if not elements:
return []
if not cost_item:
return [
{
"info": {
"id": element.id(),
"name": element.Name,
"class": element.is_a(),
"type": get_type(element).Name if get_type(element) else None,
},
"qtos": get_psets(element, qtos_only=True),
}
for element in elements or []
]
data = []
for element in elements or []:
psets = get_psets(element, qtos_only=True)
element_type = get_type(element)
quantities, unit = tool.Cost.get_assigned_quantities(cost_item, element)
data.append(
{
"info": {
"id": element.id(),
"name": element.Name,
"class": element.is_a(),
"type": element_type.Name if element_type else None,
},
"qtos": psets,
"assigned_quantities": [
{
"cost_item_id": cost_item.id(),
"product_id": element.id(),
"name": q.Name,
"value": q[3],
"type": q.Unit,
}
for q in quantities or []
],
"unit": unit,
}
)
return data
ifc_file = tool.Ifc.get()
if operator_data["type"] == "getPredefinedTypes":
print("getting predefined types")
@@ -425,31 +379,6 @@ class Web(bonsai.core.tool.Web):
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
products = tool.Cost.get_cost_item_products(cost_item, is_deep=True)
tool.Spatial.select_products(products, unhide=True)
if operator_data["type"] == "enableEditingQuantities":
cost_item_id = operator_data["costItemId"]
cost_item = ifc_file.by_id(cost_item_id)
if not cost_item:
print("---> cost item not found")
return
selected_products = list(tool.Spatial.get_selected_products())
selected_products_data = selection_data(cost_item, selected_products)
assigned_products = tool.Cost.get_cost_item_products(cost_item, is_deep=False)
assigned_products_data = selection_data(cost_item, assigned_products)
names = ifcopenshell.util.cost.get_product_quantity_names(selected_products)
cls.send_webui_data(
data={
"selected_products": selected_products_data,
"assigned_products": assigned_products_data,
"product_quantity_names": names,
"cost_item_id": cost_item_id,
},
data_key="quantities",
event="quantities",
)
if operator_data["type"] == "addSummaryCostItem":
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
bonsai.core.cost.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=cost_schedule)
@@ -520,12 +449,109 @@ class Web(bonsai.core.tool.Web):
prop_name = operator_data["propName"]
if prop_name == "count":
prop_name = ""
print(prop_name)
bpy.ops.bim.assign_cost_item_quantity(
cost_item=operator_data["costItemId"], related_object_type="PRODUCT", prop_name=prop_name
)
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
cls.load_cost_schedule_web_ui(cost_schedule)
if operator_data["type"] == "enableEditingQuantities":
cost_item_id = operator_data["costItemId"]
cost_item = ifc_file.by_id(cost_item_id)
cls.enableEditingCostItemQuantities(cost_item)
if operator_data["type"] == "AddCostItemQuantity":
cost_item_id = operator_data["costItemId"]
cost_item = ifc_file.by_id(cost_item_id)
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
bpy.ops.bim.add_cost_item_quantity(cost_item=cost_item_id, ifc_class=operator_data["ifcClass"])
cls.load_cost_schedule_web_ui(cost_schedule)
cls.enableEditingCostItemQuantities(cost_item)
if operator_data["type"] == "editCostItemQuantity":
cost_item_id = operator_data["costItemId"]
cost_item = ifc_file.by_id(cost_item_id)
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
physical_quantity = tool.Ifc.get().by_id(operator_data["quantityId"])
attributes = operator_data["attributes"]
tool.Ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=attributes)
tool.Cost.load_cost_item_quantities(ifc_file.by_id(operator_data["costItemId"]))
cls.load_cost_schedule_web_ui(cost_schedule)
cls.enableEditingCostItemQuantities(cost_item)
if operator_data["type"] == "deleteCostItemQuantity":
bpy.ops.bim.remove_cost_item_quantity(cost_item=operator_data["costItemId"], physical_quantity=operator_data["quantityId"])
cost_item_id = operator_data["costItemId"]
cost_item = ifc_file.by_id(cost_item_id)
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
cls.load_cost_schedule_web_ui(cost_schedule)
cls.enableEditingCostItemQuantities(cost_item)
@classmethod
def enableEditingCostItemQuantities(cls, cost_item):
if not cost_item:
return
selected_products = list(tool.Spatial.get_selected_products())
selected_products_data = cls.selection_data(cost_item, selected_products)
assigned_products = tool.Cost.get_cost_item_products(cost_item, is_deep=False)
assigned_products_data = cls.selection_data(cost_item, assigned_products)
names = ifcopenshell.util.cost.get_product_quantity_names(selected_products)
cls.send_webui_data(
data={
"selected_products": selected_products_data,
"assigned_products": assigned_products_data,
"product_quantity_names": names,
"cost_item_id": cost_item.id(),
"cost_quantities": tool.Cost.get_cost_quantities(cost_item),
},
data_key="quantities",
event="quantities",
)
@classmethod
def selection_data(cls, cost_item, elements):
if not elements:
return []
if not cost_item:
return [
{
"info": {
"id": element.id(),
"name": element.Name,
"class": element.is_a(),
"type": get_type(element).Name if get_type(element) else None,
},
"qtos": get_psets(element, qtos_only=True),
}
for element in elements or []
]
data = []
for element in elements or []:
psets = get_psets(element, qtos_only=True)
element_type = get_type(element)
quantities, unit = tool.Cost.get_assigned_quantities(cost_item, element)
data.append(
{
"info": {
"id": element.id(),
"name": element.Name,
"class": element.is_a(),
"type": element_type.Name if element_type else None,
},
"qtos": psets,
"assigned_quantities": [
{
"cost_item_id": cost_item.id(),
"product_id": element.id(),
"name": q.Name,
"value": q[3],
"type": q.Unit,
}
for q in quantities or []
],
"unit": unit,
}
)
return data
@classmethod
def load_cost_schedule_web_ui(cls, cost_schedule):