Compare commits

...

23 Commits

Author SHA1 Message Date
Ziad-I 6402b0ed74 add logo and change path of jsgantt lib 2024-07-05 10:15:21 +03:00
Ziad-I 87dc1d5d01 add old gantt ui if web ui is not running 2024-07-05 10:08:54 +03:00
Ziad-I cdb5b6fc41 fix setting editable after printing 2024-07-05 09:25:17 +03:00
Ziad-I e32c9ae4fc add display and update for work schedule data 2024-07-03 23:47:38 +03:00
Ziad-I 20fcc65cac fix editing task name and time from webui 2024-07-03 22:18:52 +03:00
Ziad-I e83b1ec67d add editing of gantt chart from webui 2024-07-02 22:22:55 +03:00
Ziad-I 56469b99c8 fix sending work_schedule 2024-07-02 20:11:26 +03:00
Ziad-I f60a6e0ca6 fix updating chart error 2024-07-02 18:15:44 +03:00
Ziad-I c9b440d348 add sending updated task column data 2024-07-02 18:15:21 +03:00
Ziad-I ba4939b969 fix error with getting blenderId from chart id 2024-06-29 23:09:16 +03:00
Ziad-I 33e93fbb68 remove unneeded loop for task creation in gantt chart 2024-06-29 23:03:23 +03:00
Ziad-I bf0489da46 add updating of gantt chart 2024-06-29 22:24:46 +03:00
Ziad-I a708362c68 add sending gantt data on pressing generate gantt chart in blender 2024-06-29 22:06:47 +03:00
Ziad-I 1b2e5cba7f add key name parameter in send_webui_data 2024-06-29 22:06:04 +03:00
Ziad-I 361515171a add change event handling in gantt data table 2024-06-29 21:23:52 +03:00
Ziad-I ddd07b4894 refactor web ui operator handling 2024-06-29 01:51:43 +03:00
Ziad-I b7662a122b add print options selection button 2024-06-27 22:06:09 +03:00
Ziad-I bbd5e05ee0 fix some css issues 2024-06-27 20:27:58 +03:00
Ziad-I 4a521fcf67 add print button for gantt chart 2024-06-27 20:24:01 +03:00
Ziad-I 2d50a7cac2 add gantt html/css/js 2024-06-26 23:02:16 +03:00
Ziad-I 9c339f30f9 rename styles.css to index.css
to reflect which page it is used for
2024-06-26 22:47:47 +03:00
Ziad-I 78538cc3c3 small index html/css changes 2024-06-26 22:45:23 +03:00
Ziad-I 1b3c491032 add handler for gantt url path 2024-06-26 22:43:19 +03:00
10 changed files with 756 additions and 25 deletions
@@ -36,7 +36,12 @@ class WebNamespace(socketio.AsyncNamespace):
print(f"Web client disconnected: {sid}")
async def on_web_operator(self, sid, data):
await sio.emit("web_operator", data["operator"], namespace="/blender", room=data["blenderId"])
await sio.emit(
"web_operator",
data,
namespace="/blender",
room=data["blenderId"],
)
# Blender namespace
@@ -68,6 +73,11 @@ class BlenderNamespace(socketio.AsyncNamespace):
blender_messages[sid]["csv_data"] = data
await sio.emit("csv_data", {"blenderId": sid, "data": data}, namespace="/web")
async def on_gantt_data(self, sid, data):
print(f"Gant Chart data from Blender client {sid}")
blender_messages[sid]["gantt_data"] = data
await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web")
# Attach namespaces
sio.register_namespace(WebNamespace("/web"))
@@ -82,7 +92,16 @@ async def index(request):
return web.Response(text=html_content, content_type="text/html")
async def gantt(request):
with open("templates/gantt.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port})
return web.Response(text=html_content, content_type="text/html")
app.router.add_get("/", index)
app.router.add_get("/gantt", gantt)
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
app.router.add_static("/static/", path="./static", name="static")
@@ -0,0 +1,280 @@
:root {
color-scheme: dark;
}
body {
background-color: #252525;
color: #e0e0e0;
margin: 0;
font-family: Arial, sans-serif;
}
#container {
margin-top: 20px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 20px;
}
h3 {
margin-top: 0;
}
nav {
background-color: #121212;
padding: 1em 0;
display: flex;
align-items: center;
position: relative;
}
nav .logo {
margin-left: 40px;
height: 40px;
/* Adjust height as needed */
}
nav ul {
list-style-type: none;
margin: 0;
margin-left: 1px;
display: flex;
flex-grow: 1;
justify-content: center;
}
nav ul li {
margin-right: 50px;
}
nav ul li a {
text-decoration: none;
color: #fff;
font-size: 1.2em;
}
nav ul li a:hover,
nav ul li a.active {
color: #3fb449;
}
nav .bottom-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #25682a;
}
.table-description {
margin-bottom: 1rem;
width: 50%;
background-color: transparent;
border-collapse: collapse;
border-spacing: 0;
border-color: #464444;
border: 1px solid #464444;
border-radius: 0.25rem;
box-shadow: 0 0 0.7rem rgba(95, 95, 95, 0.4);
text-align: center;
}
.table-description tr {
display: table-row;
border-bottom: 1px solid #464444;
}
.gantt-info {
margin: 0.5rem;
font-size: 1rem;
font-weight: bold;
}
.btn {
margin: 0.5rem;
font-size: 1rem;
cursor: pointer;
}
@media print {
.no-print {
display: none;
}
}
/* ------------ Overwriting JSGantt CSS rules ------------ */
div.gantt {
background-color: #252525;
color: #c7c7c7;
}
.gantt table {
border-color: #464444;
;
}
.gantt td {
border-color: #464444;
;
}
/* Headings and cell defaults */
.gmajorheading,
.gminorheading,
.gminorheadingwkend,
.gtaskcell,
.gtaskcellcurrent,
.gtaskcellwkend,
.gname,
.ggroupitem,
.gtaskheading {
background-color: #252525;
border-color: #464444;
;
}
.gtaskheading,
.gname,
.gtaskname,
.gres,
.gdur,
.gcomp,
.gstartdate,
.gplanstartdate,
.gplanenddate,
.gcost,
.gchartlbl,
.gcontainercol,
.genddate {
color: #c7c7c7;
border-color: #464444;
;
}
.gtaskname>div {
color: #c7c7c7;
}
.gtaskbarcontainer.gplan,
.gchartlbl.gcontainercol {
background: #252525;
border-color: #464444;
}
.gtaskname div,
.gtaskheading div,
.gtaskname span {
color: #c7c7c7;
}
.gtasklist,
.gadditional {
border: #464444 1px solid
}
.gchartgrid {
background-color: #252525;
}
.glistgrid,
.glistlbl {
background-color: #252525;
border-color: #464444;
;
}
/* .gTaskInfo {
background-color: #2e2e2e;
color: #c7c7c7;
border-color: #c7c7c7;;
} */
/* Dark mode scrollbar */
.frame::-webkit-scrollbar-thumb {
background-color: #252525;
}
.frame::-webkit-scrollbar-track {
background-color: #252525;
}
/* Highlight row */
.gitemhighlight td {
background-color: #009136;
color: #ffffff;
}
/* Differentiate Group, Milestone and Ordinary task items (applied to row) */
.ggroupitem {
background-color: #252525;
font-weight: bold;
border-color: #464444;
}
.gmileitem,
.glineitem {
background-color: #252525;
}
/* Task bar caption text styles */
.gmilecaption,
.ggroupcaption,
.gcaption {
color: #ffffff;
}
/* Task complete %age bar */
.gtaskcomplete {
background-color: #777777;
}
/* Milestones */
.gmdtop {
border-bottom: 5px solid #ffffff;
}
.gmdbottom {
border-top: 5px solid #ffffff;
}
.gfoldercollapse {
color: #c7c7c7;
cursor: pointer;
font-weight: bold;
font-size: 12px;
font-family: Courier, "Courier New", monospace;
}
/* Highlight for collapsible row */
.gname.ggroupitem {
background-color: rgb(75, 75, 75);
}
/* Form label and selected highlighting */
.gformlabel {
background-color: #2e2e2e;
color: #c7c7c7;
border: #4a4a4a 1px solid;
}
span.gformlabel:hover {
background-color: #3a3a3a;
border-color: #565656;
}
span.gselected {
background-color: #565656;
border-color: #777777;
color: #ffffff;
}
.gantt-inputtable {
background-color: #3b3b3b;
box-sizing: border-box;
border: 1px solid #000;
color: #c7c7c7;
}
@@ -1,3 +1,7 @@
:root {
color-scheme: dark;
}
body {
background-color: #252525;
color: #e0e0e0;
@@ -44,7 +48,7 @@ nav ul {
margin-left: 1px;
display: flex;
flex-grow: 1;
justify-content: left;
justify-content: center;
}
nav ul li {
@@ -0,0 +1,339 @@
// keeps track of blenders connected in form of
// shown:bool, workSchedule: {}, ganttTasks: {},
const connectedClients = {};
let socket;
// print options
const pageSizes = [
{ value: "210,297", text: "A4 Portrait" },
{ value: "297,210", text: "A4 Landscape" },
{ value: "297,420", text: "A3 Portrait" },
{ value: "420,297", text: "A3 Landscape" },
{ value: "420,594", text: "A2 Portrait" },
{ value: "594,420", text: "A2 Landscape" },
{ value: "594,841", text: "A1 Portrait" },
{ value: "841,594", text: "A1 Landscape" },
{ value: "841,1189", text: "A0 Portrait" },
{ value: "1189,841", text: "A0 Landscape" },
];
// Document ready function
$(document).ready(function () {
connectSocket();
});
// Function to connect to Socket.IO server
function connectSocket() {
const url = "ws://localhost:" + SOCKET_PORT + "/web";
socket = io(url);
console.log("socket: ", socket);
// Register socket event handlers
socket.on("blender_connect", handleBlenderConnect);
socket.on("blender_disconnect", handleBlenderDisconnect);
socket.on("gantt_data", handleGanttData);
}
// Function to handle 'blender_connect' event
function handleBlenderConnect(blenderId) {
console.log("blender_connect: ", blenderId);
if (!connectedClients.hasOwnProperty(blenderId)) {
connectedClients[blenderId] = {
shown: false,
workSchedule: {},
ganttTasks: {},
};
}
}
// Function to handle 'blender_disconnect' event
function handleBlenderDisconnect(blenderId) {
console.log("blender_disconnect: ", blenderId);
if (connectedClients.hasOwnProperty(blenderId)) {
delete connectedClients[blenderId];
removeGanttElement(blenderId);
}
}
// Function to handle 'gantt_data' event
function handleGanttData(data) {
const blenderId = data["blenderId"];
console.log(data);
const filename = data["data"]["IFC_File"];
const ganttTasks = data["data"]["gantt_data"]["tasks"];
const ganttWorkSched = data["data"]["gantt_data"]["work_schedule"];
// const ganttWorkSched = {};
if (connectedClients.hasOwnProperty(blenderId)) {
if (!connectedClients[blenderId].shown) {
connectedClients[blenderId] = {
shown: true,
ganttTasks: ganttTasks,
workSchedule: ganttWorkSched,
};
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
} else {
updateGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
connectedClients[blenderId].workSchedule = ganttWorkSched;
connectedClients[blenderId].ganttTasks = ganttTasks;
}
} else {
connectedClients[blenderId] = {
shown: true,
ganttTasks: ganttTasks,
workSchedule: ganttWorkSched,
};
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
}
}
// Function to add a new gantt with data and filename
function addGanttElement(blenderId, tasks, workSched, filename) {
const ganttContainer = $("<div></div>")
.addClass("gantt-container")
.attr("id", "container-" + blenderId);
const ganttTitle = $("<h3></h3>")
.attr("id", "title-" + blenderId)
.text(filename)
.addClass("no-print")
.css("margin-bottom", "10px");
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);
ganttContainer.append(ganttTitle);
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);
}
// Function to update gantt and filename
function updateGanttElement(blenderId, tasks, workSched, filename) {
// update work schedule table
const table = $("#workSchedTable-" + blenderId);
table.empty();
$.each(workSched, (key, value) => {
value = value ? value : "null";
$("<tr></tr>")
.append($("<td></td>").text(key))
.append($("<td></td>").text(value))
.appendTo(table);
});
// update gantt chart with new data
let g = connectedClients[blenderId]["gantt"];
g.ClearTasks();
g.Draw();
JSGantt.addJSONTask(g, tasks);
g.Draw();
$("#title-" + blenderId).text(filename);
}
// Function to remove gantt element
function removeGanttElement(blenderId) {
$("#container-" + blenderId).remove();
}
// Utility function to create a tooltip for the gantt chars
function generateTooltip(task) {
var dataObject = task.getDataObject();
var numberResources = dataObject.resourceUsage
? dataObject.resourceUsage
: "NULL";
return `
<dl>
<dt>Name:</dt><dd>{{pName}}</dd>
<dt>Start:</dt><dd>{{pStart}}</dd>
<dt>End:</dt><dd>{{pEnd}}</dd>
<dt>Duration:</dt><dd>${dataObject.ifcduration}</dd>
<dt>Number of Resources:</dt><dd>${numberResources}</dd>
<dt>Resources:</dt><dd>{{pRes}}</dd>
</dl>
`;
}
// 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);
const ganttId = task.getGantt()["vDiv"].id;
const index = ganttId.indexOf("-") + 1;
const blenderId = ganttId.substring(index);
const workSchedId = connectedClients[blenderId].workSchedule.id;
// update data object reprsenting the task
const dataObj = task.getDataObject();
dataObj[column] = event.target.value;
task.setDataObject(dataObj);
const msg = {
sourcePage: "gantt",
blenderId: blenderId,
operator: {
type: "editTask",
workScheduleId: workSchedId,
taskId: task.getOriginalID(),
column: column,
value: event.target.value,
},
};
socket.emit("web_operator", msg);
}
@@ -40,7 +40,7 @@ function handleBlenderDisconnect(blenderId) {
// Function to handle 'csv_data' event
function handleCsvData(data) {
const blenderId = data["blenderId"];
const csvData = data["data"]["data"];
const csvData = data["data"]["csv_data"];
const filename = data["data"]["IFC_File"];
console.log(data);
@@ -167,10 +167,11 @@ function addTableElement(blenderId, csvData, filename) {
var tableId = row.getTable().element.id.substr(6);
const msg = {
sourcePage: "csv",
blenderId: tableId,
operator: {
type: "selection",
GlobalId: index,
globalId: index,
},
};
socket.emit("web_operator", msg);
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Client</title>
<link rel="stylesheet" type="text/css" href="/jsgantt/jsgantt.css" />
<link rel="stylesheet" href="/static/css/gantt.css" />
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
></script>
<script type="text/javascript" src="./jsgantt/jsgantt.js"></script>
<script>
var SOCKET_PORT = {{port}};
</script>
<script defer src="./static/js/gantt.js"></script>
</head>
<body>
<nav class="no-print">
<img src="https://blenderbim.org/assets/images/blender/blender-logo.png" alt="Logo" class="logo" />
<ul>
<li><a href="/">IFC Data</a></li>
<li><a href="/gantt" class="active">Gantt Chart</a></li>
</ul>
<div class="bottom-bar"></div>
</nav>
<div id="container"></div>
</body>
</html>
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Client</title>
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="stylesheet" href="/static/css/index.css" />
<link
rel="stylesheet"
href="https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.css"
@@ -28,8 +28,10 @@
</head>
<body>
<nav>
<img src="https://blenderbim.org/assets/images/blender/blender-logo.png" alt="Logo" class="logo" />
<ul>
<li><a href="/">IFC Data</a></li>
<li><a href="/" class="active">IFC Data</a></li>
<li><a href="/gantt">Gantt Chart</a></li>
</ul>
<div class="bottom-bar"></div>
</nav>
@@ -254,7 +254,9 @@ 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":
tool.Web.send_webui_data(extra_data=ifc_csv.dataframe.to_csv(index=False), event="csv_data")
tool.Web.send_webui_data(
extra_data=ifc_csv.dataframe.to_csv(index=False), extra_data_key="csv_data", event="csv_data"
)
self.report({"INFO"}, f"Data is exported to {props.format.upper()}.")
return {"FINISHED"}
+6 -1
View File
@@ -1544,11 +1544,16 @@ class Sequence(blenderbim.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(extra_data=gantt_data, extra_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())})
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"))
+62 -17
View File
@@ -20,6 +20,7 @@ import bpy
from blenderbim.bim.module.web.data import WebData
import blenderbim.core.tool
import blenderbim.tool as tool
import ifcopenshell.api.sequence
import socket
import os
import subprocess
@@ -35,6 +36,13 @@ ws_process = None
ws_thread = None
web_operator_queue = queue.Queue()
IFC_TASK_ATTRIBUTE_MAP = {
"pStart": "ScheduleStart",
"pEnd": "ScheduleFinish",
"pName": "Name",
"pRes": "",
}
class Web(blenderbim.core.tool.Web):
@classmethod
@@ -118,7 +126,9 @@ class Web(blenderbim.core.tool.Web):
print("Websocket server terminated successfully")
@classmethod
def send_webui_data(cls, extra_data=None, event="data", namespace="/blender", use_web_data=True):
def send_webui_data(
cls, extra_data=None, extra_data_key="data", event="data", namespace="/blender", use_web_data=True
):
global ws_thread
data = {}
@@ -128,7 +138,7 @@ class Web(blenderbim.core.tool.Web):
data = WebData.data
if extra_data is not None:
data["data"] = extra_data
data[extra_data_key] = extra_data
if ws_thread is not None and bpy.context.scene.WebProperties.is_connected:
ws_thread.run_coro(cls.sio_send(data, event, namespace))
@@ -141,20 +151,57 @@ class Web(blenderbim.core.tool.Web):
return None # unregister timer if not connected
while not web_operator_queue.empty():
operator_data = web_operator_queue.get_nowait()
if not operator_data:
operator = web_operator_queue.get_nowait()
if not operator:
continue
if operator_data["type"] == "selection":
bpy.ops.object.select_all(action="DESELECT")
guid = operator_data["GlobalId"]
ele = tool.Ifc.get().by_guid(guid)
obj = tool.Ifc.get_object(ele)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
if operator["sourcePage"] == "csv":
cls.handle_csv_operator(operator["operator"])
elif operator["sourcePage"] == "gantt":
cls.handle_gantt_operator(operator["operator"])
return 1.0
@classmethod
def handle_csv_operator(cls, operator_data):
if operator_data["type"] == "selection":
bpy.ops.object.select_all(action="DESELECT")
guid = operator_data["globalId"]
ele = tool.Ifc.get().by_guid(guid)
obj = tool.Ifc.get_object(ele)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
@classmethod
def handle_gantt_operator(cls, operator_data):
ifc_file = tool.Ifc.get()
if operator_data["type"] == "editTask":
task_id = int(operator_data["taskId"])
task = ifc_file.by_id(task_id)
task_time = task.TaskTime
column = operator_data["column"]
new_value = operator_data["value"]
try:
ifcopenshell.api.sequence.edit_task(
ifc_file, task, attributes={IFC_TASK_ATTRIBUTE_MAP[column]: str(new_value)}
)
except AttributeError:
if task_time is None:
ifcopenshell.api.sequence.add_task_time(ifc_file, task)
task_time = task.TaskTime
ifcopenshell.api.sequence.edit_task_time(
ifc_file, task_time=task_time, attributes={IFC_TASK_ATTRIBUTE_MAP[column]: str(new_value)}
)
bpy.ops.bim.load_task_properties()
# after updating, send new gantt data to handle the case where
# changing a task cascades and changes other tasks. as this wouldn't
# be reflected in the web ui
work_schedule = ifc_file.by_id(operator_data["workScheduleId"])
task_json = tool.Sequence.create_tasks_json(work_schedule)
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
cls.send_webui_data(extra_data=gantt_data, extra_data_key="gantt_data", event="gantt_data")
@classmethod
def open_web_browser(cls, port):
webbrowser.open(f"http://127.0.0.1:{port}/")
@@ -162,9 +209,7 @@ class Web(blenderbim.core.tool.Web):
@classmethod
async def sio_connect(cls, url):
await sio.connect(url, transports=["websocket"], namespaces="/blender")
# TODO: register event handlers to handle incoming messages
sio.on("web_operator", cls.sio_handle_web_operator, namespace="/blender")
sio.on("web_operator", cls.sio_listen_web_operator, namespace="/blender")
@classmethod
async def sio_disconnect(cls):
@@ -175,7 +220,7 @@ class Web(blenderbim.core.tool.Web):
await sio.emit(event, data, namespace=namespace)
@classmethod
async def sio_handle_web_operator(cls, data):
async def sio_listen_web_operator(cls, data):
try:
web_operator_queue.put_nowait(data)
except queue.Full: