mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 02:07:36 +00:00
- Add button to generate cost schedule
- Feature to view, edit and add cost item values. ( Right Click - Edit ).
This commit is contained in:
@@ -166,6 +166,16 @@ class BlenderNamespace(socketio.AsyncNamespace):
|
|||||||
blender_messages[sid]["cost_schedules"] = data
|
blender_messages[sid]["cost_schedules"] = data
|
||||||
await sio.emit("cost_schedules", {"blenderId": sid, "data": data}, namespace="/web")
|
await sio.emit("cost_schedules", {"blenderId": sid, "data": data}, namespace="/web")
|
||||||
|
|
||||||
|
async def on_cost_values(self, sid, data):
|
||||||
|
print(f"Cost values from Blender client {sid}")
|
||||||
|
blender_messages[sid]["cost_values"] = data
|
||||||
|
await sio.emit("cost_values", {"blenderId": sid, "data": data}, namespace="/web")
|
||||||
|
|
||||||
|
async def on_cost_value(self, sid, data):
|
||||||
|
print(f"Cost values from Blender client {sid}")
|
||||||
|
blender_messages[sid]["cost_value"] = data
|
||||||
|
await sio.emit("cost_value", {"blenderId": sid, "data": data}, namespace="/web")
|
||||||
|
|
||||||
async def schedules(request):
|
async def schedules(request):
|
||||||
with open("templates/index.html", "r") as f:
|
with open("templates/index.html", "r") as f:
|
||||||
template = f.read()
|
template = f.read()
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
|
||||||
|
th {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.resizer {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 5px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: col-resize;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.context-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgb(51, 51, 51);
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.context-menu button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.context-menu button:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Context menu button is clicked, should zoom into object and change color */
|
||||||
|
.context-menu button:active {
|
||||||
|
background-color: #ff9634;
|
||||||
|
color: rgba(37, 51, 77, 0.466);
|
||||||
|
/* zoom */
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#cost-items tr:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color : black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #363636;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost-values-form {
|
||||||
|
position: absolute; /* Set initial position to absolute */
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid black;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 9999;
|
||||||
|
cursor: move; /* Change cursor to move */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost-values-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost-values-table th, .cost-values-table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost-values-table th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@import url("./components/card.css");
|
@import url("./components/card.css");
|
||||||
|
@import url("./components/contextMenu.css");
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-family: Arial, sans-serif;
|
--font-family: Arial, sans-serif;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ function connectSocket() {
|
|||||||
socket.on("connect", handleWebConnect);
|
socket.on("connect", handleWebConnect);
|
||||||
socket.on("cost_schedules", handleCostSchedulesData);
|
socket.on("cost_schedules", handleCostSchedulesData);
|
||||||
socket.on("cost_items", handleCostItemsData);
|
socket.on("cost_items", handleCostItemsData);
|
||||||
|
socket.on("cost_values", handleCostValuesData);
|
||||||
|
socket.on("cost_value", handleCostValueData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBlenderConnect(blenderId) {
|
function handleBlenderConnect(blenderId) {
|
||||||
@@ -46,8 +48,20 @@ function handleBlenderDisconnect(blenderId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeTableElement(blenderId) {
|
||||||
|
$("#cost-items-" + blenderId).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleCostValueData(data) {
|
||||||
|
|
||||||
|
const costItemId = data.data["cost_value"]["cost_item_id"];
|
||||||
|
const costValueId = data.data["cost_value"]["cost_value_id"];
|
||||||
|
console.log("Handling cost value data", costItemId, costValueId);
|
||||||
|
CostUI.addNewCostValueRow(costItemId, costValueId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function handleConnectedClients(data) {
|
function handleConnectedClients(data) {
|
||||||
$("#blender-count").text(data.length);
|
$("#blender-count").text(data.length);
|
||||||
|
|
||||||
@@ -56,6 +70,29 @@ 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCostValue(costItemId) {
|
||||||
|
executeOperator({ type: "addCostValue", costItemId: costItemId });
|
||||||
|
}
|
||||||
|
|
||||||
|
function editCostValues(costItemId, costValues) {
|
||||||
|
executeOperator({
|
||||||
|
type: "editCostValues",
|
||||||
|
costItemId: costItemId,
|
||||||
|
costValues: costValues
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
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));
|
||||||
@@ -99,7 +136,6 @@ function setTheme(theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addCostItem(costItemId) {
|
function addCostItem(costItemId) {
|
||||||
console.log("addCostItem", costItemId);
|
|
||||||
executeOperator({ type: "addCostItem", costItemId: costItemId });
|
executeOperator({ type: "addCostItem", costItemId: costItemId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,11 +155,7 @@ function handleCostSchedulesData(data) {
|
|||||||
const blenderId = data.blenderId;
|
const blenderId = data.blenderId;
|
||||||
const costSchedules = data.data["cost_schedules"]["cost_schedules"];
|
const costSchedules = data.data["cost_schedules"]["cost_schedules"];
|
||||||
const currency = data.data["cost_schedules"]["currency"]["name"];
|
const currency = data.data["cost_schedules"]["currency"]["name"];
|
||||||
|
|
||||||
console.log(data.data["cost_schedules"]);
|
|
||||||
|
|
||||||
const costScheduleDiv = $("#cost-schedules");
|
const costScheduleDiv = $("#cost-schedules");
|
||||||
|
|
||||||
costSchedules.forEach((costSchedule) => {
|
costSchedules.forEach((costSchedule) => {
|
||||||
costSchedule.UpdateDate = new Date(costSchedule.UpdateDate);
|
costSchedule.UpdateDate = new Date(costSchedule.UpdateDate);
|
||||||
const mainContainer = CostUI.text("Updated On: " + costSchedule.UpdateDate);
|
const mainContainer = CostUI.text("Updated On: " + costSchedule.UpdateDate);
|
||||||
@@ -136,7 +168,6 @@ function handleCostSchedulesData(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleCostItemsData(data) {
|
function handleCostItemsData(data) {
|
||||||
console.log(data);
|
|
||||||
CostUI.createCostSchedule({
|
CostUI.createCostSchedule({
|
||||||
data: data.data["cost_items"],
|
data: data.data["cost_items"],
|
||||||
blenderID: data.blenderId,
|
blenderID: data.blenderId,
|
||||||
@@ -144,6 +175,7 @@ function handleCostItemsData(data) {
|
|||||||
"addCostItem": addCostItem,
|
"addCostItem": addCostItem,
|
||||||
"selectAssignedElements": selectAssignedElements,
|
"selectAssignedElements": selectAssignedElements,
|
||||||
'editCostItemName': editCostItemName,
|
'editCostItemName': editCostItemName,
|
||||||
|
'enableEditingCostValues': enableEditingCostValues,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -166,3 +198,7 @@ function loadCostSchedule(costScheduleId, blenderId) {
|
|||||||
function getCostSchedules(blenderId) {
|
function getCostSchedules(blenderId) {
|
||||||
executeOperator({ type: "getCostSchedules" }, blenderId);
|
executeOperator({ type: "getCostSchedules" }, blenderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enableEditingCostValues(costItemId) {
|
||||||
|
executeOperator({ type: "enableEditingCostValues", costItemId: costItemId});
|
||||||
|
}
|
||||||
@@ -82,7 +82,6 @@ function handleBlenderDisconnect(blenderId) {
|
|||||||
|
|
||||||
function handleConnectedClients(data) {
|
function handleConnectedClients(data) {
|
||||||
$("#blender-count").text(data.length);
|
$("#blender-count").text(data.length);
|
||||||
// console.log(data);
|
|
||||||
data.forEach(function (id) {
|
data.forEach(function (id) {
|
||||||
connectedClients[id] = {
|
connectedClients[id] = {
|
||||||
shown: false,
|
shown: false,
|
||||||
@@ -115,7 +114,6 @@ function handleThemeData(themeData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cssRule = generateCssVariableRule(themeData.theme);
|
const cssRule = generateCssVariableRule(themeData.theme);
|
||||||
console.log(cssRule);
|
|
||||||
|
|
||||||
var styleElement = $("#gantt-stylesheet")[0];
|
var styleElement = $("#gantt-stylesheet")[0];
|
||||||
if (styleElement) {
|
if (styleElement) {
|
||||||
@@ -133,7 +131,6 @@ function handleWorkScheduleData(data) {
|
|||||||
const workSchedules = data["data"]["work_schedule_info"];
|
const workSchedules = data["data"]["work_schedule_info"];
|
||||||
|
|
||||||
workSchedules.forEach((workSchedule) => {
|
workSchedules.forEach((workSchedule) => {
|
||||||
console.log(workSchedule);
|
|
||||||
const mainContainer = CostUI.text(new Date(workSchedule.CreationDate).toLocaleDateString());
|
const mainContainer = CostUI.text(new Date(workSchedule.CreationDate).toLocaleDateString());
|
||||||
const callback = () => loadWorkSchedule(workSchedule.id);
|
const callback = () => loadWorkSchedule(workSchedule.id);
|
||||||
const card = CostUI.createCard(workSchedule.Name,mainContainer, callback);
|
const card = CostUI.createCard(workSchedule.Name,mainContainer, callback);
|
||||||
@@ -142,11 +139,8 @@ function handleWorkScheduleData(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleGanttData(data) {
|
function handleGanttData(data) {
|
||||||
console.log("running handleGanttData");
|
|
||||||
const blenderId = data["blenderId"];
|
const blenderId = data["blenderId"];
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
const filename = data["data"]["ifc_file"];
|
const filename = data["data"]["ifc_file"];
|
||||||
const ganttTasks = data["data"]["gantt_data"]["tasks"];
|
const ganttTasks = data["data"]["gantt_data"]["tasks"];
|
||||||
const ganttWorkSched = data["data"]["gantt_data"]["work_schedule"];
|
const ganttWorkSched = data["data"]["gantt_data"]["work_schedule"];
|
||||||
@@ -183,7 +177,6 @@ function handleDefaultData(data) {
|
|||||||
const blenderId = data["blenderId"];
|
const blenderId = data["blenderId"];
|
||||||
const isDirty = data["data"]["is_dirty"];
|
const isDirty = data["data"]["is_dirty"];
|
||||||
showWarning(blenderId, isDirty);
|
showWarning(blenderId, isDirty);
|
||||||
console.log('default data',data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to add a new gantt with data and filename
|
// Function to add a new gantt with data and filename
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
export class CostUI {
|
export class CostUI {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
createButton() {
|
|
||||||
console.log("Button created");
|
|
||||||
}
|
|
||||||
|
|
||||||
createInput() {
|
|
||||||
console.log("Input created");
|
|
||||||
}
|
|
||||||
|
|
||||||
static isCostScheduleLoaded(id) {
|
static isCostScheduleLoaded(id) {
|
||||||
const existingTable = document.getElementById('cost-items-' + id);
|
const existingTable = document.getElementById('cost-items-' + id);
|
||||||
return existingTable !== null;
|
return existingTable !== null;
|
||||||
@@ -17,7 +9,7 @@ export class CostUI {
|
|||||||
static removeCostSchedule(id) {
|
static removeCostSchedule(id) {
|
||||||
document.getElementById("cost-items-" + id).remove();
|
document.getElementById("cost-items-" + id).remove();
|
||||||
}
|
}
|
||||||
static createTable(id) {
|
static createTable(id, callbacks) {
|
||||||
CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null;
|
CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null;
|
||||||
|
|
||||||
const table = document.createElement("table");
|
const table = document.createElement("table");
|
||||||
@@ -51,7 +43,7 @@ export class CostUI {
|
|||||||
CostUI.addTableStyles(id);
|
CostUI.addTableStyles(id);
|
||||||
|
|
||||||
// Create context menu
|
// Create context menu
|
||||||
CostUI.createContextMenu();
|
CostUI.createContextMenu(callbacks);
|
||||||
|
|
||||||
table.get_blender_id = function() {
|
table.get_blender_id = function() {
|
||||||
return this.getAttribute("id").split("-")[2];
|
return this.getAttribute("id").split("-")[2];
|
||||||
@@ -71,52 +63,19 @@ export class CostUI {
|
|||||||
#cost-items-${id} td:not(:nth-child(1)) {
|
#cost-items-${id} td:not(:nth-child(1)) {
|
||||||
width: 100px; /* Set a fixed width for other columns */
|
width: 100px; /* Set a fixed width for other columns */
|
||||||
}
|
}
|
||||||
th {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.resizer {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 5px;
|
|
||||||
height: 100%;
|
|
||||||
cursor: col-resize;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.context-menu {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
.context-menu button {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.context-menu button:hover {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
#cost-items tr:hover {
|
|
||||||
background-color: #f0f0f0; /* Change this color to your desired hover color */
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
static createContextMenu() {
|
static createContextMenu(callbacks) {
|
||||||
// Create context menu
|
// Create context menu
|
||||||
const contextMenu = document.createElement("div");
|
const contextMenu = document.createElement("div");
|
||||||
contextMenu.id = "context-menu";
|
contextMenu.id = "context-menu";
|
||||||
contextMenu.classList.add("context-menu");
|
contextMenu.classList.add("context-menu");
|
||||||
contextMenu.innerHTML = `
|
contextMenu.innerHTML = `
|
||||||
<button id="edit-button">Edit</button>
|
<button id="add-cost-item-button">Add sub-cost</button>
|
||||||
|
<button id="edit-cost-values-button">Edit</button>
|
||||||
<button id="delete-button">Delete</button>
|
<button id="delete-button">Delete</button>
|
||||||
<button id="duplicate-button">Duplicate</button>
|
<button id="duplicate-button">Duplicate</button>
|
||||||
`;
|
`;
|
||||||
@@ -126,6 +85,7 @@ export class CostUI {
|
|||||||
document.addEventListener("contextmenu", function(event) {
|
document.addEventListener("contextmenu", function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const targetRow = event.target.closest("tr");
|
const targetRow = event.target.closest("tr");
|
||||||
|
const targetCell = event.target.closest("td");
|
||||||
if (targetRow && targetRow.parentElement.id === "cost-items") {
|
if (targetRow && targetRow.parentElement.id === "cost-items") {
|
||||||
const contextMenu = document.getElementById("context-menu");
|
const contextMenu = document.getElementById("context-menu");
|
||||||
contextMenu.style.display = "block";
|
contextMenu.style.display = "block";
|
||||||
@@ -134,6 +94,7 @@ export class CostUI {
|
|||||||
|
|
||||||
// Store the target row in the context menu for later use
|
// Store the target row in the context menu for later use
|
||||||
contextMenu.targetRow = targetRow;
|
contextMenu.targetRow = targetRow;
|
||||||
|
contextMenu.targetCell = targetCell;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("context-menu").style.display = "none";
|
document.getElementById("context-menu").style.display = "none";
|
||||||
}
|
}
|
||||||
@@ -146,14 +107,32 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("edit-button").addEventListener("click", function() {
|
const editCostValuesButton = document.getElementById("edit-cost-values-button");
|
||||||
|
editCostValuesButton.addEventListener("click", function() {
|
||||||
|
console.log("Edit cost values executed");
|
||||||
const targetRow = document.getElementById("context-menu").targetRow;
|
const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
|
const targetCell = document.getElementById("context-menu").targetCell;
|
||||||
if (targetRow) {
|
if (targetRow) {
|
||||||
// Implement your edit action here
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
console.log("Edit row:", targetRow.getAttribute("id"));
|
callbacks.enableEditingCostValues ? callbacks.enableEditingCostValues(costItemId) : null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("delete-button").addEventListener("click", function() {
|
document.getElementById("delete-button").addEventListener("click", function() {
|
||||||
const targetRow = document.getElementById("context-menu").targetRow;
|
const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
if (targetRow) {
|
if (targetRow) {
|
||||||
@@ -246,7 +225,7 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static createCostSchedule({ data, blenderID, title, callbacks = {} }) {
|
static createCostSchedule({ data, blenderID, title, callbacks = {} }) {
|
||||||
const [table, tbody] = CostUI.createTable(blenderID);
|
const [table, tbody] = CostUI.createTable(blenderID, callbacks);
|
||||||
CostUI.createCostItem(data, tbody, 0, null, callbacks);
|
CostUI.createCostItem(data, tbody, 0, null, callbacks);
|
||||||
CostUI.applyExpandedState();
|
CostUI.applyExpandedState();
|
||||||
}
|
}
|
||||||
@@ -263,6 +242,35 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static createRow(obj, nestingLevel, parentID, callbacks = {}) {
|
static createRow(obj, nestingLevel, parentID, callbacks = {}) {
|
||||||
|
const row = CostUI.createTableRow(obj, nestingLevel, parentID);
|
||||||
|
const expandButton = CostUI.createExpandButton(obj);
|
||||||
|
const nameCell = CostUI.createNameCell(obj, nestingLevel, expandButton, callbacks);
|
||||||
|
const totalCostQuantityCell = CostUI.createTableCell(obj.TotalCostQuantity);
|
||||||
|
const unitSymbolCell = CostUI.createTableCell(obj.UnitSymbol);
|
||||||
|
const totalAppliedValueCell = CostUI.createTableCell(obj.TotalAppliedValue);
|
||||||
|
const totalCostCell = CostUI.createTotalCostCell(obj);
|
||||||
|
const flexContainerCell = CostUI.createFlexContainerCell(obj, callbacks);
|
||||||
|
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
row.appendChild(totalCostQuantityCell);
|
||||||
|
row.appendChild(unitSymbolCell);
|
||||||
|
row.appendChild(totalAppliedValueCell);
|
||||||
|
row.appendChild(totalCostCell);
|
||||||
|
row.appendChild(flexContainerCell);
|
||||||
|
|
||||||
|
row.get_id = function() {
|
||||||
|
return this.getAttribute("id");
|
||||||
|
};
|
||||||
|
|
||||||
|
row.get_parent = function() {
|
||||||
|
const parentId = this.getAttribute("parent-id");
|
||||||
|
return parentId ? document.getElementById(parentId) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createTableRow(obj, nestingLevel, parentID) {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
row.setAttribute("id", obj.id);
|
row.setAttribute("id", obj.id);
|
||||||
row.setAttribute("parent-id", parentID);
|
row.setAttribute("parent-id", parentID);
|
||||||
@@ -270,6 +278,10 @@ export class CostUI {
|
|||||||
row.classList.add("nested");
|
row.classList.add("nested");
|
||||||
row.classList.add(`level-${nestingLevel}`);
|
row.classList.add(`level-${nestingLevel}`);
|
||||||
}
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createExpandButton(obj) {
|
||||||
const expandButton = document.createElement("button");
|
const expandButton = document.createElement("button");
|
||||||
expandButton.classList.add("toggle-button");
|
expandButton.classList.add("toggle-button");
|
||||||
if (obj.is_nested_by && obj.is_nested_by.length > 0) {
|
if (obj.is_nested_by && obj.is_nested_by.length > 0) {
|
||||||
@@ -277,51 +289,62 @@ export class CostUI {
|
|||||||
} else {
|
} else {
|
||||||
expandButton.style.visibility = "hidden";
|
expandButton.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
//row.appendChild(expandButton);
|
|
||||||
|
|
||||||
expandButton.addEventListener("click", function() {
|
expandButton.addEventListener("click", function() {
|
||||||
CostUI.contractExpandRow.call(this, obj.id);
|
CostUI.contractExpandRow.call(this, obj.id);
|
||||||
});
|
});
|
||||||
|
return expandButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createNameCell(obj, 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 = obj.name ? obj.name : "Unnamed";
|
nameInput.value = obj.name ? obj.name : "Unnamed";
|
||||||
|
|
||||||
nameInput.addEventListener("change", function() {
|
nameInput.addEventListener("change", function() {
|
||||||
callbacks.editCostItemName ? callbacks.editCostItemName(obj.id, this.value) : null;
|
callbacks.editCostItemName ? callbacks.editCostItemName(obj.id, this.value) : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
nameInput.addEventListener("keydown", function(event) {
|
nameInput.addEventListener("keydown", function(event) {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
callbacks.editCostItemName ? callbacks.editCostItemName(obj.id, this.value) : null;
|
callbacks.editCostItemName ? callbacks.editCostItemName(obj.id, this.value) : null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nameCell.style.paddingLeft = nestingLevel * 20 + "px";
|
nameCell.style.paddingLeft = nestingLevel * 20 + "px";
|
||||||
nameCell.appendChild(expandButton);
|
nameCell.appendChild(expandButton);
|
||||||
nameCell.appendChild(nameInput);
|
nameCell.appendChild(nameInput);
|
||||||
row.appendChild(nameCell);
|
return nameCell;
|
||||||
|
}
|
||||||
const totalCostQuantityCell = document.createElement("td");
|
|
||||||
totalCostQuantityCell.textContent = obj.TotalCostQuantity;
|
static createTableCell(content) {
|
||||||
row.appendChild(totalCostQuantityCell);
|
const cell = document.createElement("td");
|
||||||
|
cell.textContent = content;
|
||||||
const unitSymbolCell = document.createElement("td");
|
return cell;
|
||||||
unitSymbolCell.textContent = obj.UnitSymbol;
|
}
|
||||||
row.appendChild(unitSymbolCell);
|
|
||||||
|
static createTotalCostCell(obj) {
|
||||||
const totalAppliedValueCell = document.createElement("td");
|
|
||||||
totalAppliedValueCell.textContent = obj.TotalAppliedValue;
|
|
||||||
row.appendChild(totalAppliedValueCell);
|
|
||||||
|
|
||||||
const totalCostCell = document.createElement("td");
|
const totalCostCell = document.createElement("td");
|
||||||
const totalCost = parseFloat(obj.TotalCost).toFixed(2);
|
const totalCost = parseFloat(obj.TotalCost).toFixed(2);
|
||||||
|
|
||||||
totalCostCell.textContent = obj.is_sum ? totalCost + " (Σ)" : totalCost;
|
totalCostCell.textContent = obj.is_sum ? totalCost + " (Σ)" : totalCost;
|
||||||
|
return totalCostCell;
|
||||||
row.appendChild(totalCostCell);
|
}
|
||||||
|
|
||||||
|
static createFlexContainerCell(obj, callbacks) {
|
||||||
const divFlex = document.createElement("div");
|
const divFlex = document.createElement("div");
|
||||||
divFlex.classList.add("flex-container");
|
divFlex.classList.add("flex-container");
|
||||||
|
|
||||||
|
const addButton = CostUI.createAddButton(obj, callbacks);
|
||||||
|
const selectButton = CostUI.createSelectButton(obj, callbacks);
|
||||||
|
|
||||||
|
divFlex.appendChild(addButton);
|
||||||
|
divFlex.appendChild(selectButton);
|
||||||
|
|
||||||
|
const flexContainerCell = document.createElement("td");
|
||||||
|
flexContainerCell.appendChild(divFlex);
|
||||||
|
return flexContainerCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createAddButton(obj, callbacks) {
|
||||||
const addButton = document.createElement("button");
|
const addButton = document.createElement("button");
|
||||||
addButton.textContent = "+";
|
addButton.textContent = "+";
|
||||||
addButton.classList.add("add-button");
|
addButton.classList.add("add-button");
|
||||||
@@ -329,31 +352,17 @@ export class CostUI {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
callbacks.addCostItem ? callbacks.addCostItem(obj.id) : null;
|
callbacks.addCostItem ? callbacks.addCostItem(obj.id) : null;
|
||||||
});
|
});
|
||||||
|
return addButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createSelectButton(obj, callbacks) {
|
||||||
const selectButton = document.createElement("button");
|
const selectButton = document.createElement("button");
|
||||||
selectButton.textContent = "Select";
|
selectButton.textContent = "Select";
|
||||||
selectButton.addEventListener("click", function(e) {
|
selectButton.addEventListener("click", function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
callbacks.selectAssignedElements ? callbacks.selectAssignedElements(obj.id) : null;
|
callbacks.selectAssignedElements ? callbacks.selectAssignedElements(obj.id) : null;
|
||||||
});
|
});
|
||||||
|
return selectButton;
|
||||||
divFlex.appendChild(addButton);
|
|
||||||
divFlex.appendChild(selectButton);
|
|
||||||
|
|
||||||
const flexContainerCell = document.createElement("td");
|
|
||||||
flexContainerCell.appendChild(divFlex);
|
|
||||||
row.appendChild(flexContainerCell);
|
|
||||||
|
|
||||||
row.get_id = function() {
|
|
||||||
return this.getAttribute("id");
|
|
||||||
};
|
|
||||||
|
|
||||||
row.get_parent = function() {
|
|
||||||
const parentId = this.getAttribute("parent-id");
|
|
||||||
return parentId ? document.getElementById(parentId) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static hideNestedRows(parentId) {
|
static hideNestedRows(parentId) {
|
||||||
@@ -461,4 +470,267 @@ export class CostUI {
|
|||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createCostValuesForm({ costItemId, costValues, callbacks }) {
|
||||||
|
// Check if the form already exists and remove it if it does
|
||||||
|
const formId = "cost-values-form-" + costItemId;
|
||||||
|
let existingForm = document.getElementById(formId);
|
||||||
|
if (existingForm) {
|
||||||
|
existingForm.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = CostUI.createFormElement(formId, "cost-values-form");
|
||||||
|
|
||||||
|
const header = CostUI.createHeader("Cost Values Form");
|
||||||
|
form.appendChild(header);
|
||||||
|
|
||||||
|
const table = CostUI.createCostValuesTable(costItemId, ["Type", "Category", "Value"]);
|
||||||
|
|
||||||
|
costValues.forEach(costValue => {
|
||||||
|
const tr = CostUI.createCostvaluesRow(costItemId, costValue);
|
||||||
|
table.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
form.appendChild(table);
|
||||||
|
|
||||||
|
const addButton = CostUI.createAddCostValueButton(costItemId, callbacks);
|
||||||
|
form.appendChild(addButton);
|
||||||
|
|
||||||
|
const submitButton = CostUI.createSubmitButton(callbacks);
|
||||||
|
form.appendChild(submitButton);
|
||||||
|
|
||||||
|
const closeButton = CostUI.createCloseButton(form);
|
||||||
|
form.appendChild(closeButton);
|
||||||
|
|
||||||
|
CostUI.makeHeaderDraggable(header, form);
|
||||||
|
|
||||||
|
document.body.appendChild(form);
|
||||||
|
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static getCostValuesTable(costItemId) {
|
||||||
|
return document.getElementById("cost-values-table-" + costItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static addNewCostValueRow(costItemId, costValueId) {
|
||||||
|
const table = CostUI.getCostValuesTable(costItemId);
|
||||||
|
// check if table exists
|
||||||
|
if (!table) {
|
||||||
|
console.log("Cost values table not found for cost item ID:", costItemId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Adding new cost value row", costValueId);
|
||||||
|
console.log(table)
|
||||||
|
const tr = CostUI.createCostvaluesRow(costItemId, {"category": "", "name": "", "applied_value": 0, "id": costValueId, "parent": costItemId});
|
||||||
|
table.appendChild(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static createCostValuesTable(costItemId, headers) {
|
||||||
|
const table = document.createElement("table");
|
||||||
|
table.classList.add("cost-values-table");
|
||||||
|
table.id = "cost-values-table-" + costItemId;
|
||||||
|
|
||||||
|
const headerRow = document.createElement("tr");
|
||||||
|
headers.forEach(headerText => {
|
||||||
|
const th = document.createElement("th");
|
||||||
|
th.textContent = headerText;
|
||||||
|
headerRow.appendChild(th);
|
||||||
|
});
|
||||||
|
table.appendChild(headerRow);
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createCostvaluesRow(costItemId,costValue) {
|
||||||
|
|
||||||
|
function cleanLabel(value) {
|
||||||
|
// remove anything which is not a dot or a digit
|
||||||
|
return value.replace(/[^0-9.]/g, '');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (document.getElementById(costValue.id)) {
|
||||||
|
return document.getElementById(costValue.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.isCostValue = true;
|
||||||
|
tr.parent = costItemId;
|
||||||
|
tr.id = costValue.id;
|
||||||
|
|
||||||
|
let costType = "FIXED";
|
||||||
|
if (costValue.category === "*") {
|
||||||
|
costType = "SUM";
|
||||||
|
}
|
||||||
|
else if (costValue.category && costValue.category !== "*") {
|
||||||
|
costType = "CATEGORY";
|
||||||
|
}
|
||||||
|
else if (costValue.applied_value) {
|
||||||
|
costType = "FIXED";
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeCell = CostUI.createTableDropdown("type", costType);
|
||||||
|
const dropdown = typeCell.querySelector("select");
|
||||||
|
dropdown.addEventListener("change", function() {
|
||||||
|
const selectedType = this.value;
|
||||||
|
CostUI.updateRowBasedOnType(selectedType, categoryCell, valueCell1);
|
||||||
|
});
|
||||||
|
tr.appendChild(typeCell);
|
||||||
|
|
||||||
|
const categoryCell = CostUI.createTableInput("text", "category", costValue.category);
|
||||||
|
tr.appendChild(categoryCell);
|
||||||
|
|
||||||
|
let value
|
||||||
|
|
||||||
|
if (costValue.category === "*"){
|
||||||
|
value = cleanLabel(costValue.label)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = costValue.applied_value
|
||||||
|
}
|
||||||
|
const valueCell1 = CostUI.createTableInput("number", "value", value);
|
||||||
|
tr.appendChild(valueCell1);
|
||||||
|
// Apply initial state based on costType
|
||||||
|
CostUI.updateRowBasedOnType(costType, categoryCell, valueCell1);
|
||||||
|
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static updateRowBasedOnType(type, categoryCell, valueCell1) {
|
||||||
|
if (type === "FIXED") {
|
||||||
|
categoryCell.querySelector("input").disabled = true;
|
||||||
|
categoryCell.querySelector("input").style.backgroundColor = "lightgrey";
|
||||||
|
valueCell1.querySelector("input").disabled = false;
|
||||||
|
valueCell1.querySelector("input").style.backgroundColor = "";
|
||||||
|
} else if (type === "CATEGORY") {
|
||||||
|
categoryCell.querySelector("input").disabled = false;
|
||||||
|
categoryCell.querySelector("input").style.backgroundColor = "";
|
||||||
|
valueCell1.querySelector("input").disabled = false;
|
||||||
|
valueCell1.querySelector("input").style.backgroundColor = "";
|
||||||
|
} else if (type === "SUM") {
|
||||||
|
categoryCell.querySelector("input").disabled = true;
|
||||||
|
categoryCell.querySelector("input").style.backgroundColor = "lightgrey";
|
||||||
|
valueCell1.querySelector("input").disabled = true;
|
||||||
|
valueCell1.querySelector("input").style.backgroundColor = "lightgrey";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static createTableDropdown(name, value="FIXED") {
|
||||||
|
const cell = document.createElement("td");
|
||||||
|
const dropdown = document.createElement("select");
|
||||||
|
dropdown.name = name;
|
||||||
|
|
||||||
|
const options = ["FIXED", "CATEGORY", "SUM"];
|
||||||
|
options.forEach(optionValue => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = optionValue;
|
||||||
|
option.textContent = optionValue;
|
||||||
|
if (optionValue === value) {
|
||||||
|
option.selected = true;
|
||||||
|
}
|
||||||
|
dropdown.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
cell.appendChild(dropdown);
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createTableInput(type, name, value) {
|
||||||
|
const cell = document.createElement("td");
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = type;
|
||||||
|
input.name = name;
|
||||||
|
input.value = value;
|
||||||
|
cell.appendChild(input);
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createAddCostValueButton(costItemId, callbacks) {
|
||||||
|
const addButton = document.createElement("button");
|
||||||
|
addButton.textContent = "+";
|
||||||
|
addButton.classList.add("add-button");
|
||||||
|
addButton.addEventListener("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
callbacks.addCostValue ? callbacks.addCostValue(costItemId) : null;
|
||||||
|
});
|
||||||
|
return addButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createSubmitButton(callbacks) {
|
||||||
|
const submitButton = document.createElement("button");
|
||||||
|
submitButton.type = "submit";
|
||||||
|
submitButton.textContent = "Save";
|
||||||
|
|
||||||
|
submitButton.addEventListener("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const form = this.closest("form");
|
||||||
|
const costValues = [];
|
||||||
|
const rows = Array.from(form.querySelectorAll("tr")).filter(row => row.isCostValue);
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const costCategory = row.querySelector("input[name='category']").value;
|
||||||
|
const appliedValue = parseFloat(row.querySelector("input[name='value']").value);
|
||||||
|
const costType = row.querySelector("select[name='type']").value;
|
||||||
|
const id = parseInt(row.id);
|
||||||
|
costValues.push({ costType, costCategory, appliedValue, id: id, costItemId: row.parent });
|
||||||
|
});
|
||||||
|
const costItemId = parseInt(form.id.split("-")[3]);
|
||||||
|
console.log(form.id)
|
||||||
|
console.log("Cost values to be saved:", costItemId);
|
||||||
|
callbacks.editCostValues ? callbacks.editCostValues(costItemId, costValues) : null;
|
||||||
|
form.remove();
|
||||||
|
});
|
||||||
|
return submitButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createFormElement(id, className) {
|
||||||
|
const form = document.createElement("form");
|
||||||
|
form.id = id;
|
||||||
|
form.classList.add(className);
|
||||||
|
form.style.position = "absolute"; // Set initial position to absolute
|
||||||
|
form.style.top = "50%";
|
||||||
|
form.style.left = "50%";
|
||||||
|
form.style.transform = "translate(-50%, -50%)";
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createHeader(text) {
|
||||||
|
const header = document.createElement("div");
|
||||||
|
header.classList.add("form-header");
|
||||||
|
header.textContent = text;
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createCloseButton(form) {
|
||||||
|
const closeButton = document.createElement("span");
|
||||||
|
closeButton.classList.add("close-button");
|
||||||
|
closeButton.innerHTML = "×";
|
||||||
|
closeButton.addEventListener("click", function() {
|
||||||
|
form.remove();
|
||||||
|
});
|
||||||
|
return closeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static makeHeaderDraggable(header, form) {
|
||||||
|
header.addEventListener("mousedown", function(e) {
|
||||||
|
let offsetX = e.clientX - form.getBoundingClientRect().left;
|
||||||
|
let offsetY = e.clientY - form.getBoundingClientRect().top;
|
||||||
|
|
||||||
|
function mouseMoveHandler(e) {
|
||||||
|
form.style.left = `${e.clientX - offsetX}px`;
|
||||||
|
form.style.top = `${e.clientY - offsetY}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseUpHandler() {
|
||||||
|
document.removeEventListener("mousemove", mouseMoveHandler);
|
||||||
|
document.removeEventListener("mouseup", mouseUpHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", mouseMoveHandler);
|
||||||
|
document.addEventListener("mouseup", mouseUpHandler);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>BlenderBIM Web UI</title>
|
<title>BlenderBIM Web UI</title>
|
||||||
<link rel="stylesheet" href="/static/css/gantt.css" id="index-stylesheet" />
|
<link rel="stylesheet" type="text/css" href="/static/css/gantt.css" id="index-stylesheet" />
|
||||||
<link rel="stylesheet" href="/static/css/components/card.css" />
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
id="tabulator-stylesheet"
|
id="tabulator-stylesheet"
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ classes = (
|
|||||||
operator.SelectUnassignedProducts,
|
operator.SelectUnassignedProducts,
|
||||||
operator.UnassignCostItemQuantity,
|
operator.UnassignCostItemQuantity,
|
||||||
operator.UnassignCostItemType,
|
operator.UnassignCostItemType,
|
||||||
|
operator.GenerateCostScheduleBrowser,
|
||||||
prop.CostItem,
|
prop.CostItem,
|
||||||
prop.CostItemQuantity,
|
prop.CostItemQuantity,
|
||||||
prop.CostItemType,
|
prop.CostItemType,
|
||||||
|
|||||||
@@ -265,16 +265,10 @@ class CostSchedulesData:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cost_values(cls):
|
def cost_values(cls):
|
||||||
results = []
|
|
||||||
ifc_id = bpy.context.scene.BIMCostProperties.active_cost_item_id
|
ifc_id = bpy.context.scene.BIMCostProperties.active_cost_item_id
|
||||||
if not ifc_id:
|
if not ifc_id:
|
||||||
return results
|
return []
|
||||||
cost_item = tool.Ifc.get().by_id(ifc_id)
|
return ifcopenshell.util.cost.get_cost_values(tool.Ifc.get().by_id(ifc_id))
|
||||||
for cost_value in cost_item.CostValues or []:
|
|
||||||
label = "{0:.2f}".format(ifcopenshell.util.cost.calculate_applied_value(cost_item, cost_value))
|
|
||||||
label += " = {}".format(ifcopenshell.util.cost.serialise_cost_value(cost_value))
|
|
||||||
results.append({"id": cost_value.id(), "label": label, "name": cost_value.Name})
|
|
||||||
return results
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def quantity_types(cls):
|
def quantity_types(cls):
|
||||||
|
|||||||
@@ -790,3 +790,14 @@ class AddCurrency(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
core.add_currency(tool.Ifc, tool.Cost)
|
core.add_currency(tool.Ifc, tool.Cost)
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateCostScheduleBrowser(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.generate_cost_schedule_browser"
|
||||||
|
bl_label = "Generate Cost Schedule Browser"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
cost_schedule: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
core.generate_cost_schedule_browser(tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
|
||||||
|
return {"FINISHED"}
|
||||||
@@ -73,7 +73,8 @@ class BIM_PT_cost_schedules(Panel):
|
|||||||
row1.label(text="Schedule tools")
|
row1.label(text="Schedule tools")
|
||||||
row1 = col.row(align=True)
|
row1 = col.row(align=True)
|
||||||
row1.alignment = "RIGHT"
|
row1.alignment = "RIGHT"
|
||||||
row1.operator("bim.export_cost_schedules", text="Export", icon="EXPORT").cost_schedule = cost_schedule["id"]
|
row1.operator("bim.export_cost_schedules", text="Export spreadsheet", icon="EXPORT").cost_schedule = cost_schedule["id"]
|
||||||
|
row1.operator("bim.generate_cost_schedule_browser", text="Generate spreadsheet browsser", icon="URL").cost_schedule = cost_schedule["id"]
|
||||||
row2 = col.row(align=True)
|
row2 = col.row(align=True)
|
||||||
row2.alignment = "RIGHT"
|
row2.alignment = "RIGHT"
|
||||||
op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
|
op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
|
||||||
|
|||||||
@@ -398,3 +398,8 @@ def add_currency(ifc: tool.Ifc, cost: tool.Cost) -> ifcopenshell.entity_instance
|
|||||||
ifc.run("unit.edit_monetary_unit", unit=unit, attributes=attributes)
|
ifc.run("unit.edit_monetary_unit", unit=unit, attributes=attributes)
|
||||||
ifc.run("unit.assign_unit", units=[unit])
|
ifc.run("unit.assign_unit", units=[unit])
|
||||||
return unit
|
return unit
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cost_schedule_browser(cost: tool.Cost, cost_schedule: ifcopenshell.entity_instance) -> bpy.types.Panel:
|
||||||
|
cost_schedule_data = cost.create_cost_schedule_json(cost_schedule)
|
||||||
|
return cost.generate_cost_schedule_browser(cost_schedule_data)
|
||||||
@@ -811,9 +811,8 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create_cost_schedule_json(cls, cost_schedule: ifcopenshell.entity_instance) -> dict:
|
def create_cost_schedule_json(cls, cost_schedule: ifcopenshell.entity_instance) -> dict:
|
||||||
from bonsai.bim.module.cost.data import CostSchedulesData
|
from bonsai.bim.module.cost.data import CostSchedulesData
|
||||||
if not CostSchedulesData.is_loaded:
|
CostSchedulesData.load()
|
||||||
CostSchedulesData.load()
|
cost_items = CostSchedulesData.data["cost_items"]
|
||||||
cost_items = CostSchedulesData.data["cost_items"]
|
|
||||||
data = []
|
data = []
|
||||||
for rel in cost_schedule.Controls or []:
|
for rel in cost_schedule.Controls or []:
|
||||||
for cost_item in rel.RelatedObjects or []:
|
for cost_item in rel.RelatedObjects or []:
|
||||||
@@ -852,3 +851,9 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
unit = tool.Unit.get_project_currency_unit()
|
unit = tool.Unit.get_project_currency_unit()
|
||||||
if unit:
|
if unit:
|
||||||
return {"id": unit.id(), "name": unit.Currency}
|
return {"id": unit.id(), "name": unit.Currency}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_cost_schedule_browser(cls, cost_schedule_data: list[dict[str, Any]]) -> None:
|
||||||
|
if not bpy.context.scene.WebProperties.is_connected:
|
||||||
|
bpy.ops.bim.connect_websocket_server(page="costing")
|
||||||
|
tool.Web.send_webui_data(data=cost_schedule_data, data_key="cost_items", event="cost_items")
|
||||||
@@ -21,6 +21,7 @@ from bonsai.bim.module.web.data import WebData
|
|||||||
import bonsai.core.tool
|
import bonsai.core.tool
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
import ifcopenshell.api.sequence
|
import ifcopenshell.api.sequence
|
||||||
|
import ifcopenshell.api.cost
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
import time
|
import time
|
||||||
import socket
|
import socket
|
||||||
@@ -354,17 +355,15 @@ class Web(bonsai.core.tool.Web):
|
|||||||
if operator_data["type"] == "loadCostSchedule":
|
if operator_data["type"] == "loadCostSchedule":
|
||||||
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)
|
bonsai.core.cost.enable_editing_cost_items(tool.Cost, cost_schedule=cost_schedule)
|
||||||
json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
cls.send_webui_data(data=json_data, data_key="cost_items", event="cost_items")
|
|
||||||
if operator_data["type"] == "addCostItem":
|
|
||||||
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"]))
|
|
||||||
json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
|
|
||||||
cls.send_webui_data(data=json_data, data_key="cost_items", event="cost_items")
|
|
||||||
if operator_data["type"] == "selectAssignedElements":
|
if operator_data["type"] == "selectAssignedElements":
|
||||||
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"] == "addCostItem":
|
||||||
|
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"]))
|
||||||
|
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(
|
tool.Ifc.run(
|
||||||
@@ -373,6 +372,65 @@ class Web(bonsai.core.tool.Web):
|
|||||||
attributes = {"Name": operator_data["name"]}
|
attributes = {"Name": operator_data["name"]}
|
||||||
)
|
)
|
||||||
tool.Cost.load_cost_schedule_tree()
|
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)
|
||||||
|
cls.send_webui_data(data={
|
||||||
|
"cost_values": cost_values,
|
||||||
|
"cost_item_id": operator_data["costItemId"]
|
||||||
|
}, data_key="cost_values", event="cost_values")
|
||||||
|
if operator_data["type"] == "addCostValue":
|
||||||
|
value = ifcopenshell.api.cost.add_cost_value(
|
||||||
|
ifc_file,
|
||||||
|
parent=ifc_file.by_id(operator_data["costItemId"]),
|
||||||
|
)
|
||||||
|
cls.send_webui_data(
|
||||||
|
data={
|
||||||
|
"cost_value_id" : value.id(),
|
||||||
|
"cost_item_id":operator_data["costItemId"]},
|
||||||
|
data_key="cost_value",
|
||||||
|
event="cost_value"
|
||||||
|
)
|
||||||
|
if operator_data["type"] == "editCostValues":
|
||||||
|
cost_item_id = operator_data["costItemId"]
|
||||||
|
print('Editing cost values', cost_item_id)
|
||||||
|
print(cost_item_id)
|
||||||
|
print(type(operator_data["costValues"]))
|
||||||
|
for value_data in operator_data["costValues"] or []:
|
||||||
|
print(value_data)
|
||||||
|
value = ifc_file.by_id(value_data["id"])
|
||||||
|
print(value.get_info())
|
||||||
|
if value_data["costType"] == "FIXED":
|
||||||
|
attributes= {
|
||||||
|
"AppliedValue": value_data["appliedValue"],
|
||||||
|
}
|
||||||
|
elif value_data["costType"] == "CATEGORY":
|
||||||
|
attributes= {
|
||||||
|
"AppliedValue": value_data["appliedValue"],
|
||||||
|
"Category": value_data["costCategory"],
|
||||||
|
}
|
||||||
|
elif value_data["costType"] == "SUM":
|
||||||
|
attributes= {
|
||||||
|
"Category": '*'
|
||||||
|
}
|
||||||
|
ifcopenshell.api.cost.edit_cost_value(
|
||||||
|
file=ifc_file,
|
||||||
|
cost_value=value,
|
||||||
|
attributes= attributes
|
||||||
|
)
|
||||||
|
tool.Cost.load_cost_schedule_tree()
|
||||||
|
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||||
|
if not cost_item:
|
||||||
|
print("Cost item not found")
|
||||||
|
return
|
||||||
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||||
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_cost_schedule_web_ui(cls, cost_schedule):
|
||||||
|
json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
|
||||||
|
cls.send_webui_data(data=json_data, data_key="cost_items", event="cost_items")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def handle_gantt_operator(cls, operator_data: dict) -> None:
|
def handle_gantt_operator(cls, operator_data: dict) -> None:
|
||||||
|
|||||||
@@ -245,6 +245,20 @@ def get_cost_item_assignments(
|
|||||||
for product in get_cost_assignments_by_type(nested_cost_item, filter_by_type)
|
for product in get_cost_assignments_by_type(nested_cost_item, filter_by_type)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_cost_values(cost_item: ifcopenshell.entity_instance) -> list[dict[str, str]]:
|
||||||
|
results = []
|
||||||
|
for cost_value in cost_item.CostValues or []:
|
||||||
|
label = "{0:.2f}".format(calculate_applied_value(cost_item, cost_value))
|
||||||
|
label += " = {}".format(serialise_cost_value(cost_value))
|
||||||
|
results.append({
|
||||||
|
"id": cost_value.id(),
|
||||||
|
"label": label,
|
||||||
|
"name": cost_value.Name,
|
||||||
|
"category": cost_value.Category,
|
||||||
|
"applied_value": get_primitive_applied_value(cost_value.AppliedValue) if cost_value.AppliedValue else None,
|
||||||
|
})
|
||||||
|
print(results)
|
||||||
|
return results
|
||||||
|
|
||||||
class CostValueUnserialiser:
|
class CostValueUnserialiser:
|
||||||
def parse(self, formula: str):
|
def parse(self, formula: str):
|
||||||
|
|||||||
Reference in New Issue
Block a user