mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
cost module web-ui:
- remove Blender operators in favor of ifc.api functions - replace BonsaiBIM Data class dependency with new ifc5D2json script - enable viewing multiple cost schedules - add Identifaction column ref #5369
This commit is contained in:
@@ -19,6 +19,7 @@ body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.floating-form {
|
||||
@@ -39,15 +40,15 @@ body {
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
background-color: #323936;
|
||||
padding: var(--padding-medium);
|
||||
font-size: large;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-radius: 6px 6px 0 0;
|
||||
height: 5%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
@@ -58,9 +59,7 @@ body {
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 1em;
|
||||
height: 65%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.form-section h3 {
|
||||
@@ -185,14 +184,21 @@ body {
|
||||
|
||||
.table-container {
|
||||
margin-top: 1em;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
border: 1px solid #ddd;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
border: 2px solid #254e3c;
|
||||
/* shadow to the top */
|
||||
box-shadow: 0 -5px 5px -0 #254e3c;
|
||||
}
|
||||
|
||||
.floating-form .table-container {
|
||||
max-height: 80%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#cost-items {
|
||||
@@ -208,9 +214,12 @@ body {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
tr, td, th {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-collapse: collapse;
|
||||
user-select: none;
|
||||
font-size: var(--fontSize);
|
||||
@@ -247,8 +256,9 @@ td {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#cost-items td:first-child {
|
||||
.flex-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -375,10 +385,6 @@ form table {
|
||||
[id^="cost-items"] td:nth-child(1) {
|
||||
width: auto;
|
||||
}
|
||||
[id^="cost-items"] th:not(:nth-child(1)),
|
||||
[id^="cost-items"] td:not(:nth-child(1)) {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
[id^="cost-items"] th:last-child,
|
||||
[id^="cost-items"] td:last-child {
|
||||
@@ -411,3 +417,7 @@ td {
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ button:hover {
|
||||
border-collapse: collapse;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.column-container {
|
||||
|
||||
@@ -160,36 +160,6 @@ function handleConnectedClients(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleCostValuesData(data) {
|
||||
CostUI.createCostValuesForm({
|
||||
costValues: data.data["cost_values"]["cost_values"],
|
||||
costItemId: data.data["cost_values"]["cost_item_id"],
|
||||
callbacks: {
|
||||
editCostValues: editCostValues,
|
||||
addCostValue: addCostValue,
|
||||
deleteCostValue: deleteCostValue,
|
||||
},
|
||||
});
|
||||
|
||||
CostUI.highlightElement(data.data["cost_values"]["cost_item_id"]);
|
||||
}
|
||||
|
||||
function addCostValue(costItemId) {
|
||||
executeOperator({ type: "addCostValue", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function editCostValues(costItemId, costValues) {
|
||||
executeOperator({
|
||||
type: "editCostValues",
|
||||
costItemId: costItemId,
|
||||
costValues: costValues,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCostItem(costItemId) {
|
||||
executeOperator({ type: "deleteCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
@@ -291,34 +261,6 @@ function toggleClientList() {
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
function addCostItem(costItemId) {
|
||||
executeOperator({ type: "addCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function editCostItemName(costItemId, name) {
|
||||
executeOperator({
|
||||
type: "editCostItemName",
|
||||
costItemId: costItemId,
|
||||
name: name,
|
||||
});
|
||||
}
|
||||
|
||||
function selectAssignedElements(costItemId) {
|
||||
executeOperator({ type: "selectAssignedElements", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function handleWebConnect() {
|
||||
executeOperator({ type: "getPredefinedTypes" });
|
||||
getCostSchedules();
|
||||
}
|
||||
|
||||
function addSummaryCostItem(costScheduleId) {
|
||||
executeOperator({
|
||||
type: "addSummaryCostItem",
|
||||
costScheduleId: costScheduleId,
|
||||
});
|
||||
}
|
||||
|
||||
function handleCostSchedulesData(data) {
|
||||
const blenderId = data.blenderId;
|
||||
const loadedSchedule = loadedSchedules[blenderId]
|
||||
@@ -358,28 +300,20 @@ function handleCostSchedulesData(data) {
|
||||
}
|
||||
|
||||
function handleCostItemsData(data) {
|
||||
const costScheduleId = data.data["cost_items"]["schedule_data"][0]["id"];
|
||||
const cards = document.querySelectorAll("[id^='schedule-']");
|
||||
cards.forEach((card) => {
|
||||
if (card.id === "schedule-" + data.data["cost_items"]["cost_schedule_id"]) {
|
||||
card.classList.add("highlighted");
|
||||
} else {
|
||||
card.classList.remove("highlighted");
|
||||
}
|
||||
card.classList.remove("highlighted");
|
||||
});
|
||||
|
||||
const costScheduleId = data.data["cost_items"]["cost_schedule_id"];
|
||||
loadedSchedules[data.blenderId] = costScheduleId;
|
||||
CostUI.highlightElement("schedule-" + costScheduleId);
|
||||
|
||||
loadedSchedules[data.blenderId] = costScheduleId;
|
||||
const currency = data.data["cost_items"]["currency"]
|
||||
? data.data["cost_items"]["currency"]["name"]
|
||||
: "Undefined";
|
||||
|
||||
const costSchedule = data.data["cost_items"]["schedule_data"][0];
|
||||
CostUI.createCostSchedule({
|
||||
costItems: data.data["cost_items"]["cost_items"],
|
||||
costSchedule: costSchedule,
|
||||
currency: currency,
|
||||
costScheduleId: costScheduleId,
|
||||
blenderID: data.blenderId,
|
||||
callbacks: {
|
||||
addCostItem: addCostItem,
|
||||
deleteCostItem: deleteCostItem,
|
||||
@@ -394,23 +328,32 @@ function handleCostItemsData(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function enableEditingQuantities(costItemId) {
|
||||
executeOperator({ type: "enableEditingQuantities", costItemId: costItemId });
|
||||
function addCostItem(costItemId) {
|
||||
executeOperator({ type: "addCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function duplicateCostItem(costItemId) {
|
||||
executeOperator({ type: "duplicateCostItem", costItemId: costItemId });
|
||||
function editCostItemName(costItemId, name) {
|
||||
executeOperator({
|
||||
type: "editCostItemName",
|
||||
costItemId: costItemId,
|
||||
name: name,
|
||||
});
|
||||
}
|
||||
|
||||
function executeOperator(operator, blenderId) {
|
||||
const msg = {
|
||||
sourcePage: "cost",
|
||||
operator: operator,
|
||||
};
|
||||
if (blenderId !== undefined) {
|
||||
msg.BlenderId = blenderId;
|
||||
}
|
||||
socket.emit("web_operator", msg);
|
||||
function selectAssignedElements(costItemId) {
|
||||
executeOperator({ type: "selectAssignedElements", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function handleWebConnect() {
|
||||
executeOperator({ type: "getPredefinedTypes" });
|
||||
getCostSchedules();
|
||||
}
|
||||
|
||||
function addSummaryCostItem(costScheduleId) {
|
||||
executeOperator({
|
||||
type: "addSummaryCostItem",
|
||||
costScheduleId: costScheduleId,
|
||||
});
|
||||
}
|
||||
|
||||
function loadCostSchedule(costScheduleId, blenderId) {
|
||||
@@ -427,3 +370,53 @@ function getCostSchedules(blenderId) {
|
||||
function enableEditingCostValues(costItemId) {
|
||||
executeOperator({ type: "enableEditingCostValues", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function enableEditingQuantities(costItemId) {
|
||||
executeOperator({ type: "enableEditingQuantities", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function duplicateCostItem(costItemId) {
|
||||
executeOperator({ type: "duplicateCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function handleCostValuesData(data) {
|
||||
CostUI.createCostValuesForm({
|
||||
costValues: data.data["cost_values"]["cost_values"],
|
||||
costItemId: data.data["cost_values"]["cost_item_id"],
|
||||
callbacks: {
|
||||
editCostValues: editCostValues,
|
||||
addCostValue: addCostValue,
|
||||
deleteCostValue: deleteCostValue,
|
||||
},
|
||||
});
|
||||
|
||||
CostUI.highlightElement(data.data["cost_values"]["cost_item_id"]);
|
||||
}
|
||||
|
||||
function addCostValue(costItemId) {
|
||||
executeOperator({ type: "addCostValue", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function editCostValues(costItemId, costValues) {
|
||||
executeOperator({
|
||||
type: "editCostValues",
|
||||
costItemId: costItemId,
|
||||
costValues: costValues,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCostItem(costItemId) {
|
||||
CostUI.deleteCostItem(costItemId);
|
||||
executeOperator({ type: "deleteCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function executeOperator(operator, blenderId) {
|
||||
const msg = {
|
||||
sourcePage: "cost",
|
||||
operator: operator,
|
||||
};
|
||||
if (blenderId !== undefined) {
|
||||
msg.BlenderId = blenderId;
|
||||
}
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,42 @@ export class CostUI {
|
||||
}
|
||||
|
||||
static removeCostSchedule(id) {
|
||||
document.getElementById("cost-items-" + id).remove();
|
||||
const table = document.getElementById("cost-items-" + id);
|
||||
table ? table.parentElement.remove() : null;
|
||||
}
|
||||
static createCostTable(id, currency, callbacks) {
|
||||
CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null;
|
||||
static createCostTable({ costSchedule, currency, callbacks }) {
|
||||
let tableWrapper;
|
||||
const id = costSchedule.id;
|
||||
if (CostUI.isCostScheduleLoaded(id)) {
|
||||
const table = document.getElementById("cost-items-" + id);
|
||||
tableWrapper = table.parentElement;
|
||||
table.remove();
|
||||
} else {
|
||||
tableWrapper = document.createElement("div");
|
||||
tableWrapper.classList.add("table-wrapper");
|
||||
document.getElementById("cost-items").appendChild(tableWrapper);
|
||||
const tableHeader = document.createElement("div");
|
||||
tableHeader.classList.add("form-header");
|
||||
const text = CostUI.Text(
|
||||
costSchedule.Name,
|
||||
"fa-solid fa-money-bill-wave",
|
||||
"x-large"
|
||||
);
|
||||
|
||||
const callback = () => {
|
||||
tableWrapper.remove();
|
||||
CostUI.unhighlightElement("schedule-" + id);
|
||||
};
|
||||
let closeButton = CostUI.createCloseButton(callback);
|
||||
tableHeader.appendChild(text);
|
||||
tableHeader.appendChild(closeButton);
|
||||
tableWrapper.appendChild(tableHeader);
|
||||
}
|
||||
const table = document.createElement("table");
|
||||
table.id = "cost-items-" + id;
|
||||
const tbody = document.createElement("tbody");
|
||||
tbody.setAttribute("id", "cost-items");
|
||||
|
||||
const columnHeaders = [
|
||||
"ID",
|
||||
"Name",
|
||||
"Quantity",
|
||||
"Unit",
|
||||
@@ -44,7 +69,7 @@ export class CostUI {
|
||||
|
||||
table.appendChild(thead);
|
||||
table.appendChild(tbody);
|
||||
document.getElementById("cost-items").appendChild(table);
|
||||
tableWrapper.appendChild(table);
|
||||
|
||||
CostUI.createContextMenu(callbacks);
|
||||
table.get_blender_id = function () {
|
||||
@@ -54,12 +79,11 @@ export class CostUI {
|
||||
return [table, tbody];
|
||||
}
|
||||
|
||||
static deleteCostItem(costItemId, callback) {
|
||||
static deleteCostItem(costItemId) {
|
||||
const costItemRow = document.getElementById(costItemId);
|
||||
let expandedState = JSON.parse(localStorage.getItem("expandedState")) || {};
|
||||
expandedState = CostUI.deleteCostItemRow(costItemRow, expandedState);
|
||||
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
||||
callback(costItemId);
|
||||
}
|
||||
|
||||
static createContextMenu(callbacks) {
|
||||
@@ -79,16 +103,20 @@ export class CostUI {
|
||||
event.preventDefault();
|
||||
const targetRow = event.target.closest("tr");
|
||||
const targetCell = event.target.closest("td");
|
||||
if (targetRow && targetRow.parentElement.id === "cost-items") {
|
||||
const contextMenu = document.getElementById("context-menu");
|
||||
contextMenu.style.display = "block";
|
||||
contextMenu.style.left = `${event.pageX}px`;
|
||||
contextMenu.style.top = `${event.pageY}px`;
|
||||
|
||||
contextMenu.targetRow = targetRow;
|
||||
contextMenu.targetCell = targetCell;
|
||||
} else {
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
if (targetRow) {
|
||||
const isCostTable = targetRow.parentElement.parentElement
|
||||
? targetRow.parentElement.parentElement.id.includes("cost-items")
|
||||
: false;
|
||||
if (isCostTable) {
|
||||
const contextMenu = document.getElementById("context-menu");
|
||||
contextMenu.style.display = "block";
|
||||
contextMenu.style.left = `${event.pageX}px`;
|
||||
contextMenu.style.top = `${event.pageY}px`;
|
||||
contextMenu.targetRow = targetRow;
|
||||
contextMenu.targetCell = targetCell;
|
||||
} else {
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -102,104 +130,79 @@ export class CostUI {
|
||||
const editCostValuesButton = document.getElementById(
|
||||
"edit-cost-values-button"
|
||||
);
|
||||
editCostValuesButton.addEventListener("click", function () {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
const targetCell = document.getElementById("context-menu").targetCell;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
callbacks.enableEditingCostValues
|
||||
? callbacks.enableEditingCostValues(costItemId)
|
||||
: null;
|
||||
}
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
});
|
||||
|
||||
const addButton = document.getElementById("add-cost-item-button");
|
||||
if (!addButton.dataset.listenerAdded) {
|
||||
function addCostItemHandler(e) {
|
||||
e.stopPropagation();
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
callbacks.addCostItem ? callbacks.addCostItem(costItemId) : null;
|
||||
}
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
}
|
||||
addButton.addEventListener("click", addCostItemHandler);
|
||||
addButton.dataset.listenerAdded = "true";
|
||||
}
|
||||
|
||||
const deleteCostItemButton = document.getElementById("delete-cost-item");
|
||||
if (deleteCostItemButton && !deleteCostItemButton.hasListener) {
|
||||
deleteCostItemButton.addEventListener("click", function () {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
CostUI.deleteCostItem(costItemId, callbacks.deleteCostItem);
|
||||
}
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
});
|
||||
deleteCostItemButton.hasListener = true;
|
||||
}
|
||||
|
||||
const duplicateButton = document.getElementById("duplicate-button");
|
||||
if (duplicateButton && !duplicateButton.hasListener) {
|
||||
duplicateButton.addEventListener("click", function () {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
callbacks.duplicateCostItem(costItemId);
|
||||
}
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
});
|
||||
duplicateButton.hasListener = true;
|
||||
}
|
||||
|
||||
const getSelectedProducts = document.getElementById("assign-selection");
|
||||
if (getSelectedProducts && !getSelectedProducts.hasListener) {
|
||||
getSelectedProducts.addEventListener("click", function () {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
callbacks.enableEditingQuantities(costItemId);
|
||||
}
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
});
|
||||
getSelectedProducts.hasListener = true;
|
||||
|
||||
function handleButtonClick(button, callback) {
|
||||
if (button && !button.hasListener) {
|
||||
button.addEventListener("click", function () {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
callback(costItemId);
|
||||
}
|
||||
document.getElementById("context-menu").style.display = "none";
|
||||
});
|
||||
button.hasListener = true;
|
||||
}
|
||||
}
|
||||
|
||||
function getColumnNames(tableId) {
|
||||
const table = document.getElementById(tableId);
|
||||
const headerRow = table.querySelector("thead tr");
|
||||
return Array.from(headerRow.children).map((headerCell) =>
|
||||
headerCell.textContent.trim()
|
||||
);
|
||||
// if (deleteCostItemButton && !deleteCostItemButton.hasListener) {
|
||||
// deleteCostItemButton.addEventListener("click", function () {
|
||||
// const targetRow = document.getElementById("context-menu").targetRow;
|
||||
// if (targetRow) {
|
||||
// const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
// CostUI.deleteCostItem(costItemId, callbacks.deleteCostItem);
|
||||
// }
|
||||
// document.getElementById("context-menu").style.display = "none";
|
||||
// });
|
||||
// deleteCostItemButton.hasListener = true;
|
||||
// }
|
||||
|
||||
handleButtonClick(deleteCostItemButton, callbacks.deleteCostItem);
|
||||
handleButtonClick(duplicateButton, callbacks.duplicateCostItem);
|
||||
handleButtonClick(getSelectedProducts, callbacks.enableEditingQuantities);
|
||||
handleButtonClick(editCostValuesButton, callbacks.enableEditingCostValues);
|
||||
handleButtonClick(addButton, callbacks.addCostItem);
|
||||
handleButtonClick(deleteCostItemButton, callbacks.deleteCostItem);
|
||||
|
||||
addTableListeners(callbacks);
|
||||
|
||||
function addTableListeners(callbacks) {
|
||||
function getColumnNames(tableId) {
|
||||
const table = document.getElementById(tableId);
|
||||
const headerRow = table.querySelector("thead tr");
|
||||
return Array.from(headerRow.children).map((headerCell) =>
|
||||
headerCell.textContent.trim()
|
||||
);
|
||||
}
|
||||
document
|
||||
.getElementById("cost-items")
|
||||
.addEventListener("click", function (event) {
|
||||
const targetRow = event.target.closest("tr");
|
||||
const targetCell = event.target.closest("td");
|
||||
if (targetRow && targetCell) {
|
||||
const columnIndex = Array.from(targetRow.children).indexOf(
|
||||
targetCell
|
||||
);
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
const columnName = getColumnNames("cost-items")[columnIndex];
|
||||
|
||||
if (columnName.includes("Cost") && !columnName.includes("Total")) {
|
||||
callbacks.enableEditingCostValues
|
||||
? callbacks.enableEditingCostValues(costItemId)
|
||||
: null;
|
||||
}
|
||||
if (columnName === "Quantity") {
|
||||
callbacks.enableEditingQuantities
|
||||
? callbacks.enableEditingQuantities(costItemId)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("cost-items")
|
||||
.addEventListener("click", function (event) {
|
||||
const targetRow = event.target.closest("tr");
|
||||
const targetCell = event.target.closest("td");
|
||||
if (targetRow && targetCell) {
|
||||
const columnIndex = Array.from(targetRow.children).indexOf(
|
||||
targetCell
|
||||
);
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
const columnName = getColumnNames("cost-items")[columnIndex];
|
||||
|
||||
if (columnName.includes("Cost") && !columnName.includes("Total")) {
|
||||
callbacks.enableEditingCostValues
|
||||
? callbacks.enableEditingCostValues(costItemId)
|
||||
: null;
|
||||
}
|
||||
if (columnName === "Quantity") {
|
||||
callbacks.enableEditingQuantities
|
||||
? callbacks.enableEditingQuantities(costItemId)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static deleteCostItemRow(targetRow, expandedState) {
|
||||
@@ -353,19 +356,13 @@ export class CostUI {
|
||||
return div;
|
||||
}
|
||||
|
||||
static createCostSchedule({
|
||||
costItems,
|
||||
currency,
|
||||
costScheduleId,
|
||||
blenderID,
|
||||
callbacks = {},
|
||||
}) {
|
||||
const [table, tbody] = CostUI.createCostTable(
|
||||
blenderID,
|
||||
static createCostSchedule({ costSchedule, currency, callbacks = {} }) {
|
||||
const [table, tbody] = CostUI.createCostTable({
|
||||
costSchedule,
|
||||
currency,
|
||||
callbacks
|
||||
);
|
||||
if (costItems.length === 0) {
|
||||
callbacks,
|
||||
});
|
||||
if (costSchedule["cost_items"].length === 0) {
|
||||
const tr = document.createElement("tr");
|
||||
const td = document.createElement("td");
|
||||
td.colSpan = 6;
|
||||
@@ -383,7 +380,13 @@ export class CostUI {
|
||||
td.appendChild(addSummaryCostItemButton);
|
||||
addSummaryCostItemButton.classList.add("action-button");
|
||||
} else {
|
||||
CostUI.createCostTree(costItems, tbody, 0, null, callbacks);
|
||||
CostUI.createCostTree(
|
||||
costSchedule["cost_items"],
|
||||
tbody,
|
||||
0,
|
||||
null,
|
||||
callbacks
|
||||
);
|
||||
CostUI.applyExpandedState();
|
||||
}
|
||||
}
|
||||
@@ -404,9 +407,9 @@ export class CostUI {
|
||||
);
|
||||
container.appendChild(row);
|
||||
|
||||
if (costItem.is_nested_by && costItem.is_nested_by.length > 0) {
|
||||
if (costItem.IsNestedBy && costItem.IsNestedBy.length > 0) {
|
||||
CostUI.createCostTree(
|
||||
costItem.is_nested_by,
|
||||
costItem.IsNestedBy,
|
||||
container,
|
||||
nestingLevel + 1,
|
||||
costItem.id,
|
||||
@@ -434,6 +437,10 @@ export class CostUI {
|
||||
? CostUI.format_number(costItem.TotalAppliedValue)
|
||||
: "-";
|
||||
const row = CostUI.createCostItemRow(costItem, nestingLevel, parentID);
|
||||
const identification = costItem.Identification
|
||||
? costItem.Identification
|
||||
: "XXX";
|
||||
const idCell = CostUI.createTableCell(identification);
|
||||
const expandButton = CostUI.createExpandButton(costItem);
|
||||
const nameCell = CostUI.createNameCell(
|
||||
costItem,
|
||||
@@ -456,6 +463,7 @@ export class CostUI {
|
||||
|
||||
actionsCell.classList.add("actions-column");
|
||||
|
||||
row.appendChild(idCell);
|
||||
row.appendChild(nameCell);
|
||||
row.appendChild(totalCostQuantityCell);
|
||||
row.appendChild(unitSymbolCell);
|
||||
@@ -491,7 +499,7 @@ export class CostUI {
|
||||
expandButton.classList.add("fa-solid");
|
||||
expandButton.classList.add("fa-angle-right");
|
||||
|
||||
if (row.is_nested_by && row.is_nested_by.length > 0) {
|
||||
if (row.IsNestedBy && row.IsNestedBy.length > 0) {
|
||||
expandButton.classList.add("fa-angle-right");
|
||||
} else {
|
||||
expandButton.style.visibility = "hidden";
|
||||
@@ -614,7 +622,7 @@ export class CostUI {
|
||||
static createNameCell(costItem, nestingLevel, expandButton, callbacks) {
|
||||
const nameCell = document.createElement("td");
|
||||
const nameInput = document.createElement("input");
|
||||
nameInput.value = costItem.name ? costItem.name : "Unnamed";
|
||||
nameInput.value = costItem.Name ? costItem.Name : "Unnamed";
|
||||
|
||||
nameInput.addEventListener("change", function () {
|
||||
callbacks.editCostItemName
|
||||
@@ -631,6 +639,7 @@ export class CostUI {
|
||||
});
|
||||
|
||||
nameCell.style.paddingLeft = nestingLevel * 20 + "px";
|
||||
nameCell.classList.add("row-container");
|
||||
nameCell.appendChild(expandButton);
|
||||
nameCell.appendChild(nameInput);
|
||||
const widthButton = 20;
|
||||
@@ -652,7 +661,7 @@ export class CostUI {
|
||||
const totalCostCell = document.createElement("td");
|
||||
const totalCost = CostUI.format_number(costItem.TotalCost);
|
||||
totalCostCell.textContent = totalCost;
|
||||
if (costItem.is_sum) {
|
||||
if (costItem.IsSum) {
|
||||
totalCostCell.textContent = totalCost + " *";
|
||||
} else if (!costItem.TotalCost) {
|
||||
const button = CostUI.createButton("Sum", "fa-solid fa-calculator");
|
||||
@@ -746,7 +755,7 @@ export class CostUI {
|
||||
const deleteButton = CostUI.createButton("Delete", "fa-solid fa-trash");
|
||||
deleteButton.addEventListener(
|
||||
"click",
|
||||
CostUI.deleteCostItem.bind(null, costItemId, deleteCostItem)
|
||||
deleteCostItem.bind(null, costItemId)
|
||||
);
|
||||
return deleteButton;
|
||||
}
|
||||
@@ -1089,6 +1098,13 @@ export class CostUI {
|
||||
}
|
||||
|
||||
static Form({ id, name, icon = "fa-solid fa-file", shouldHide = false }) {
|
||||
function style(form) {
|
||||
form.classList.add("floating-form");
|
||||
form.style.position = "absolute";
|
||||
form.style.top = "50%";
|
||||
form.style.left = "50%";
|
||||
form.style.transform = "translate(-50%, -50%)";
|
||||
}
|
||||
if (document.getElementById(id)) {
|
||||
const formContainer = document
|
||||
.getElementById(id)
|
||||
@@ -1098,17 +1114,19 @@ export class CostUI {
|
||||
}
|
||||
const form = document.createElement("form");
|
||||
form.id = id;
|
||||
form.classList.add("floating-form");
|
||||
form.style.position = "absolute";
|
||||
form.style.top = "50%";
|
||||
form.style.left = "50%";
|
||||
form.style.transform = "translate(-50%, -50%)";
|
||||
style(form);
|
||||
const header = CostUI.createHeader(name, icon);
|
||||
let closeButton;
|
||||
|
||||
if (shouldHide) {
|
||||
closeButton = CostUI.createHideButton(form);
|
||||
} else {
|
||||
closeButton = CostUI.createCloseButton(form);
|
||||
const callback = () => {
|
||||
form.remove();
|
||||
const costItemId = form.id.split("-").pop();
|
||||
CostUI.unhighlightElement(costItemId);
|
||||
};
|
||||
closeButton = CostUI.createCloseButton(callback); // Pass the function reference without invoking it
|
||||
}
|
||||
form.appendChild(header);
|
||||
form.appendChild(closeButton);
|
||||
@@ -1200,16 +1218,11 @@ export class CostUI {
|
||||
return hideButton;
|
||||
}
|
||||
|
||||
static createCloseButton(form) {
|
||||
static createCloseButton(callback) {
|
||||
const closeButton = document.createElement("span");
|
||||
closeButton.classList.add("close-button");
|
||||
closeButton.innerHTML = "×";
|
||||
closeButton.addEventListener("click", function () {
|
||||
form.remove();
|
||||
const costItemId = form.id.split("-").pop();
|
||||
CostUI.unhighlightElement(costItemId);
|
||||
});
|
||||
|
||||
closeButton.addEventListener("click", callback);
|
||||
return closeButton;
|
||||
}
|
||||
|
||||
@@ -2114,9 +2127,7 @@ export class CostUI {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
else if (key.toLowerCase().includes("value")) {
|
||||
} else if (key.toLowerCase().includes("value")) {
|
||||
const cell = document.createElement("td");
|
||||
const input = document.createElement("input");
|
||||
input.type = "number";
|
||||
|
||||
@@ -865,12 +865,7 @@ class Cost(bonsai.core.tool.Cost):
|
||||
def generate_cost_schedule_browser(cls, cost_chedule) -> None:
|
||||
if not bpy.context.scene.WebProperties.is_connected:
|
||||
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(), "currency": cls.currency()},
|
||||
data_key="cost_items",
|
||||
event="cost_items",
|
||||
)
|
||||
tool.Web.load_cost_schedule_web_ui(cost_chedule)
|
||||
|
||||
@classmethod
|
||||
def get_cost_quantities(cls, cost_item: ifcopenshell.entity_instance) -> dict:
|
||||
|
||||
@@ -41,6 +41,7 @@ from time import sleep
|
||||
from pathlib import Path
|
||||
import bonsai.core.sequence
|
||||
import bonsai.core.cost
|
||||
from ifc5d.ifc2json import ifc5D2json
|
||||
|
||||
sio = None
|
||||
ws_process = None
|
||||
@@ -346,9 +347,11 @@ class Web(bonsai.core.tool.Web):
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
if operator_data["type"] == "getPredefinedTypes":
|
||||
print("getting predefined types")
|
||||
predefined_types = ifcopenshell.util.cost.get_cost_schedule_types(ifc_file)
|
||||
cls.send_webui_data(data=predefined_types, data_key="predefined_types", event="predefined_types")
|
||||
cls.send_webui_data(
|
||||
data=ifcopenshell.util.cost.get_cost_schedule_types(ifc_file),
|
||||
data_key="predefined_types",
|
||||
event="predefined_types"
|
||||
)
|
||||
if operator_data["type"] == "addCostSchedule":
|
||||
bonsai.core.cost.add_cost_schedule(
|
||||
tool.Ifc, name=operator_data["name"], predefined_type=operator_data["predefinedType"]
|
||||
@@ -361,7 +364,6 @@ class Web(bonsai.core.tool.Web):
|
||||
event="cost_schedules",
|
||||
)
|
||||
if operator_data["type"] == "getCostSchedules":
|
||||
print("getting cost schedules")
|
||||
cost_schedules_json = [cs.get_info(recursive=True) for cs in ifc_file.by_type("IfcCostSchedule")]
|
||||
currency = tool.Cost.currency()
|
||||
cls.send_webui_data(
|
||||
@@ -370,38 +372,36 @@ class Web(bonsai.core.tool.Web):
|
||||
event="cost_schedules",
|
||||
)
|
||||
if operator_data["type"] == "loadCostSchedule":
|
||||
print("loading cost schedule")
|
||||
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
|
||||
bonsai.core.cost.enable_editing_cost_items(tool.Cost, cost_schedule=cost_schedule)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "selectAssignedElements":
|
||||
print("selecting assigned elements")
|
||||
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"] == "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)
|
||||
tool.Ifc.run("cost.add_cost_item", cost_schedule=cost_schedule)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "addCostItem":
|
||||
if not operator_data["costItemId"]:
|
||||
return
|
||||
bpy.ops.bim.add_cost_item(cost_item=operator_data["costItemId"])
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
||||
cost_item=tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
tool.Ifc.run("cost.add_cost_item", cost_item=cost_item)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "deleteCostItem":
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
||||
bpy.ops.bim.remove_cost_item(cost_item=operator_data["costItemId"])
|
||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
tool.Ifc.run("cost.remove_cost_item", cost_item=cost_item)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "duplicateCostItem":
|
||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
new_cost_item = ifcopenshell.api.cost.copy_cost_item(ifc_file, cost_item)
|
||||
tool.Ifc.run("cost.copy_cost_item", cost_item=cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "editCostItemName":
|
||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
tool.Ifc.run("cost.edit_cost_item", cost_item=cost_item, attributes={"Name": operator_data["name"]})
|
||||
tool.Cost.load_cost_schedule_tree()
|
||||
if operator_data["type"] == "enableEditingCostValues":
|
||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
cost_values = ifcopenshell.util.cost.get_cost_values(cost_item)
|
||||
@@ -422,18 +422,15 @@ class Web(bonsai.core.tool.Web):
|
||||
)
|
||||
if operator_data["type"] == "addSumCostValue":
|
||||
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||
value = ifcopenshell.api.cost.add_cost_value(
|
||||
ifc_file,
|
||||
parent=cost_item,
|
||||
)
|
||||
ifcopenshell.api.cost.edit_cost_value(file=ifc_file, cost_value=value, attributes={"Category": "*"})
|
||||
value = tool.Ifc.run("cost.add_cost_value", parent=cost_item)
|
||||
tool.Ifc.run("cost.edit_cost_value", cost_value=value, attributes={"Category": "*"})
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
tool.Cost.load_cost_schedule_tree()
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
|
||||
if operator_data["type"] == "deleteCostValue":
|
||||
bpy.ops.bim.remove_cost_value(parent=operator_data["costItemId"], cost_value=operator_data["costValueId"])
|
||||
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||
cost_value = ifc_file.by_id(operator_data["costValueId"])
|
||||
tool.Ifc.run("cost.remove_cost_value", parent=cost_item, cost_value=cost_value)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "editCostValues":
|
||||
@@ -441,7 +438,6 @@ class Web(bonsai.core.tool.Web):
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
for value_data in operator_data["costValues"] or []:
|
||||
print("editing cost value data", value_data)
|
||||
value = ifc_file.by_id(value_data["id"])
|
||||
if value_data["costType"] == "FIXED":
|
||||
attributes = {"AppliedValue": value_data["appliedValue"], "Category": None}
|
||||
@@ -453,51 +449,43 @@ class Web(bonsai.core.tool.Web):
|
||||
elif value_data["costType"] == "SUM":
|
||||
attributes = {"Category": "*"}
|
||||
ifcopenshell.api.cost.edit_cost_value(file=ifc_file, cost_value=value, attributes=attributes)
|
||||
print(value.get_info())
|
||||
tool.Cost.load_cost_schedule_tree()
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "addProductAssignments":
|
||||
prop_name = operator_data["propName"]
|
||||
if prop_name == "count":
|
||||
prop_name = ""
|
||||
bpy.ops.bim.assign_cost_item_quantity(
|
||||
cost_item=operator_data["costItemId"], related_object_type="PRODUCT", prop_name=prop_name
|
||||
)
|
||||
bonsai.core.cost.assign_cost_item_quantity(tool.Ifc, tool.Cost, cost_item=ifc_file.by_id(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)
|
||||
cls.load_cost_item_quantities_ui(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"])
|
||||
tool.Ifc.run("cost.add_cost_item_quantity", cost_item=cost_item, ifc_class=operator_data["ifcClass"])
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
cls.enableEditingCostItemQuantities(cost_item)
|
||||
cls.load_cost_item_quantities_ui(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"]))
|
||||
tool.Ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=operator_data["attributes"])
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
cls.enableEditingCostItemQuantities(cost_item)
|
||||
cls.load_cost_item_quantities_ui(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_item = ifc_file.by_id(operator_data["costItemId"])
|
||||
physical_quantity = ifc_file.by_id(operator_data["quantityId"])
|
||||
tool.Ifc.run("cost.remove_cost_item_quantity", cost_item=cost_item, physical_quantity=physical_quantity)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
cls.enableEditingCostItemQuantities(cost_item)
|
||||
cls.load_cost_item_quantities_ui(cost_item)
|
||||
|
||||
@classmethod
|
||||
def enableEditingCostItemQuantities(cls, cost_item):
|
||||
def load_cost_item_quantities_ui(cls, cost_item : ifcopenshell.entity_instance) -> None:
|
||||
if not cost_item:
|
||||
return
|
||||
selected_products = list(tool.Spatial.get_selected_products())
|
||||
@@ -520,7 +508,7 @@ class Web(bonsai.core.tool.Web):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def selection_data(cls, cost_item, elements):
|
||||
def selection_data(cls, cost_item : ifcopenshell.entity_instance, elements : list) -> list:
|
||||
if not elements:
|
||||
return []
|
||||
if not cost_item:
|
||||
@@ -566,13 +554,15 @@ class Web(bonsai.core.tool.Web):
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def load_cost_schedule_web_ui(cls, cost_schedule):
|
||||
json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
|
||||
def load_cost_schedule_web_ui(cls, cost_schedule :ifcopenshell.entity_instance) -> None:
|
||||
ifc2json = ifc5D2json()
|
||||
ifc2json.file_path = tool.Ifc.get()
|
||||
ifc2json.cost_schedule = cost_schedule
|
||||
schedule_data = ifc2json.convert_ifc_to_json()
|
||||
cls.send_webui_data(
|
||||
data={
|
||||
"cost_items": json_data,
|
||||
"cost_schedule_id": cost_schedule.id(),
|
||||
"currency": tool.Cost.currency(),
|
||||
"schedule_data": schedule_data,
|
||||
},
|
||||
data_key="cost_items",
|
||||
event="cost_items",
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from typing import Any
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.date
|
||||
|
||||
CostItem = dict[str, Any]
|
||||
|
||||
class ifc5D2json:
|
||||
def __init__(self):
|
||||
self.json: str = None
|
||||
self.file_path: str = None
|
||||
self.file: ifcopenshell.file = None
|
||||
self.cost_schedule: ifcopenshell.entity_instance = None
|
||||
self.units: dict[str, ifcopenshell.entity_instance] = {}
|
||||
self._cost_values = {}
|
||||
self.data: list[dict[str, Any]] = []
|
||||
|
||||
def convert_ifc_to_json(self):
|
||||
self.data = []
|
||||
if not isinstance(self.file_path, str):
|
||||
self.file = self.file_path
|
||||
else:
|
||||
self.file = ifcopenshell.open(self.file_path)
|
||||
if not self.cost_schedule:
|
||||
self.extract_cost_schedules()
|
||||
else:
|
||||
self.extract_cost_schedule()
|
||||
return self.data
|
||||
|
||||
def extract_cost_schedule(self):
|
||||
data = self.cost_schedule.get_info(recursive=True)
|
||||
data["cost_items"] = self.build_schedule_tree(self.cost_schedule)
|
||||
self.data.append(data)
|
||||
|
||||
def extract_cost_schedules(self):
|
||||
for cost_schedule in self.file.by_type("IfcCostSchedule"):
|
||||
data = cost_schedule.get_info(recursive=True)
|
||||
data["cost_items"] = self.build_schedule_tree(cost_schedule)
|
||||
self.data.append(data)
|
||||
|
||||
def build_schedule_tree(self, cost_schedule: ifcopenshell.entity_instance) -> dict[str, Any]:
|
||||
schedule_data = []
|
||||
self._cost_values = {}
|
||||
for rel in cost_schedule.Controls or []:
|
||||
for cost_item in rel.RelatedObjects:
|
||||
self.extract_cost_item_data(cost_item, schedule_data)
|
||||
return schedule_data
|
||||
|
||||
def extract_cost_item_data(self , cost_item: ifcopenshell.entity_instance, json_data):
|
||||
data = {}
|
||||
self.extract_cost_item_quantities(cost_item, data)
|
||||
self.populate_cost_values(cost_item, data)
|
||||
self.populate_nesting_index(cost_item, data)
|
||||
data = {**data, **cost_item.get_info(recursive=True)}
|
||||
data["id"] = cost_item.id()
|
||||
data["IsNestedBy"] = []
|
||||
data["IsSum"] = self.check_if_cost_item_is_sum(cost_item)
|
||||
json_data.append(data)
|
||||
for rel in cost_item.IsNestedBy or []:
|
||||
for sub_cost in rel.RelatedObjects:
|
||||
self.extract_cost_item_data(sub_cost, data["IsNestedBy"])
|
||||
|
||||
def check_if_cost_item_is_sum(self, cost_item: ifcopenshell.entity_instance) -> bool:
|
||||
cost_values = []
|
||||
if cost_item.is_a("IfcCostItem"):
|
||||
cost_values = cost_item.CostValues
|
||||
elif cost_item.is_a("IfcCostValue"):
|
||||
cost_values = cost_item.Components
|
||||
for cost_value in cost_values or []:
|
||||
if cost_value.Category == "*":
|
||||
return True
|
||||
return False
|
||||
|
||||
def populate_nesting_index(self, cost_item, data):
|
||||
data["NestingIndex"] = None
|
||||
for rel in cost_item.Nests or []:
|
||||
data["NestingIndex"] = rel.RelatedObjects.index(cost_item)
|
||||
|
||||
def populate_cost_values(self, root_element, data):
|
||||
# data["CostValues"] = []
|
||||
data["CategoryValues"] = {}
|
||||
data["UnitBasisValueComponent"] = None
|
||||
data["UnitBasisUnitSymbol"] = None
|
||||
data["TotalAppliedValue"] = 0.0
|
||||
data["TotalCost"] = 0.0
|
||||
has_unit_basis = False
|
||||
is_sum = False
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
values = root_element.BaseCosts
|
||||
for cost_value in values or []:
|
||||
self.extract_cost_value(root_element, data, cost_value)
|
||||
# data["CostValues"].append(cost_value.id())
|
||||
data["TotalAppliedValue"] += self._cost_values[cost_value.id()]["AppliedValue"]
|
||||
if cost_value.UnitBasis:
|
||||
cost_value_data = self._cost_values[cost_value.id()]
|
||||
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
|
||||
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
|
||||
has_unit_basis = True
|
||||
else:
|
||||
data["UnitBasisValueComponent"] = 1
|
||||
data["UnitBasisUnitSymbol"] = "U"
|
||||
if cost_value.Category == "*":
|
||||
is_sum = True
|
||||
cost_quantity = 1 if data["TotalCostQuantity"] is None else data["TotalCostQuantity"]
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity
|
||||
if is_sum:
|
||||
data["TotalAppliedValue"] = None
|
||||
|
||||
def extract_cost_value(self, root_element, root_element_data, cost_value):
|
||||
value_data = cost_value.get_info()
|
||||
del value_data["AppliedValue"]
|
||||
if value_data["UnitBasis"]:
|
||||
data = cost_value.UnitBasis.get_info()
|
||||
data["ValueComponent"] = data["ValueComponent"].wrappedValue
|
||||
data["UnitComponent"] = data["UnitComponent"].id()
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(cost_value.UnitBasis.UnitComponent)
|
||||
value_data["UnitBasis"] = data
|
||||
if value_data["ApplicableDate"]:
|
||||
value_data["ApplicableDate"] = ifcopenshell.util.date.ifc2datetime(value_data["ApplicableDate"])
|
||||
if value_data["FixedUntilDate"]:
|
||||
value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"])
|
||||
value_data["Components"] = [c.id() for c in value_data["Components"] or []]
|
||||
value_data["AppliedValue"] = ifcopenshell.util.cost.calculate_applied_value(root_element, cost_value)
|
||||
|
||||
if cost_value.Category not in [None, "*"]:
|
||||
root_element_data["CategoryValues"].setdefault(cost_value.Category, 0)
|
||||
root_element_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"]
|
||||
|
||||
value_data["Formula"] = ifcopenshell.util.cost.serialise_cost_value(cost_value)
|
||||
|
||||
self._cost_values[cost_value.id()] = value_data
|
||||
for component in cost_value.Components or []:
|
||||
self.extract_cost_value(root_element, root_element_data, component)
|
||||
|
||||
def extract_cost_item_quantities(self, cost_item, data):
|
||||
data["TotalCostQuantity"] = ifcopenshell.util.cost.get_total_quantity(cost_item)
|
||||
data["UnitSymbol"] = "-"
|
||||
if cost_item.CostQuantities:
|
||||
quantity = cost_item.CostQuantities[0]
|
||||
data["QuantityType"] = quantity.is_a()
|
||||
unit = ifcopenshell.util.unit.get_property_unit(quantity, self.file)
|
||||
if unit:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
if quantity.is_a("IfcPhysicalSimpleQuantity"):
|
||||
measure_class = (
|
||||
quantity.wrapped_data.declaration()
|
||||
.as_entity()
|
||||
.attribute_by_index(3)
|
||||
.type_of_attribute()
|
||||
.declared_type()
|
||||
.name()
|
||||
)
|
||||
if "Count" in measure_class:
|
||||
data["UnitSymbol"] = "U"
|
||||
Reference in New Issue
Block a user