mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 12:18:02 +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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-form {
|
.floating-form {
|
||||||
@@ -39,15 +40,15 @@ body {
|
|||||||
|
|
||||||
.form-header {
|
.form-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
|
||||||
background-color: #323936;
|
background-color: #323936;
|
||||||
padding: var(--padding-medium);
|
padding: var(--padding-medium);
|
||||||
font-size: large;
|
font-size: large;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
border-radius: 6px 6px 0 0;
|
border-radius: 6px 6px 0 0;
|
||||||
height: 5%;
|
height: 5%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
@@ -58,9 +59,7 @@ body {
|
|||||||
|
|
||||||
.form-section {
|
.form-section {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
height: 65%;
|
height: 70%;
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-section h3 {
|
.form-section h3 {
|
||||||
@@ -185,14 +184,21 @@ body {
|
|||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
overflow-x: auto;
|
display: flex;
|
||||||
overflow-y: auto;
|
flex-direction: row;
|
||||||
position: relative;
|
}
|
||||||
border: 1px solid #ddd;
|
|
||||||
|
.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 {
|
.floating-form .table-container {
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cost-items {
|
#cost-items {
|
||||||
@@ -208,9 +214,12 @@ body {
|
|||||||
height: 50vh;
|
height: 50vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr, td, th {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
font-size: var(--fontSize);
|
font-size: var(--fontSize);
|
||||||
@@ -247,8 +256,9 @@ td {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cost-items td:first-child {
|
.flex-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,10 +385,6 @@ form table {
|
|||||||
[id^="cost-items"] td:nth-child(1) {
|
[id^="cost-items"] td:nth-child(1) {
|
||||||
width: auto;
|
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"] th:last-child,
|
||||||
[id^="cost-items"] td:last-child {
|
[id^="cost-items"] td:last-child {
|
||||||
@@ -411,3 +417,7 @@ td {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
@@ -222,7 +222,7 @@ button:hover {
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container {
|
.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 handleThemeData(themeData) {
|
||||||
function arrayToRgbString(arr) {
|
function arrayToRgbString(arr) {
|
||||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||||
@@ -291,34 +261,6 @@ function toggleClientList() {
|
|||||||
clientList.addClass("show");
|
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) {
|
function handleCostSchedulesData(data) {
|
||||||
const blenderId = data.blenderId;
|
const blenderId = data.blenderId;
|
||||||
const loadedSchedule = loadedSchedules[blenderId]
|
const loadedSchedule = loadedSchedules[blenderId]
|
||||||
@@ -358,28 +300,20 @@ function handleCostSchedulesData(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleCostItemsData(data) {
|
function handleCostItemsData(data) {
|
||||||
|
const costScheduleId = data.data["cost_items"]["schedule_data"][0]["id"];
|
||||||
const cards = document.querySelectorAll("[id^='schedule-']");
|
const cards = document.querySelectorAll("[id^='schedule-']");
|
||||||
cards.forEach((card) => {
|
cards.forEach((card) => {
|
||||||
if (card.id === "schedule-" + data.data["cost_items"]["cost_schedule_id"]) {
|
card.classList.remove("highlighted");
|
||||||
card.classList.add("highlighted");
|
|
||||||
} else {
|
|
||||||
card.classList.remove("highlighted");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const costScheduleId = data.data["cost_items"]["cost_schedule_id"];
|
|
||||||
loadedSchedules[data.blenderId] = costScheduleId;
|
|
||||||
CostUI.highlightElement("schedule-" + costScheduleId);
|
CostUI.highlightElement("schedule-" + costScheduleId);
|
||||||
|
loadedSchedules[data.blenderId] = costScheduleId;
|
||||||
const currency = data.data["cost_items"]["currency"]
|
const currency = data.data["cost_items"]["currency"]
|
||||||
? data.data["cost_items"]["currency"]["name"]
|
? data.data["cost_items"]["currency"]["name"]
|
||||||
: "Undefined";
|
: "Undefined";
|
||||||
|
const costSchedule = data.data["cost_items"]["schedule_data"][0];
|
||||||
CostUI.createCostSchedule({
|
CostUI.createCostSchedule({
|
||||||
costItems: data.data["cost_items"]["cost_items"],
|
costSchedule: costSchedule,
|
||||||
currency: currency,
|
currency: currency,
|
||||||
costScheduleId: costScheduleId,
|
|
||||||
blenderID: data.blenderId,
|
|
||||||
callbacks: {
|
callbacks: {
|
||||||
addCostItem: addCostItem,
|
addCostItem: addCostItem,
|
||||||
deleteCostItem: deleteCostItem,
|
deleteCostItem: deleteCostItem,
|
||||||
@@ -394,23 +328,32 @@ function handleCostItemsData(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableEditingQuantities(costItemId) {
|
function addCostItem(costItemId) {
|
||||||
executeOperator({ type: "enableEditingQuantities", costItemId: costItemId });
|
executeOperator({ type: "addCostItem", costItemId: costItemId });
|
||||||
}
|
}
|
||||||
|
|
||||||
function duplicateCostItem(costItemId) {
|
function editCostItemName(costItemId, name) {
|
||||||
executeOperator({ type: "duplicateCostItem", costItemId: costItemId });
|
executeOperator({
|
||||||
|
type: "editCostItemName",
|
||||||
|
costItemId: costItemId,
|
||||||
|
name: name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeOperator(operator, blenderId) {
|
function selectAssignedElements(costItemId) {
|
||||||
const msg = {
|
executeOperator({ type: "selectAssignedElements", costItemId: costItemId });
|
||||||
sourcePage: "cost",
|
}
|
||||||
operator: operator,
|
|
||||||
};
|
function handleWebConnect() {
|
||||||
if (blenderId !== undefined) {
|
executeOperator({ type: "getPredefinedTypes" });
|
||||||
msg.BlenderId = blenderId;
|
getCostSchedules();
|
||||||
}
|
}
|
||||||
socket.emit("web_operator", msg);
|
|
||||||
|
function addSummaryCostItem(costScheduleId) {
|
||||||
|
executeOperator({
|
||||||
|
type: "addSummaryCostItem",
|
||||||
|
costScheduleId: costScheduleId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadCostSchedule(costScheduleId, blenderId) {
|
function loadCostSchedule(costScheduleId, blenderId) {
|
||||||
@@ -427,3 +370,53 @@ function getCostSchedules(blenderId) {
|
|||||||
function enableEditingCostValues(costItemId) {
|
function enableEditingCostValues(costItemId) {
|
||||||
executeOperator({ type: "enableEditingCostValues", costItemId: 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) {
|
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) {
|
static createCostTable({ costSchedule, currency, callbacks }) {
|
||||||
CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null;
|
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");
|
const table = document.createElement("table");
|
||||||
table.id = "cost-items-" + id;
|
table.id = "cost-items-" + id;
|
||||||
const tbody = document.createElement("tbody");
|
const tbody = document.createElement("tbody");
|
||||||
tbody.setAttribute("id", "cost-items");
|
|
||||||
|
|
||||||
const columnHeaders = [
|
const columnHeaders = [
|
||||||
|
"ID",
|
||||||
"Name",
|
"Name",
|
||||||
"Quantity",
|
"Quantity",
|
||||||
"Unit",
|
"Unit",
|
||||||
@@ -44,7 +69,7 @@ export class CostUI {
|
|||||||
|
|
||||||
table.appendChild(thead);
|
table.appendChild(thead);
|
||||||
table.appendChild(tbody);
|
table.appendChild(tbody);
|
||||||
document.getElementById("cost-items").appendChild(table);
|
tableWrapper.appendChild(table);
|
||||||
|
|
||||||
CostUI.createContextMenu(callbacks);
|
CostUI.createContextMenu(callbacks);
|
||||||
table.get_blender_id = function () {
|
table.get_blender_id = function () {
|
||||||
@@ -54,12 +79,11 @@ export class CostUI {
|
|||||||
return [table, tbody];
|
return [table, tbody];
|
||||||
}
|
}
|
||||||
|
|
||||||
static deleteCostItem(costItemId, callback) {
|
static deleteCostItem(costItemId) {
|
||||||
const costItemRow = document.getElementById(costItemId);
|
const costItemRow = document.getElementById(costItemId);
|
||||||
let expandedState = JSON.parse(localStorage.getItem("expandedState")) || {};
|
let expandedState = JSON.parse(localStorage.getItem("expandedState")) || {};
|
||||||
expandedState = CostUI.deleteCostItemRow(costItemRow, expandedState);
|
expandedState = CostUI.deleteCostItemRow(costItemRow, expandedState);
|
||||||
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
||||||
callback(costItemId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static createContextMenu(callbacks) {
|
static createContextMenu(callbacks) {
|
||||||
@@ -79,16 +103,20 @@ export class CostUI {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const targetRow = event.target.closest("tr");
|
const targetRow = event.target.closest("tr");
|
||||||
const targetCell = event.target.closest("td");
|
const targetCell = event.target.closest("td");
|
||||||
if (targetRow && targetRow.parentElement.id === "cost-items") {
|
if (targetRow) {
|
||||||
const contextMenu = document.getElementById("context-menu");
|
const isCostTable = targetRow.parentElement.parentElement
|
||||||
contextMenu.style.display = "block";
|
? targetRow.parentElement.parentElement.id.includes("cost-items")
|
||||||
contextMenu.style.left = `${event.pageX}px`;
|
: false;
|
||||||
contextMenu.style.top = `${event.pageY}px`;
|
if (isCostTable) {
|
||||||
|
const contextMenu = document.getElementById("context-menu");
|
||||||
contextMenu.targetRow = targetRow;
|
contextMenu.style.display = "block";
|
||||||
contextMenu.targetCell = targetCell;
|
contextMenu.style.left = `${event.pageX}px`;
|
||||||
} else {
|
contextMenu.style.top = `${event.pageY}px`;
|
||||||
document.getElementById("context-menu").style.display = "none";
|
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(
|
const editCostValuesButton = document.getElementById(
|
||||||
"edit-cost-values-button"
|
"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");
|
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");
|
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");
|
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");
|
const getSelectedProducts = document.getElementById("assign-selection");
|
||||||
if (getSelectedProducts && !getSelectedProducts.hasListener) {
|
|
||||||
getSelectedProducts.addEventListener("click", function () {
|
function handleButtonClick(button, callback) {
|
||||||
const targetRow = document.getElementById("context-menu").targetRow;
|
if (button && !button.hasListener) {
|
||||||
if (targetRow) {
|
button.addEventListener("click", function () {
|
||||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
callbacks.enableEditingQuantities(costItemId);
|
if (targetRow) {
|
||||||
}
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
document.getElementById("context-menu").style.display = "none";
|
callback(costItemId);
|
||||||
});
|
}
|
||||||
getSelectedProducts.hasListener = true;
|
document.getElementById("context-menu").style.display = "none";
|
||||||
|
});
|
||||||
|
button.hasListener = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColumnNames(tableId) {
|
// if (deleteCostItemButton && !deleteCostItemButton.hasListener) {
|
||||||
const table = document.getElementById(tableId);
|
// deleteCostItemButton.addEventListener("click", function () {
|
||||||
const headerRow = table.querySelector("thead tr");
|
// const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
return Array.from(headerRow.children).map((headerCell) =>
|
// if (targetRow) {
|
||||||
headerCell.textContent.trim()
|
// 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) {
|
static deleteCostItemRow(targetRow, expandedState) {
|
||||||
@@ -353,19 +356,13 @@ export class CostUI {
|
|||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCostSchedule({
|
static createCostSchedule({ costSchedule, currency, callbacks = {} }) {
|
||||||
costItems,
|
const [table, tbody] = CostUI.createCostTable({
|
||||||
currency,
|
costSchedule,
|
||||||
costScheduleId,
|
|
||||||
blenderID,
|
|
||||||
callbacks = {},
|
|
||||||
}) {
|
|
||||||
const [table, tbody] = CostUI.createCostTable(
|
|
||||||
blenderID,
|
|
||||||
currency,
|
currency,
|
||||||
callbacks
|
callbacks,
|
||||||
);
|
});
|
||||||
if (costItems.length === 0) {
|
if (costSchedule["cost_items"].length === 0) {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
const td = document.createElement("td");
|
const td = document.createElement("td");
|
||||||
td.colSpan = 6;
|
td.colSpan = 6;
|
||||||
@@ -383,7 +380,13 @@ export class CostUI {
|
|||||||
td.appendChild(addSummaryCostItemButton);
|
td.appendChild(addSummaryCostItemButton);
|
||||||
addSummaryCostItemButton.classList.add("action-button");
|
addSummaryCostItemButton.classList.add("action-button");
|
||||||
} else {
|
} else {
|
||||||
CostUI.createCostTree(costItems, tbody, 0, null, callbacks);
|
CostUI.createCostTree(
|
||||||
|
costSchedule["cost_items"],
|
||||||
|
tbody,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
CostUI.applyExpandedState();
|
CostUI.applyExpandedState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,9 +407,9 @@ export class CostUI {
|
|||||||
);
|
);
|
||||||
container.appendChild(row);
|
container.appendChild(row);
|
||||||
|
|
||||||
if (costItem.is_nested_by && costItem.is_nested_by.length > 0) {
|
if (costItem.IsNestedBy && costItem.IsNestedBy.length > 0) {
|
||||||
CostUI.createCostTree(
|
CostUI.createCostTree(
|
||||||
costItem.is_nested_by,
|
costItem.IsNestedBy,
|
||||||
container,
|
container,
|
||||||
nestingLevel + 1,
|
nestingLevel + 1,
|
||||||
costItem.id,
|
costItem.id,
|
||||||
@@ -434,6 +437,10 @@ export class CostUI {
|
|||||||
? CostUI.format_number(costItem.TotalAppliedValue)
|
? CostUI.format_number(costItem.TotalAppliedValue)
|
||||||
: "-";
|
: "-";
|
||||||
const row = CostUI.createCostItemRow(costItem, nestingLevel, parentID);
|
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 expandButton = CostUI.createExpandButton(costItem);
|
||||||
const nameCell = CostUI.createNameCell(
|
const nameCell = CostUI.createNameCell(
|
||||||
costItem,
|
costItem,
|
||||||
@@ -456,6 +463,7 @@ export class CostUI {
|
|||||||
|
|
||||||
actionsCell.classList.add("actions-column");
|
actionsCell.classList.add("actions-column");
|
||||||
|
|
||||||
|
row.appendChild(idCell);
|
||||||
row.appendChild(nameCell);
|
row.appendChild(nameCell);
|
||||||
row.appendChild(totalCostQuantityCell);
|
row.appendChild(totalCostQuantityCell);
|
||||||
row.appendChild(unitSymbolCell);
|
row.appendChild(unitSymbolCell);
|
||||||
@@ -491,7 +499,7 @@ export class CostUI {
|
|||||||
expandButton.classList.add("fa-solid");
|
expandButton.classList.add("fa-solid");
|
||||||
expandButton.classList.add("fa-angle-right");
|
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");
|
expandButton.classList.add("fa-angle-right");
|
||||||
} else {
|
} else {
|
||||||
expandButton.style.visibility = "hidden";
|
expandButton.style.visibility = "hidden";
|
||||||
@@ -614,7 +622,7 @@ export class CostUI {
|
|||||||
static createNameCell(costItem, nestingLevel, expandButton, callbacks) {
|
static createNameCell(costItem, nestingLevel, expandButton, callbacks) {
|
||||||
const nameCell = document.createElement("td");
|
const nameCell = document.createElement("td");
|
||||||
const nameInput = document.createElement("input");
|
const nameInput = document.createElement("input");
|
||||||
nameInput.value = costItem.name ? costItem.name : "Unnamed";
|
nameInput.value = costItem.Name ? costItem.Name : "Unnamed";
|
||||||
|
|
||||||
nameInput.addEventListener("change", function () {
|
nameInput.addEventListener("change", function () {
|
||||||
callbacks.editCostItemName
|
callbacks.editCostItemName
|
||||||
@@ -631,6 +639,7 @@ export class CostUI {
|
|||||||
});
|
});
|
||||||
|
|
||||||
nameCell.style.paddingLeft = nestingLevel * 20 + "px";
|
nameCell.style.paddingLeft = nestingLevel * 20 + "px";
|
||||||
|
nameCell.classList.add("row-container");
|
||||||
nameCell.appendChild(expandButton);
|
nameCell.appendChild(expandButton);
|
||||||
nameCell.appendChild(nameInput);
|
nameCell.appendChild(nameInput);
|
||||||
const widthButton = 20;
|
const widthButton = 20;
|
||||||
@@ -652,7 +661,7 @@ export class CostUI {
|
|||||||
const totalCostCell = document.createElement("td");
|
const totalCostCell = document.createElement("td");
|
||||||
const totalCost = CostUI.format_number(costItem.TotalCost);
|
const totalCost = CostUI.format_number(costItem.TotalCost);
|
||||||
totalCostCell.textContent = totalCost;
|
totalCostCell.textContent = totalCost;
|
||||||
if (costItem.is_sum) {
|
if (costItem.IsSum) {
|
||||||
totalCostCell.textContent = totalCost + " *";
|
totalCostCell.textContent = totalCost + " *";
|
||||||
} else if (!costItem.TotalCost) {
|
} else if (!costItem.TotalCost) {
|
||||||
const button = CostUI.createButton("Sum", "fa-solid fa-calculator");
|
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");
|
const deleteButton = CostUI.createButton("Delete", "fa-solid fa-trash");
|
||||||
deleteButton.addEventListener(
|
deleteButton.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
CostUI.deleteCostItem.bind(null, costItemId, deleteCostItem)
|
deleteCostItem.bind(null, costItemId)
|
||||||
);
|
);
|
||||||
return deleteButton;
|
return deleteButton;
|
||||||
}
|
}
|
||||||
@@ -1089,6 +1098,13 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Form({ id, name, icon = "fa-solid fa-file", shouldHide = false }) {
|
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)) {
|
if (document.getElementById(id)) {
|
||||||
const formContainer = document
|
const formContainer = document
|
||||||
.getElementById(id)
|
.getElementById(id)
|
||||||
@@ -1098,17 +1114,19 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
const form = document.createElement("form");
|
const form = document.createElement("form");
|
||||||
form.id = id;
|
form.id = id;
|
||||||
form.classList.add("floating-form");
|
style(form);
|
||||||
form.style.position = "absolute";
|
|
||||||
form.style.top = "50%";
|
|
||||||
form.style.left = "50%";
|
|
||||||
form.style.transform = "translate(-50%, -50%)";
|
|
||||||
const header = CostUI.createHeader(name, icon);
|
const header = CostUI.createHeader(name, icon);
|
||||||
let closeButton;
|
let closeButton;
|
||||||
|
|
||||||
if (shouldHide) {
|
if (shouldHide) {
|
||||||
closeButton = CostUI.createHideButton(form);
|
closeButton = CostUI.createHideButton(form);
|
||||||
} else {
|
} 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(header);
|
||||||
form.appendChild(closeButton);
|
form.appendChild(closeButton);
|
||||||
@@ -1200,16 +1218,11 @@ export class CostUI {
|
|||||||
return hideButton;
|
return hideButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCloseButton(form) {
|
static createCloseButton(callback) {
|
||||||
const closeButton = document.createElement("span");
|
const closeButton = document.createElement("span");
|
||||||
closeButton.classList.add("close-button");
|
closeButton.classList.add("close-button");
|
||||||
closeButton.innerHTML = "×";
|
closeButton.innerHTML = "×";
|
||||||
closeButton.addEventListener("click", function () {
|
closeButton.addEventListener("click", callback);
|
||||||
form.remove();
|
|
||||||
const costItemId = form.id.split("-").pop();
|
|
||||||
CostUI.unhighlightElement(costItemId);
|
|
||||||
});
|
|
||||||
|
|
||||||
return closeButton;
|
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 cell = document.createElement("td");
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "number";
|
input.type = "number";
|
||||||
|
|||||||
@@ -865,12 +865,7 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
def generate_cost_schedule_browser(cls, cost_chedule) -> None:
|
def generate_cost_schedule_browser(cls, cost_chedule) -> None:
|
||||||
if not bpy.context.scene.WebProperties.is_connected:
|
if not bpy.context.scene.WebProperties.is_connected:
|
||||||
bpy.ops.bim.connect_websocket_server(page="costing")
|
bpy.ops.bim.connect_websocket_server(page="costing")
|
||||||
cost_schedule_data = cls.create_cost_schedule_json(cost_chedule)
|
tool.Web.load_cost_schedule_web_ui(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",
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_cost_quantities(cls, cost_item: ifcopenshell.entity_instance) -> dict:
|
def get_cost_quantities(cls, cost_item: ifcopenshell.entity_instance) -> dict:
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from time import sleep
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import bonsai.core.sequence
|
import bonsai.core.sequence
|
||||||
import bonsai.core.cost
|
import bonsai.core.cost
|
||||||
|
from ifc5d.ifc2json import ifc5D2json
|
||||||
|
|
||||||
sio = None
|
sio = None
|
||||||
ws_process = None
|
ws_process = None
|
||||||
@@ -346,9 +347,11 @@ class Web(bonsai.core.tool.Web):
|
|||||||
|
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
if operator_data["type"] == "getPredefinedTypes":
|
if operator_data["type"] == "getPredefinedTypes":
|
||||||
print("getting predefined types")
|
cls.send_webui_data(
|
||||||
predefined_types = ifcopenshell.util.cost.get_cost_schedule_types(ifc_file)
|
data=ifcopenshell.util.cost.get_cost_schedule_types(ifc_file),
|
||||||
cls.send_webui_data(data=predefined_types, data_key="predefined_types", event="predefined_types")
|
data_key="predefined_types",
|
||||||
|
event="predefined_types"
|
||||||
|
)
|
||||||
if operator_data["type"] == "addCostSchedule":
|
if operator_data["type"] == "addCostSchedule":
|
||||||
bonsai.core.cost.add_cost_schedule(
|
bonsai.core.cost.add_cost_schedule(
|
||||||
tool.Ifc, name=operator_data["name"], predefined_type=operator_data["predefinedType"]
|
tool.Ifc, name=operator_data["name"], predefined_type=operator_data["predefinedType"]
|
||||||
@@ -361,7 +364,6 @@ class Web(bonsai.core.tool.Web):
|
|||||||
event="cost_schedules",
|
event="cost_schedules",
|
||||||
)
|
)
|
||||||
if operator_data["type"] == "getCostSchedules":
|
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")]
|
cost_schedules_json = [cs.get_info(recursive=True) for cs in ifc_file.by_type("IfcCostSchedule")]
|
||||||
currency = tool.Cost.currency()
|
currency = tool.Cost.currency()
|
||||||
cls.send_webui_data(
|
cls.send_webui_data(
|
||||||
@@ -370,38 +372,36 @@ class Web(bonsai.core.tool.Web):
|
|||||||
event="cost_schedules",
|
event="cost_schedules",
|
||||||
)
|
)
|
||||||
if operator_data["type"] == "loadCostSchedule":
|
if operator_data["type"] == "loadCostSchedule":
|
||||||
print("loading cost schedule")
|
|
||||||
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
|
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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "selectAssignedElements":
|
if operator_data["type"] == "selectAssignedElements":
|
||||||
print("selecting assigned elements")
|
|
||||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||||
products = tool.Cost.get_cost_item_products(cost_item, is_deep=True)
|
products = tool.Cost.get_cost_item_products(cost_item, is_deep=True)
|
||||||
tool.Spatial.select_products(products, unhide=True)
|
tool.Spatial.select_products(products, unhide=True)
|
||||||
if operator_data["type"] == "addSummaryCostItem":
|
if operator_data["type"] == "addSummaryCostItem":
|
||||||
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
|
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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "addCostItem":
|
if operator_data["type"] == "addCostItem":
|
||||||
if not operator_data["costItemId"]:
|
if not operator_data["costItemId"]:
|
||||||
return
|
return
|
||||||
bpy.ops.bim.add_cost_item(cost_item=operator_data["costItemId"])
|
cost_item=tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "deleteCostItem":
|
if operator_data["type"] == "deleteCostItem":
|
||||||
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"])
|
||||||
bpy.ops.bim.remove_cost_item(cost_item=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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "duplicateCostItem":
|
if operator_data["type"] == "duplicateCostItem":
|
||||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "editCostItemName":
|
if operator_data["type"] == "editCostItemName":
|
||||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
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.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":
|
if operator_data["type"] == "enableEditingCostValues":
|
||||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||||
cost_values = ifcopenshell.util.cost.get_cost_values(cost_item)
|
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":
|
if operator_data["type"] == "addSumCostValue":
|
||||||
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||||
value = ifcopenshell.api.cost.add_cost_value(
|
value = tool.Ifc.run("cost.add_cost_value", parent=cost_item)
|
||||||
ifc_file,
|
tool.Ifc.run("cost.edit_cost_value", cost_value=value, attributes={"Category": "*"})
|
||||||
parent=cost_item,
|
|
||||||
)
|
|
||||||
ifcopenshell.api.cost.edit_cost_value(file=ifc_file, cost_value=value, attributes={"Category": "*"})
|
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
|
|
||||||
if operator_data["type"] == "deleteCostValue":
|
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_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)
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "editCostValues":
|
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_item = ifc_file.by_id(cost_item_id)
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||||
for value_data in operator_data["costValues"] or []:
|
for value_data in operator_data["costValues"] or []:
|
||||||
print("editing cost value data", value_data)
|
|
||||||
value = ifc_file.by_id(value_data["id"])
|
value = ifc_file.by_id(value_data["id"])
|
||||||
if value_data["costType"] == "FIXED":
|
if value_data["costType"] == "FIXED":
|
||||||
attributes = {"AppliedValue": value_data["appliedValue"], "Category": None}
|
attributes = {"AppliedValue": value_data["appliedValue"], "Category": None}
|
||||||
@@ -453,51 +449,43 @@ class Web(bonsai.core.tool.Web):
|
|||||||
elif value_data["costType"] == "SUM":
|
elif value_data["costType"] == "SUM":
|
||||||
attributes = {"Category": "*"}
|
attributes = {"Category": "*"}
|
||||||
ifcopenshell.api.cost.edit_cost_value(file=ifc_file, cost_value=value, attributes=attributes)
|
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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "addProductAssignments":
|
if operator_data["type"] == "addProductAssignments":
|
||||||
prop_name = operator_data["propName"]
|
prop_name = operator_data["propName"]
|
||||||
if prop_name == "count":
|
if prop_name == "count":
|
||||||
prop_name = ""
|
prop_name = ""
|
||||||
bpy.ops.bim.assign_cost_item_quantity(
|
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_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"]))
|
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)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "enableEditingQuantities":
|
if operator_data["type"] == "enableEditingQuantities":
|
||||||
cost_item_id = operator_data["costItemId"]
|
cost_item_id = operator_data["costItemId"]
|
||||||
cost_item = ifc_file.by_id(cost_item_id)
|
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":
|
if operator_data["type"] == "AddCostItemQuantity":
|
||||||
cost_item_id = operator_data["costItemId"]
|
cost_item_id = operator_data["costItemId"]
|
||||||
cost_item = ifc_file.by_id(cost_item_id)
|
cost_item = ifc_file.by_id(cost_item_id)
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
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.load_cost_schedule_web_ui(cost_schedule)
|
||||||
cls.enableEditingCostItemQuantities(cost_item)
|
cls.load_cost_item_quantities_ui(cost_item)
|
||||||
if operator_data["type"] == "editCostItemQuantity":
|
if operator_data["type"] == "editCostItemQuantity":
|
||||||
cost_item_id = operator_data["costItemId"]
|
cost_item_id = operator_data["costItemId"]
|
||||||
cost_item = ifc_file.by_id(cost_item_id)
|
cost_item = ifc_file.by_id(cost_item_id)
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||||
physical_quantity = tool.Ifc.get().by_id(operator_data["quantityId"])
|
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=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.load_cost_schedule_web_ui(cost_schedule)
|
||||||
cls.enableEditingCostItemQuantities(cost_item)
|
cls.load_cost_item_quantities_ui(cost_item)
|
||||||
if operator_data["type"] == "deleteCostItemQuantity":
|
if operator_data["type"] == "deleteCostItemQuantity":
|
||||||
bpy.ops.bim.remove_cost_item_quantity(
|
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||||
cost_item=operator_data["costItemId"], physical_quantity=operator_data["quantityId"]
|
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_item_id = operator_data["costItemId"]
|
|
||||||
cost_item = ifc_file.by_id(cost_item_id)
|
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
cls.enableEditingCostItemQuantities(cost_item)
|
cls.load_cost_item_quantities_ui(cost_item)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def enableEditingCostItemQuantities(cls, cost_item):
|
def load_cost_item_quantities_ui(cls, cost_item : ifcopenshell.entity_instance) -> None:
|
||||||
if not cost_item:
|
if not cost_item:
|
||||||
return
|
return
|
||||||
selected_products = list(tool.Spatial.get_selected_products())
|
selected_products = list(tool.Spatial.get_selected_products())
|
||||||
@@ -520,7 +508,7 @@ class Web(bonsai.core.tool.Web):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def selection_data(cls, cost_item, elements):
|
def selection_data(cls, cost_item : ifcopenshell.entity_instance, elements : list) -> list:
|
||||||
if not elements:
|
if not elements:
|
||||||
return []
|
return []
|
||||||
if not cost_item:
|
if not cost_item:
|
||||||
@@ -566,13 +554,15 @@ class Web(bonsai.core.tool.Web):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_cost_schedule_web_ui(cls, cost_schedule):
|
def load_cost_schedule_web_ui(cls, cost_schedule :ifcopenshell.entity_instance) -> None:
|
||||||
json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
|
ifc2json = ifc5D2json()
|
||||||
|
ifc2json.file_path = tool.Ifc.get()
|
||||||
|
ifc2json.cost_schedule = cost_schedule
|
||||||
|
schedule_data = ifc2json.convert_ifc_to_json()
|
||||||
cls.send_webui_data(
|
cls.send_webui_data(
|
||||||
data={
|
data={
|
||||||
"cost_items": json_data,
|
|
||||||
"cost_schedule_id": cost_schedule.id(),
|
|
||||||
"currency": tool.Cost.currency(),
|
"currency": tool.Cost.currency(),
|
||||||
|
"schedule_data": schedule_data,
|
||||||
},
|
},
|
||||||
data_key="cost_items",
|
data_key="cost_items",
|
||||||
event="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