mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 16:01:36 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9aba274070 | |||
| 3b7d44a1c6 | |||
| bf722ecfe3 | |||
| 61cff3df43 | |||
| 8b4b1da8a4 | |||
| 69997bf9d8 | |||
| 53907accd9 | |||
| a4f881517b | |||
| 8acaf0ff26 | |||
| 105bfb5647 | |||
| 866e3b0f66 | |||
| 79d6801315 | |||
| a2539b659f | |||
| 3e154b6344 | |||
| c1fa735f9c | |||
| 9e5f73701c | |||
| 7fa8ebfbd1 | |||
| d47e5c1167 | |||
| d142fa5e62 | |||
| 27ef2b5d49 |
@@ -122,27 +122,21 @@ class BlenderNamespace(socketio.AsyncNamespace):
|
||||
await sio.emit("theme_data", data, namespace="/web")
|
||||
|
||||
|
||||
# Attach namespaces
|
||||
sio.register_namespace(WebNamespace("/web"))
|
||||
sio.register_namespace(BlenderNamespace("/blender"))
|
||||
|
||||
|
||||
# Define a route to render the index.html template
|
||||
async def index(request):
|
||||
async def schedules(request):
|
||||
with open("templates/index.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def gantt(request):
|
||||
async def sequencing(request):
|
||||
with open("templates/gantt.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def drawings(request):
|
||||
async def documentation(request):
|
||||
with open("templates/drawings.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
||||
@@ -164,12 +158,21 @@ async def on_startup(app):
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
|
||||
app.router.add_get("/", index)
|
||||
app.router.add_get("/drawings", drawings)
|
||||
app.router.add_get("/gantt", gantt)
|
||||
# Add on startup function
|
||||
app.on_startup.append(on_startup)
|
||||
|
||||
# Attach namespaces
|
||||
sio.register_namespace(WebNamespace("/web"))
|
||||
sio.register_namespace(BlenderNamespace("/blender"))
|
||||
|
||||
# Regsier routes
|
||||
app.router.add_get("/", schedules)
|
||||
app.router.add_get("/documentation", documentation)
|
||||
app.router.add_get("/sequencing", sequencing)
|
||||
|
||||
# Add static files
|
||||
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
|
||||
app.router.add_static("/static/", path="./static", name="static")
|
||||
app.on_startup.append(on_startup)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -119,11 +119,9 @@ nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-color));
|
||||
}
|
||||
|
||||
.warning {
|
||||
.warning,
|
||||
.warning-icon {
|
||||
color: var(--blender-info-warning, var(--warning-color));
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-description {
|
||||
@@ -144,6 +142,14 @@ nav ul li a.active {
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
}
|
||||
|
||||
.table-description tr:nth-child(even) {
|
||||
background-color: var(--blender-even-row);
|
||||
}
|
||||
|
||||
.table-description tr:nth-child(odd) {
|
||||
background-color: var(--blender-odd-row);
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
@@ -342,12 +348,6 @@ button:hover {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* .gTaskInfo {
|
||||
background-color: #2e2e2e;
|
||||
color: #c7c7c7;
|
||||
border-color: #c7c7c7;;
|
||||
} */
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
:root.dark .frame::-webkit-scrollbar-thumb,
|
||||
:root.dark .frame::-webkit-scrollbar-track {
|
||||
@@ -465,6 +465,11 @@ button:hover {
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gtaskcellcurrent {
|
||||
background-color: var(--blender-odd-row);
|
||||
border-color: var(--blender-tab-outline);
|
||||
}
|
||||
|
||||
:root.blender .gtaskheading,
|
||||
:root.blender .gname,
|
||||
:root.blender .gtaskname,
|
||||
|
||||
@@ -105,11 +105,9 @@ nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-color));
|
||||
}
|
||||
|
||||
.warning {
|
||||
.warning,
|
||||
.warning-icon {
|
||||
color: var(--blender-info-warning, var(--warning-color));
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-tiny);
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
|
||||
@@ -5,10 +5,6 @@ let socket;
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
$(":root").css("color-scheme", systemTheme);
|
||||
var defaultTheme = "blender";
|
||||
var theme = localStorage.getItem("theme") || defaultTheme;
|
||||
setTheme(theme);
|
||||
@@ -46,7 +42,7 @@ function handleWebConnect() {
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
console.log("blender connected: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: false,
|
||||
@@ -77,7 +73,7 @@ function handleDefaultData(data) {
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
console.log("blender disconnected: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeSelectOption(blenderId);
|
||||
@@ -90,7 +86,7 @@ function handleBlenderDisconnect(blenderId) {
|
||||
|
||||
function handleConnectedClients(data) {
|
||||
$("#blender-count").text(data.length);
|
||||
console.log(data);
|
||||
// console.log(data);
|
||||
data.forEach(function (id) {
|
||||
connectedClients[id] = {
|
||||
shown: false,
|
||||
@@ -101,7 +97,7 @@ function handleConnectedClients(data) {
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
console.log(themeData);
|
||||
// console.log(themeData);
|
||||
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
@@ -145,7 +141,7 @@ function handleDrawingsData(data) {
|
||||
|
||||
const hasDrawings = drawings.length > 0 || sheets.length > 0;
|
||||
|
||||
console.log(connectedClients);
|
||||
// console.log(connectedClients);
|
||||
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
@@ -203,11 +199,10 @@ function addSelectOption(blenderId, filename) {
|
||||
|
||||
function updateSelectOption(blenderId, filename) {
|
||||
$("#blender-" + blenderId).text(filename);
|
||||
console.log(blenderId, filename);
|
||||
// console.log(blenderId, filename);
|
||||
console.log($("#dropdown-menu").val());
|
||||
|
||||
if ($("#dropdown-menu").val() === filename) {
|
||||
console.log("here");
|
||||
displayDrawingsNames(blenderId, filename);
|
||||
}
|
||||
}
|
||||
@@ -253,7 +248,7 @@ function displayDrawingsNames(blenderId, ifcFile) {
|
||||
const msg = {
|
||||
path: path,
|
||||
};
|
||||
console.log(msg);
|
||||
// console.log(msg);
|
||||
socket.emit("get_svg", msg);
|
||||
|
||||
$("li").removeClass("selected-svg");
|
||||
@@ -277,6 +272,7 @@ function displayDrawingsNames(blenderId, ifcFile) {
|
||||
|
||||
function setTheme(theme) {
|
||||
$("html").removeClass("light dark blender").addClass(theme);
|
||||
$(":root").css("color-scheme", theme);
|
||||
if (theme === "light") {
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else if (theme === "dark") {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// keeps track of blenders connected in form of
|
||||
// shown:bool, workSchedule: {}, ganttTasks: {},
|
||||
// shown:bool, workSchedule: {}, ganttTasks: {}, warning: bool
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
@@ -19,10 +19,6 @@ const pageSizes = [
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
$(":root").css("color-scheme", systemTheme);
|
||||
var defaultTheme = "blender";
|
||||
var theme = localStorage.getItem("theme") || defaultTheme;
|
||||
setTheme(theme);
|
||||
@@ -47,7 +43,7 @@ function connectSocket() {
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
console.log("blender connected: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: false,
|
||||
@@ -63,7 +59,7 @@ function handleBlenderConnect(blenderId) {
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
console.log("blender disconnected: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeGanttElement(blenderId);
|
||||
@@ -76,7 +72,7 @@ function handleBlenderDisconnect(blenderId) {
|
||||
|
||||
function handleConnectedClients(data) {
|
||||
$("#blender-count").text(data.length);
|
||||
console.log(data);
|
||||
// console.log(data);
|
||||
data.forEach(function (id) {
|
||||
connectedClients[id] = {
|
||||
shown: false,
|
||||
@@ -87,7 +83,7 @@ function handleConnectedClients(data) {
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
console.log(themeData);
|
||||
// console.log(themeData);
|
||||
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
@@ -135,12 +131,14 @@ function handleGanttData(data) {
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
warning: false,
|
||||
};
|
||||
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
} else {
|
||||
updateGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
connectedClients[blenderId].workSchedule = ganttWorkSched;
|
||||
connectedClients[blenderId].ganttTasks = ganttTasks;
|
||||
connectedClients[blenderId].warning = false;
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
@@ -148,6 +146,7 @@ function handleGanttData(data) {
|
||||
ifc_file: filename,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
warning: false,
|
||||
};
|
||||
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
}
|
||||
@@ -161,7 +160,163 @@ function handleDefaultData(data) {
|
||||
}
|
||||
|
||||
// Function to add a new gantt with data and filename
|
||||
|
||||
function addGanttElement(blenderId, tasks, workSched, filename) {
|
||||
function createWorkSchedDiv(blenderId, workSched) {
|
||||
const workSchedDiv = $("<div></div>").attr("id", "workSched" + blenderId);
|
||||
const scheduleTable = $("<table></table>")
|
||||
.addClass("no-print table-description")
|
||||
.attr("id", "workSchedTable-" + blenderId)
|
||||
.hide();
|
||||
|
||||
$.each(workSched, (key, value) => {
|
||||
value = value ? value : "null";
|
||||
$("<tr></tr>")
|
||||
.append($("<td></td>").text(key))
|
||||
.append($("<td></td>").text(value))
|
||||
.appendTo(scheduleTable);
|
||||
});
|
||||
|
||||
const toggleButton = $("<button></button>")
|
||||
.text("Show Schedule Info")
|
||||
.addClass("btn no-print")
|
||||
.on("click", function () {
|
||||
scheduleTable.toggle();
|
||||
const buttonText = scheduleTable.is(":visible")
|
||||
? "Hide Schedule Info"
|
||||
: "Show Schedule Info";
|
||||
toggleButton.text(buttonText);
|
||||
});
|
||||
|
||||
workSchedDiv.append(toggleButton);
|
||||
workSchedDiv.append(scheduleTable);
|
||||
|
||||
return workSchedDiv;
|
||||
}
|
||||
|
||||
function createGanttInfoDiv(blenderId, workSched) {
|
||||
const ganttInfoDiv = $("<div></div>")
|
||||
.addClass("gantt-info")
|
||||
.attr("id", "gantt-info-" + blenderId);
|
||||
|
||||
const scheduleName = $("<span></span>").text("Schedule: " + workSched.Name);
|
||||
const createdOn = $("<span></span>")
|
||||
.text("Created: " + new Date(workSched.CreationDate).toLocaleDateString())
|
||||
.css("float", "right");
|
||||
|
||||
ganttInfoDiv.append(scheduleName);
|
||||
ganttInfoDiv.append(createdOn);
|
||||
|
||||
return ganttInfoDiv;
|
||||
}
|
||||
|
||||
function createGantt(blenderId) {
|
||||
const ganttDiv = $("<div></div>")
|
||||
.addClass("gantt-chart")
|
||||
.attr("id", "gantt-" + blenderId);
|
||||
|
||||
let g = new JSGantt.GanttChart(ganttDiv[0], "week");
|
||||
g.setOptions({
|
||||
vCaptionType: "Caption", // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: "day dd month yyyy", // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: "mon yyyy - Week ww", // Set format to dates in the "Major" header of the "Day" view
|
||||
vWeekMinorDateDisplayFormat: "dd mon", // Set format to display dates in the "Minor" header of the "Week" view
|
||||
vLang: "en",
|
||||
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
|
||||
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
|
||||
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
|
||||
vFormatArr: ["Day", "Week", "Month", "Quarter"], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
|
||||
vShowRes: true, // Disable the resource column.
|
||||
vShowComp: false, // Disable the completion column.
|
||||
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
|
||||
vAdditionalHeaders: {
|
||||
ifcduration: { title: "Duration" },
|
||||
resourceUsage: { title: "Resource Usage" },
|
||||
},
|
||||
vUseToolTip: true, // Disable tooltips.
|
||||
vTooltipTemplate: generateTooltip,
|
||||
vTotalHeight: 900,
|
||||
|
||||
vEventsChange: {
|
||||
taskname: editValue, // if you need to use the this scope, do: editValue.bind(this)
|
||||
res: editValue,
|
||||
dur: editValue,
|
||||
comp: editValue,
|
||||
start: editValue,
|
||||
end: editValue,
|
||||
planstart: editValue,
|
||||
planend: editValue,
|
||||
cost: editValue,
|
||||
additional_category: editValue,
|
||||
},
|
||||
});
|
||||
|
||||
JSGantt.addJSONTask(g, tasks);
|
||||
g.setEditable(true);
|
||||
|
||||
connectedClients[blenderId]["gantt"] = g;
|
||||
|
||||
return ganttDiv;
|
||||
}
|
||||
|
||||
function createPrint(blenderId) {
|
||||
const printButton = $("<button>", {
|
||||
id: "print-btn-" + blenderId,
|
||||
html: "Print",
|
||||
class: "btn no-print",
|
||||
});
|
||||
|
||||
const printOptions = $("<select>", {
|
||||
id: "print-options-" + blenderId,
|
||||
class: "no-print",
|
||||
});
|
||||
|
||||
$.each(pageSizes, function (index, size) {
|
||||
printOptions.append(
|
||||
$("<option>", {
|
||||
value: size.value,
|
||||
text: size.text,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
printButton.on("click", function () {
|
||||
$(".gantt-chart").removeClass("no-print");
|
||||
$(".gantt-chart")
|
||||
.not("#gantt-" + blenderId)
|
||||
.addClass("no-print");
|
||||
|
||||
$(".gantt-info")
|
||||
.not("#gantt-info-" + blenderId)
|
||||
.addClass("no-print");
|
||||
|
||||
const values = printOptions.val().split(",");
|
||||
|
||||
const g = connectedClients[blenderId]["gantt"];
|
||||
g.setEditable(false);
|
||||
g.setTotalHeight("");
|
||||
g.Draw();
|
||||
|
||||
addEventListener("afterprint", () => g.setEditable(true));
|
||||
|
||||
let css = `@media print {
|
||||
@page {
|
||||
size: ${values[0]}mm ${values[1]}mm;
|
||||
}
|
||||
body, p, span, h1, h2, h3, h4, h5, h6, div, a, li, td, th, * {
|
||||
color: black !important;
|
||||
}
|
||||
}`;
|
||||
|
||||
g.printChart(values[0], values[1], css);
|
||||
g.setTotalHeight(900);
|
||||
g.Draw();
|
||||
});
|
||||
|
||||
return { printButton, printOptions };
|
||||
}
|
||||
|
||||
const ganttContainer = $("<div></div>")
|
||||
.addClass("gantt-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
@@ -170,171 +325,27 @@ function addGanttElement(blenderId, tasks, workSched, filename) {
|
||||
.attr("id", "title-" + blenderId)
|
||||
.text(filename)
|
||||
.addClass("no-print")
|
||||
.css("margin-bottom", "10px");
|
||||
.css("margin-bottom", "0.875rem");
|
||||
|
||||
const warning = $("<div></div>")
|
||||
.attr("id", "warning-" + blenderId)
|
||||
.html(
|
||||
"⚠ Warning: This Gantt Chart may contain outdated data due to recent changes in Blender."
|
||||
)
|
||||
.addClass("warning no-print");
|
||||
|
||||
const workSchedDiv = $("<div></div>").attr("id", "workSched" + blenderId);
|
||||
|
||||
const scheduleTable = $("<table></table>")
|
||||
.addClass("no-print table-description")
|
||||
.attr("id", "workSchedTable-" + blenderId)
|
||||
.hide();
|
||||
|
||||
$.each(workSched, (key, value) => {
|
||||
value = value ? value : "null";
|
||||
$("<tr></tr>")
|
||||
.append($("<td></td>").text(key))
|
||||
.append($("<td></td>").text(value))
|
||||
.appendTo(scheduleTable);
|
||||
});
|
||||
|
||||
const toggleButton = $("<button></button>")
|
||||
.text("Show Schedule Info")
|
||||
.addClass("btn no-print")
|
||||
.on("click", function () {
|
||||
scheduleTable.toggle();
|
||||
const buttonText = scheduleTable.is(":visible")
|
||||
? "Hide Schedule Info"
|
||||
: "Show Schedule Info";
|
||||
toggleButton.text(buttonText);
|
||||
});
|
||||
|
||||
var ganttInfoDiv = $("<div></div>")
|
||||
.addClass("gantt-info")
|
||||
.attr("id", "gantt-info-" + blenderId);
|
||||
|
||||
const scheduleName = $("<span></span>").text("Schedule: " + workSched.Name);
|
||||
|
||||
const createdOn = $("<span></span>")
|
||||
.text("Created: " + new Date(workSched.CreationDate).toLocaleDateString())
|
||||
.css("float", "right");
|
||||
|
||||
const ganttDiv = $("<div></div>")
|
||||
.addClass("gantt-chart")
|
||||
.attr("id", "gantt-" + blenderId);
|
||||
|
||||
ganttInfoDiv.append(scheduleName);
|
||||
ganttInfoDiv.append(createdOn);
|
||||
|
||||
workSchedDiv.append(toggleButton);
|
||||
workSchedDiv.append(scheduleTable);
|
||||
const workSchedDiv = createWorkSchedDiv(blenderId, workSched);
|
||||
const ganttInfoDiv = createGanttInfoDiv(blenderId, workSched);
|
||||
const ganttDiv = createGantt(blenderId, tasks);
|
||||
const { printButton, printOptions } = createPrint(blenderId);
|
||||
|
||||
ganttContainer.append(ganttTitle);
|
||||
ganttContainer.append(warning);
|
||||
ganttContainer.append(workSchedDiv);
|
||||
ganttContainer.append(ganttInfoDiv);
|
||||
ganttContainer.append(ganttDiv);
|
||||
|
||||
$("#container").append(ganttContainer);
|
||||
|
||||
let g = new JSGantt.GanttChart($("#gantt-" + blenderId)[0], "week");
|
||||
g.setOptions({
|
||||
vCaptionType: "Caption", // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: "day dd month yyyy", // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: "mon yyyy - Week ww", // Set format to dates in the "Major" header of the "Day" view
|
||||
vWeekMinorDateDisplayFormat: "dd mon", // Set format to display dates in the "Minor" header of the "Week" view
|
||||
vLang: "en",
|
||||
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
|
||||
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
|
||||
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
|
||||
vFormatArr: ["Day", "Week", "Month", "Quarter"], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
|
||||
vShowRes: true, // Disable the resource column.
|
||||
vShowComp: false, // Disable the completion column.
|
||||
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
|
||||
vAdditionalHeaders: {
|
||||
ifcduration: { title: "Duration" },
|
||||
resourceUsage: { title: "Resource Usage" },
|
||||
},
|
||||
vUseToolTip: true, // Disable tooltips.
|
||||
vTooltipTemplate: generateTooltip,
|
||||
vTotalHeight: 900,
|
||||
|
||||
vEventsChange: {
|
||||
taskname: editValue, // if you need to use the this scope, do: editValue.bind(this)
|
||||
res: editValue,
|
||||
dur: editValue,
|
||||
comp: editValue,
|
||||
start: editValue,
|
||||
end: editValue,
|
||||
planstart: editValue,
|
||||
planend: editValue,
|
||||
cost: editValue,
|
||||
additional_category: editValue,
|
||||
},
|
||||
});
|
||||
JSGantt.addJSONTask(g, tasks);
|
||||
g.setEditable(true);
|
||||
g.Draw();
|
||||
|
||||
connectedClients[blenderId]["gantt"] = g;
|
||||
|
||||
let printButton = $("<button>", {
|
||||
id: "print-btn-" + blenderId,
|
||||
html: "Print",
|
||||
class: "btn no-print",
|
||||
});
|
||||
|
||||
let printOptions = $("<select>", {
|
||||
id: "print-options-" + blenderId,
|
||||
class: "no-print",
|
||||
});
|
||||
|
||||
$.each(pageSizes, function (index, size) {
|
||||
printOptions.append(
|
||||
$("<option>", {
|
||||
value: size.value,
|
||||
text: size.text,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
printButton.on("click", function () {
|
||||
// make it only the corresponding gantt chart is printed
|
||||
$(".gantt-chart").removeClass("no-print");
|
||||
$(".gantt-chart")
|
||||
.not("#gantt-" + blenderId)
|
||||
.addClass("your-css-class");
|
||||
|
||||
var values = $("#print-options-" + blenderId)
|
||||
.val()
|
||||
.split(",");
|
||||
|
||||
g.setEditable(false);
|
||||
g.setTotalHeight("");
|
||||
g.Draw();
|
||||
|
||||
addEventListener("afterprint", (event) => {
|
||||
g.setEditable(true);
|
||||
});
|
||||
|
||||
let css =
|
||||
"@media print {\n" +
|
||||
" @page {\n" +
|
||||
" size: " +
|
||||
values[0] +
|
||||
"mm " +
|
||||
values[1] +
|
||||
"mm;\n" +
|
||||
" }\n" +
|
||||
" /* Make all text black */\n" +
|
||||
" body, p, span, h1, h2, h3, h4, h5, h6, div, a, li, td, th, * {\n" +
|
||||
" color: black !important;\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
g.printChart(values[0], values[1], css);
|
||||
g.setTotalHeight(900);
|
||||
g.Draw();
|
||||
});
|
||||
|
||||
ganttContainer.append(printOptions);
|
||||
ganttContainer.append(printButton);
|
||||
|
||||
$("#container").append(ganttContainer);
|
||||
|
||||
// have to call draw after appening the containers to the DOM
|
||||
// otherwise the dependencies will not be displayed properly
|
||||
const g = connectedClients[blenderId].gantt;
|
||||
g.Draw();
|
||||
}
|
||||
|
||||
// Function to update gantt and filename
|
||||
@@ -358,7 +369,6 @@ function updateGanttElement(blenderId, tasks, workSched, filename) {
|
||||
g.Draw();
|
||||
|
||||
$("#title-" + blenderId).text(filename);
|
||||
$("#warning-" + blenderId).css("display", "none");
|
||||
}
|
||||
|
||||
// Function to remove gantt element
|
||||
@@ -367,7 +377,7 @@ function removeGanttElement(blenderId) {
|
||||
}
|
||||
|
||||
function showWarning(blenderId, isDirty) {
|
||||
$("#warning-" + blenderId).css("display", "block");
|
||||
connectedClients[blenderId].warning = true;
|
||||
}
|
||||
|
||||
// Utility function to create a tooltip for the gantt chars
|
||||
@@ -390,12 +400,12 @@ function generateTooltip(task) {
|
||||
|
||||
// Event handlers for editing gantt table data
|
||||
function editValue(list, task, event, cell, column) {
|
||||
console.log("editValue function called with the following parameters:");
|
||||
console.log("list:", list);
|
||||
console.log("task:", task);
|
||||
console.log("event:", event);
|
||||
console.log("cell:", cell);
|
||||
console.log("column:", column);
|
||||
// console.log("editValue function called with the following parameters:");
|
||||
// console.log("list:", list);
|
||||
// console.log("task:", task);
|
||||
// console.log("event:", event);
|
||||
// console.log("cell:", cell);
|
||||
// console.log("column:", column);
|
||||
|
||||
const ganttId = task.getGantt()["vDiv"].id;
|
||||
const index = ganttId.indexOf("-") + 1;
|
||||
@@ -418,11 +428,13 @@ function editValue(list, task, event, cell, column) {
|
||||
value: event.target.value,
|
||||
},
|
||||
};
|
||||
console.log("web operator: " + msg);
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
$("html").removeClass("light dark blender").addClass(theme);
|
||||
$(":root").css("color-scheme", theme);
|
||||
if (theme === "light") {
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else if (theme === "dark") {
|
||||
@@ -462,12 +474,29 @@ function toggleClientList() {
|
||||
|
||||
const clientDiv = $("<div>")
|
||||
.addClass("client")
|
||||
.addClass(`client-${id}`)
|
||||
.text(client.ifc_file || "Blender " + counter);
|
||||
|
||||
if (client.warning) {
|
||||
warningIcon = $("<i>")
|
||||
.addClass("fas fa-triangle-exclamation warning")
|
||||
.css("margin-left", "0.5rem");
|
||||
clientDiv.append(warningIcon);
|
||||
}
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (client.warning) {
|
||||
warningIcon = $("<i>").addClass("fa-solid fa-triangle-exclamation");
|
||||
const clientWarning = $("<div>")
|
||||
.addClass("client-detail warning")
|
||||
.text(" Might have outdated data due to recent changes in Blender.");
|
||||
clientWarning.prepend(warningIcon);
|
||||
clientDetailsDiv.append(clientWarning);
|
||||
}
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
@@ -519,5 +548,6 @@ function toggleClientList() {
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
// keeps track of blenders connected in form of
|
||||
// {shown:bool, filename:string, headers:array}
|
||||
// {shown:bool, filename:string, headers:array, warning: false}
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
$(":root").css("color-scheme", systemTheme);
|
||||
var defaultTheme = "blender";
|
||||
var theme = localStorage.getItem("theme") || defaultTheme;
|
||||
setTheme(theme);
|
||||
@@ -33,10 +29,11 @@ function connectSocket() {
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
console.log("blender connected: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = { shown: false, ifc_file: "" };
|
||||
}
|
||||
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
@@ -44,7 +41,7 @@ function handleBlenderConnect(blenderId) {
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
console.log("blender disconnected: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeTableElement(blenderId);
|
||||
@@ -57,14 +54,14 @@ function handleBlenderDisconnect(blenderId) {
|
||||
|
||||
function handleConnectedClients(data) {
|
||||
$("#blender-count").text(data.length);
|
||||
console.log(data);
|
||||
// console.log(data);
|
||||
data.forEach(function (id) {
|
||||
connectedClients[id] = { shown: false, ifc_file: "" };
|
||||
});
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
console.log(themeData);
|
||||
// console.log(themeData);
|
||||
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
@@ -108,16 +105,19 @@ function handleCsvData(data) {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
headers: [],
|
||||
warning: false,
|
||||
};
|
||||
addTableElement(blenderId, csvData, filename);
|
||||
} else {
|
||||
updateTableElement(blenderId, csvData, filename);
|
||||
connectedClients[blenderId].warning = false;
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
headers: [],
|
||||
warning: false,
|
||||
};
|
||||
addTableElement(blenderId, csvData, filename);
|
||||
}
|
||||
@@ -135,9 +135,15 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
// store headers of the csv data
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const csvHeaders = csvData.substring(0, firstLine).split(",");
|
||||
connectedClients[blenderId].headers = csvHeaders;
|
||||
const hasRows = csvData.substring(firstLine + 1).trim().length > 0;
|
||||
|
||||
console.log(connectedClients[blenderId]);
|
||||
if (hasRows) {
|
||||
connectedClients[blenderId].headers = csvHeaders;
|
||||
} else {
|
||||
connectedClients[blenderId].headers = "";
|
||||
}
|
||||
|
||||
// console.log(connectedClients[blenderId]);
|
||||
const tableContainer = $("<div></div>")
|
||||
.addClass("table-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
@@ -147,19 +153,11 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
.text(filename)
|
||||
.css("margin-bottom", "10px");
|
||||
|
||||
const warning = $("<div></div>")
|
||||
.attr("id", "warning-" + blenderId)
|
||||
.html(
|
||||
"⚠ Warning: This table may contain outdated data due to recent changes in Blender."
|
||||
)
|
||||
.addClass("warning");
|
||||
|
||||
const tableDiv = $("<div></div>")
|
||||
.addClass("csv-table")
|
||||
.attr("id", "table-" + blenderId);
|
||||
|
||||
tableContainer.append(tableTitle);
|
||||
tableContainer.append(warning);
|
||||
tableContainer.append(tableDiv);
|
||||
$("#container").append(tableContainer);
|
||||
|
||||
@@ -192,8 +190,8 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
|
||||
// Toggle current column visibility
|
||||
column.visible = !column.visible;
|
||||
console.log(column.title, column.visible);
|
||||
// Change menu item checkbox
|
||||
// console.log(column.title, column.visible);
|
||||
|
||||
if (column.visible) {
|
||||
table.showColumn(column.title);
|
||||
checkbox.textContent = "\u2611"; // ☑
|
||||
@@ -209,6 +207,7 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
}
|
||||
|
||||
var table = new Tabulator("#table-" + blenderId, {
|
||||
placeholder: "No data to display",
|
||||
height: "400px",
|
||||
resizableColumnGuide: true,
|
||||
index: "GlobalId",
|
||||
@@ -258,6 +257,8 @@ function updateTableElement(blenderId, csvData, filename) {
|
||||
// else, replace the whole data and column definations
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const newHeaders = csvData.substring(0, firstLine).split(",");
|
||||
const hasRows = csvData.substring(firstLine + 1).trim().length > 0;
|
||||
|
||||
const sameHeaders = compareHeaders(
|
||||
connectedClients[blenderId].headers,
|
||||
newHeaders
|
||||
@@ -269,10 +270,14 @@ function updateTableElement(blenderId, csvData, filename) {
|
||||
table.replaceData(csvData, { importFormat: "csv" });
|
||||
} else {
|
||||
table.setData(csvData);
|
||||
connectedClients[blenderId].headers = newHeaders;
|
||||
|
||||
if (hasRows) {
|
||||
connectedClients[blenderId].headers = newHeaders;
|
||||
} else {
|
||||
connectedClients[blenderId].headers = "";
|
||||
}
|
||||
}
|
||||
$("#title-" + blenderId).text(filename);
|
||||
$("#warning-" + blenderId).css("display", "none");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,12 +287,16 @@ function removeTableElement(blenderId) {
|
||||
}
|
||||
|
||||
function showWarning(blenderId, isDirty) {
|
||||
$("#warning-" + blenderId).css("display", "block");
|
||||
connectedClients[blenderId].warning = true;
|
||||
}
|
||||
|
||||
// Utility function to compare two csv header
|
||||
function compareHeaders(headers1, headers2) {
|
||||
if (headers1.length !== headers2.length) {
|
||||
if (
|
||||
headers1.length !== headers2.length ||
|
||||
headers1.length == 0 ||
|
||||
headers2.length == 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < headers1.length; i++) {
|
||||
@@ -300,6 +309,7 @@ function compareHeaders(headers1, headers2) {
|
||||
|
||||
function setTheme(theme) {
|
||||
$("html").removeClass("light dark blender").addClass(theme);
|
||||
$(":root").css("color-scheme", theme);
|
||||
var stylesheet = $("#tabulator-stylesheet");
|
||||
if (theme === "light") {
|
||||
stylesheet.attr(
|
||||
@@ -349,12 +359,29 @@ function toggleClientList() {
|
||||
|
||||
const clientDiv = $("<div>")
|
||||
.addClass("client")
|
||||
.addClass(`client-${id}`)
|
||||
.text(client.ifc_file || "Blender " + counter);
|
||||
|
||||
if (client.warning) {
|
||||
warningIcon = $("<i>")
|
||||
.addClass("fas fa-triangle-exclamation warning")
|
||||
.css("margin-left", "0.5rem");
|
||||
clientDiv.append(warningIcon);
|
||||
}
|
||||
|
||||
clientDiv.append(dropdownIcon);
|
||||
|
||||
const clientDetailsDiv = $("<div>").addClass("client-details");
|
||||
|
||||
if (client.warning) {
|
||||
warningIcon = $("<i>").addClass("fa-solid fa-triangle-exclamation");
|
||||
const clientWarning = $("<div>")
|
||||
.addClass("client-detail warning")
|
||||
.text(" Might have outdated data due to recent changes in Blender.");
|
||||
clientWarning.prepend(warningIcon);
|
||||
clientDetailsDiv.append(clientWarning);
|
||||
}
|
||||
|
||||
if (id) {
|
||||
const clientId = $("<div>")
|
||||
.addClass("client-detail")
|
||||
@@ -397,5 +424,6 @@ function toggleClientList() {
|
||||
|
||||
clientList.append(clientDiv);
|
||||
});
|
||||
|
||||
clientList.addClass("show");
|
||||
}
|
||||
|
||||
@@ -54,12 +54,12 @@
|
||||
<a href="/"><i class="fa-solid fa-table"></i> Schedules</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/gantt"
|
||||
<a href="/sequencing"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="drawings" class="active"
|
||||
<a href="/documentation" class="active"
|
||||
><i class="fa-solid fa-images"></i> Documentation</a
|
||||
>
|
||||
</li>
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
<a href="/"><i class="fa-solid fa-table"></i> Schedules</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/gantt" class="active"
|
||||
<a href="/sequencing" class="active"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="drawings"
|
||||
<a href="/documentation"
|
||||
><i class="fa-solid fa-images"></i> Documentation</a
|
||||
>
|
||||
</li>
|
||||
@@ -60,12 +60,16 @@
|
||||
</button>
|
||||
</nav>
|
||||
<div id="connected-list-div">
|
||||
<button id="show-connected-button" onclick="toggleClientList()">
|
||||
<button
|
||||
id="show-connected-button"
|
||||
class="no-print"
|
||||
onclick="toggleClientList()"
|
||||
>
|
||||
Connected Blenders:
|
||||
<span id="blender-count">0</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="client-list"></div>
|
||||
<div id="client-list" class="no-print"></div>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
<footer>
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/gantt"
|
||||
<a href="/sequencing"
|
||||
><i class="fa-solid fa-chart-gantt"></i> Construction Sequencing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="drawings"
|
||||
<a href="/documentation"
|
||||
><i class="fa-solid fa-images"></i> Documentation</a
|
||||
>
|
||||
</li>
|
||||
|
||||
@@ -254,7 +254,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
schedule_creator.schedule(self.filepath, tool.Drawing.get_path_with_ext(self.filepath, "svg"))
|
||||
if props.format == "web":
|
||||
if not context.scene.WebProperties.is_running:
|
||||
if not context.scene.WebProperties.is_connected:
|
||||
bpy.ops.bim.connect_websocket_server()
|
||||
tool.Web.send_webui_data(data=ifc_csv.dataframe.to_csv(index=False), data_key="csv_data", event="csv_data")
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class CsvProperties(PropertyGroup):
|
||||
("web", "web", ""),
|
||||
],
|
||||
name="Output format",
|
||||
default="csv",
|
||||
default="web",
|
||||
)
|
||||
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
||||
should_show_settings: BoolProperty(default=False, name="Show Settings")
|
||||
|
||||
@@ -26,6 +26,7 @@ class ConnectToWebsocketServer(bpy.types.Operator):
|
||||
bl_idname = "bim.connect_websocket_server"
|
||||
bl_label = "Connect/Start websocket server"
|
||||
bl_description = "Start/Connect to a Websocket server"
|
||||
page: bpy.props.StringProperty(default="")
|
||||
|
||||
def execute(self, context):
|
||||
port = context.scene.WebProperties.webserver_port
|
||||
@@ -33,7 +34,7 @@ class ConnectToWebsocketServer(bpy.types.Operator):
|
||||
context.scene.WebProperties.webserver_port = core.generate_port_number(tool.Web)
|
||||
|
||||
port = context.scene.WebProperties.webserver_port
|
||||
core.connect_websocket_server(tool.Web, port)
|
||||
core.connect_websocket_server(tool.Web, port, self.page)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -60,9 +61,10 @@ class killWebsocketServer(bpy.types.Operator):
|
||||
class OpenWebBrowser(bpy.types.Operator):
|
||||
bl_idname = "bim.open_web_browser"
|
||||
bl_label = "Open Web Browser"
|
||||
bl_description = "Open the web UI base URL in your Web Browser"
|
||||
bl_description = "Open the web UI page URL in your Web Browser"
|
||||
page: bpy.props.StringProperty(default="")
|
||||
|
||||
def execute(self, context):
|
||||
port = context.scene.WebProperties.webserver_port
|
||||
core.open_web_browser(tool.Web, port)
|
||||
core.open_web_browser(tool.Web, port, self.page)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -2,14 +2,14 @@ def generate_port_number(web):
|
||||
return web.generate_port_number()
|
||||
|
||||
|
||||
def connect_websocket_server(web, port):
|
||||
def connect_websocket_server(web, port, page):
|
||||
# check if port already has a server listening to it
|
||||
if web.is_port_available(port):
|
||||
web.start_websocket_server(port)
|
||||
if web.has_started(port):
|
||||
web.connect_websocket_server(port)
|
||||
web.send_theme_data()
|
||||
web.open_web_browser(port)
|
||||
web.open_web_browser(port, page)
|
||||
return
|
||||
|
||||
web.connect_websocket_server(port)
|
||||
@@ -23,5 +23,5 @@ def kill_websocket_server(web):
|
||||
web.kill_websocket_server()
|
||||
|
||||
|
||||
def open_web_browser(web, port):
|
||||
web.open_web_browser(port)
|
||||
def open_web_browser(web, port, page):
|
||||
web.open_web_browser(port, page)
|
||||
|
||||
@@ -1541,20 +1541,10 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
def generate_gantt_browser_chart(
|
||||
cls, task_json: list[dict[str, Any]], work_schedule: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
|
||||
tool.Web.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
|
||||
return
|
||||
|
||||
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"), "w") as f:
|
||||
with open(os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.mustache"), "r") as t:
|
||||
task_b64 = base64.b64encode(bytes(json.dumps(task_json), "utf-8")).decode("utf-8")
|
||||
f.write(
|
||||
pystache.render(
|
||||
t.read(), {"json_data": task_b64, "data": json.dumps(work_schedule.get_info(recursive=True))}
|
||||
)
|
||||
)
|
||||
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
|
||||
if not bpy.context.scene.WebProperties.is_connected:
|
||||
bpy.ops.bim.connect_websocket_server(page="sequencing")
|
||||
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
|
||||
tool.Web.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
|
||||
|
||||
@classmethod
|
||||
def is_filter_by_active_schedule(cls) -> bool:
|
||||
|
||||
@@ -61,7 +61,7 @@ class Web(bonsai.core.tool.Web):
|
||||
It then retrieves the port number, and returns it.
|
||||
|
||||
Returns:
|
||||
- int: The port number that was generated.
|
||||
int: The port number that was generated.
|
||||
"""
|
||||
print("Generating port number")
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
@@ -78,10 +78,10 @@ class Web(bonsai.core.tool.Web):
|
||||
If the connection is refused, the port is available for use; otherwise, it is in use.
|
||||
|
||||
Args:
|
||||
- port (int): The port number to check.
|
||||
port (int): The port number to check.
|
||||
|
||||
Returns:
|
||||
- bool: True if the port is available, False if it is in use.
|
||||
bool: True if the port is available, False if it is in use.
|
||||
"""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# connect_ex returns errno.SUCCESS (0) if the connection succeeds
|
||||
@@ -94,11 +94,10 @@ class Web(bonsai.core.tool.Web):
|
||||
Starts a WebSocket server on the specified port.
|
||||
|
||||
This method sets up the environment, locates paths, and starts
|
||||
the WebSocket server process. It also handles the creation and updating of a PID file to keep track
|
||||
of running server instances.
|
||||
the WebSocket server process.
|
||||
|
||||
Args:
|
||||
- port (int): The port number on which to start the WebSocket server.
|
||||
port (int): The port number on which to start the WebSocket server.
|
||||
"""
|
||||
import addon_utils
|
||||
|
||||
@@ -143,7 +142,7 @@ class Web(bonsai.core.tool.Web):
|
||||
reconnection attempts, starts an asyncio thread, connects to the WebSocket server, and sets the connection status.
|
||||
|
||||
Args:
|
||||
- port (int): The port number to connect to the WebSocket server.
|
||||
port (int): The port number to connect to the WebSocket server.
|
||||
"""
|
||||
global ws_thread, sio
|
||||
|
||||
@@ -223,15 +222,16 @@ class Web(bonsai.core.tool.Web):
|
||||
"""
|
||||
Check if the WebSocket server has started on the specified port.
|
||||
|
||||
This function continuously checks for the existence of the WebSocket server's process ID (PID) in the running_pid JSON file.
|
||||
this method continuously checks for the existence of the WebSocket server's process ID (PID) in the running_pid JSON file.
|
||||
It waits for a maximum of 5 seconds before returning False.
|
||||
|
||||
Args:
|
||||
- port (int): The port number on which the WebSocket server is expected to be running.
|
||||
port (int): The port number on which the WebSocket server is expected to be running.
|
||||
|
||||
Returns:
|
||||
- bool: True if the WebSocket server has started on the specified port within the maximum time limit, False otherwise.
|
||||
bool: True if the WebSocket server has started on the specified port within the maximum time limit, False otherwise.
|
||||
"""
|
||||
pid = ws_process.pid
|
||||
max_time = 5
|
||||
start = time.time()
|
||||
while True:
|
||||
@@ -242,7 +242,7 @@ class Web(bonsai.core.tool.Web):
|
||||
try:
|
||||
with open(pid_file, "r") as f:
|
||||
data = json.load(f)
|
||||
if port in data.values():
|
||||
if data.get(str(pid)) == port:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
@@ -261,11 +261,11 @@ class Web(bonsai.core.tool.Web):
|
||||
Sends data to the Web UI via Websocket connection.
|
||||
|
||||
Args:
|
||||
- data (Optional[Any]): The data to send. If None, just sends data from WebData.
|
||||
- data_key (str): The key under which to store the data in the payload. Defaults to "data".
|
||||
- event (str): The WebSocket event to emit. Defaults to "data".
|
||||
- namespace (str): The namespace for the WebSocket event. Defaults to "/blender".
|
||||
- use_web_data (bool): Whether to use data from WebData. Defaults to True.
|
||||
data (Optional[Any]): The data to send. If None, just sends data from WebData.
|
||||
data_key (str): The key under which to store the data in the payload. Defaults to "data".
|
||||
event (str): The WebSocket event to emit. Defaults to "data".
|
||||
namespace (str): The namespace for the WebSocket event. Defaults to "/blender".
|
||||
use_web_data (bool): Whether to use data from WebData. Defaults to True.
|
||||
"""
|
||||
|
||||
global ws_thread
|
||||
@@ -284,6 +284,14 @@ class Web(bonsai.core.tool.Web):
|
||||
|
||||
@classmethod
|
||||
def check_operator_queue(cls) -> None | float:
|
||||
"""
|
||||
this method checks the operator queue and processes the operators based on the source page.
|
||||
If the WebProperties.is_connected is False, it clears the queue and returns None to unregister the timer.
|
||||
If the queue is not empty, it processes each operator by calling the corresponding handling function.
|
||||
|
||||
Returns:
|
||||
(Optional[float]): Returns None if the WebProperties.is_connected is False, otherwise returns 1.0 to continue the timer.
|
||||
"""
|
||||
if not bpy.context.scene.WebProperties.is_connected:
|
||||
with web_operator_queue.mutex:
|
||||
web_operator_queue.queue.clear()
|
||||
@@ -303,6 +311,12 @@ class Web(bonsai.core.tool.Web):
|
||||
|
||||
@classmethod
|
||||
def handle_csv_operator(cls, operator_data: dict) -> None:
|
||||
"""
|
||||
this method handles the Schedules page operators.
|
||||
|
||||
Args:
|
||||
operator_data (dict): A dictionary containing the operator data.
|
||||
"""
|
||||
if operator_data["type"] == "selection":
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
guid = operator_data["globalId"]
|
||||
@@ -313,6 +327,12 @@ class Web(bonsai.core.tool.Web):
|
||||
|
||||
@classmethod
|
||||
def handle_gantt_operator(cls, operator_data: dict) -> None:
|
||||
"""
|
||||
this method handles the Sequencing page operators.
|
||||
|
||||
Args:
|
||||
operator_data (dict): A dictionary containing the operator data.
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
if operator_data["type"] == "editTask":
|
||||
task_id = int(operator_data["taskId"])
|
||||
@@ -345,6 +365,12 @@ class Web(bonsai.core.tool.Web):
|
||||
|
||||
@classmethod
|
||||
def handle_drawings_operator(cls, operator_data: dict) -> None:
|
||||
"""
|
||||
this method handles the Documentation page operators.
|
||||
|
||||
Args:
|
||||
operator_data (dict): A dictionary containing the operator data.
|
||||
"""
|
||||
if operator_data["type"] == "getDrawings":
|
||||
drawings_data = []
|
||||
sheets_data = []
|
||||
@@ -372,11 +398,23 @@ class Web(bonsai.core.tool.Web):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def open_web_browser(cls, port: int) -> None:
|
||||
webbrowser.open(f"http://127.0.0.1:{port}/")
|
||||
def open_web_browser(cls, port: int, page: str = "") -> None:
|
||||
"""
|
||||
Opens a web browser and navigates to the specified URL.
|
||||
|
||||
Args:
|
||||
port (int): The port number to be used in the URL.
|
||||
page (str): The page name to be appended to the URL. Default is an empty string which points to the index page.
|
||||
"""
|
||||
webbrowser.open(f"http://127.0.0.1:{port}/{page}")
|
||||
|
||||
@classmethod
|
||||
def send_theme_data(cls) -> None:
|
||||
"""
|
||||
this method retrieves the theme colors from Blender preferences,
|
||||
calculates the mixed colors, and sends the theme data to the Web UI.
|
||||
"""
|
||||
|
||||
def get_color(theme, attribute) -> tuple[Any, ...]:
|
||||
return tuple(getattr(theme, attribute)[:])
|
||||
|
||||
@@ -409,7 +447,7 @@ class Web(bonsai.core.tool.Web):
|
||||
"top_bar_header_text": get_color(top_bar, "header_text"),
|
||||
"active_object": get_color(view_3d, "object_active"),
|
||||
"selected_object": get_color(view_3d, "object_selected"),
|
||||
"info_warning": get_color(info, "info_warning"),
|
||||
"info_warning": get_color(info, "info_warning_text"),
|
||||
"odd_row": get_color(outliner.space, "back"),
|
||||
"even_row": mix_colors(get_color(outliner.space, "back"), get_color(outliner, "row_alternate")),
|
||||
"active_highlight": get_color(outliner, "active"),
|
||||
@@ -422,19 +460,52 @@ class Web(bonsai.core.tool.Web):
|
||||
|
||||
@classmethod
|
||||
async def sio_connect(cls, url: str) -> None:
|
||||
"""
|
||||
Asynchronously establishes a WebSocket connection and sets up event listeners.
|
||||
|
||||
This method connects to the specified URL using WebSocket transport and registers
|
||||
an event listener for the `web_operator` event within the `/blender` namespace.
|
||||
|
||||
Args:
|
||||
url (str): The URL of the WebSocket server to connect to.
|
||||
"""
|
||||
await sio.connect(url, transports=["websocket"], namespaces="/blender")
|
||||
sio.on("web_operator", cls.sio_listen_web_operator, namespace="/blender")
|
||||
|
||||
@classmethod
|
||||
async def sio_disconnect(cls) -> None:
|
||||
"""
|
||||
Asynchronously disconnects the WebSocket connection.
|
||||
|
||||
This method terminates the active WebSocket connection established by the `sio_connect` method.
|
||||
"""
|
||||
await sio.disconnect()
|
||||
|
||||
@classmethod
|
||||
async def sio_send(cls, data: Any, event: str = "data", namespace: str = "/blender") -> None:
|
||||
"""
|
||||
Asynchronously sends data to the WebSocket server.
|
||||
|
||||
This method emits an event with the provided data to the WebSocket server within the specified namespace.
|
||||
|
||||
Args:
|
||||
data (Any): The data to send to the WebSocket server.
|
||||
event (Optional[str]): The WebSocket event to emit. Defaults to "data".
|
||||
namespace (Optional[str]): The namespace for the WebSocket event. Defaults to "/blender".
|
||||
"""
|
||||
await sio.emit(event, data, namespace=namespace)
|
||||
|
||||
@classmethod
|
||||
async def sio_listen_web_operator(cls, data: dict) -> None:
|
||||
"""
|
||||
Asynchronously handles incoming `web_operator` events from the WebSocket server.
|
||||
|
||||
This method receives data from the WebSocket server and attempts to place it into the
|
||||
`web_operator_queue`. If the queue is full, the data is discarded.
|
||||
|
||||
Args:
|
||||
data (dict): The data received from the `web_operator` event.
|
||||
"""
|
||||
try:
|
||||
web_operator_queue.put_nowait(data)
|
||||
except queue.Full:
|
||||
@@ -458,9 +529,9 @@ class AsyncioThread(threading.Thread):
|
||||
in a separate thread from the main thread.
|
||||
|
||||
Args:
|
||||
- *args: Variable length argument list. These arguments are passed to the superclass constructor.
|
||||
- loop: An existing asyncio event loop. If None, a new event loop is created.
|
||||
- **kwargs: Arbitrary keyword arguments. These arguments are passed to the superclass constructor.
|
||||
*args: Variable length argument list. These arguments are passed to the superclass constructor.
|
||||
loop: An existing asyncio event loop. If None, a new event loop is created.
|
||||
**kwargs: Arbitrary keyword arguments. These arguments are passed to the superclass constructor.
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.loop = loop or asyncio.new_event_loop()
|
||||
@@ -478,7 +549,7 @@ class AsyncioThread(threading.Thread):
|
||||
Run a coroutine in the asyncio event loop from a separate thread.
|
||||
|
||||
Args:
|
||||
- coro: The coroutine to be run.
|
||||
coro: The coroutine to be run.
|
||||
|
||||
Returns:
|
||||
The result of the coroutine.
|
||||
|
||||
Reference in New Issue
Block a user