diff --git a/src/blenderbim/blenderbim/bim/data/webui/sioserver.py b/src/blenderbim/blenderbim/bim/data/webui/sioserver.py index 75106c6e8c..6d81c03665 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/sioserver.py +++ b/src/blenderbim/blenderbim/bim/data/webui/sioserver.py @@ -2,7 +2,9 @@ import sys import os import webbrowser -blenderbim_lib_path = os.environ.get("blenderbim_lib_path") +blenderbim_lib_path = os.environ.get("BLENDERBIM_LIB_PATH") +blenderbim_version = os.environ.get("BLENDERBIM_VERSION") + if blenderbim_lib_path: sys.path.insert(0, blenderbim_lib_path) @@ -52,9 +54,16 @@ class WebNamespace(socketio.AsyncNamespace): "web_operator", data, namespace="/blender", - room=data["blenderId"], + room=data.get("blenderId", None), ) + async def on_get_svg(self, sid, data): + print("hello world!") + file_path = data["path"] + with open(file_path, "r") as file: + svg_data = file.read() + await sio.emit("svg_data", svg_data, room=sid, namespace="/web") + async def send_cached_messages(self, sid): # Send cached messages to the connected web client for blenderId, messages in blender_messages.items(): @@ -98,6 +107,12 @@ class BlenderNamespace(socketio.AsyncNamespace): blender_messages[sid]["gantt_data"] = data await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web") + async def on_drawings_data(self, sid, data): + print(f"Drawings directory from Blender client {sid}") + print(data) + blender_messages[sid]["drwings_data"] = data + await sio.emit("drawings_data", {"blenderId": sid, "data": data}, namespace="/web") + # Attach namespaces sio.register_namespace(WebNamespace("/web")) @@ -108,13 +123,20 @@ sio.register_namespace(BlenderNamespace("/blender")) async def index(request): with open("templates/index.html", "r") as f: template = f.read() - html_content = pystache.render(template, {"port": sio_port}) + html_content = pystache.render(template, {"port": sio_port, "version": blenderbim_version}) 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, "version": blenderbim_version}) + return web.Response(text=html_content, content_type="text/html") + + +async def drawings(request): + with open("templates/drawings.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") @@ -135,6 +157,7 @@ async def on_startup(app): app.router.add_get("/", index) +app.router.add_get("/drawings", drawings) app.router.add_get("/gantt", gantt) app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt") app.router.add_static("/static/", path="./static", name="static") diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/css/drawings.css b/src/blenderbim/blenderbim/bim/data/webui/static/css/drawings.css new file mode 100644 index 0000000000..9dd2437ecf --- /dev/null +++ b/src/blenderbim/blenderbim/bim/data/webui/static/css/drawings.css @@ -0,0 +1,249 @@ +:root { + --font-family: Arial, sans-serif; + --base-font-size: 16px; + --margin-tiny: 0.125rem; + --margin-small: 0.625rem; + --margin-medium: 1.25rem; + --margin-large: 2.5rem; + --padding-tiny: 0.125rem; + --padding-small: 0.625rem; + --padding-medium: 1rem; + --font-size-large: 1.2rem; + --logo-height: 2.5rem; + --nav-height: 2.5rem; +} + +:root.dark { + color-scheme: dark; + --bg-color: #252525; + --text-color: #e0e0e0; + --nav-bg-color: #121212; + --nav-border-color: #25682a; + --nav-link-color: #fff; + --nav-link-hover-color: #3fb449; + --warning-color: #FFDB8F; + --border-color: #464444; +} + +:root.light { + color-scheme: light; + --bg-color: #ffffff; + --text-color: #000000; + --nav-bg-color: #f8f8f8; + --nav-border-color: #cccccc; + --nav-link-color: #000000; + --nav-link-hover-color: #38a63d; + --warning-color: #FF4500; + --border-color: #222; +} + +html { + font-size: var(--base-font-size); + color: var(--text-color); +} + +body { + background-color: var(--bg-color); + color: var(--text-color); + margin: 0; + font-family: var(--font-family); +} + +#container { + margin-top: var(--margin-medium); + margin-left: var(--margin-small); + margin-right: var(--margin-small); + margin-bottom: var(--margin-medium); + height: calc(100vh - var(--nav-height)); + box-sizing: border-box; + display: flex; +} + +h3 { + margin-top: 0; +} + +nav { + background-color: var(--nav-bg-color); + height: var(--nav-height); + padding: var(--padding-medium) 0; + display: flex; + align-items: center; + position: relative; + border-bottom: 2px solid var(--nav-border-color); +} + +nav .logo { + margin-left: var(--margin-large); + height: var(--logo-height); +} + +nav ul { + list-style-type: none; + margin: 0; + margin-left: 0.0625rem; + display: flex; + flex-grow: 1; + justify-content: center; +} + +nav ul li { + margin-right: 3.125rem; +} + +nav ul li a { + text-decoration: none; + color: var(--nav-link-color); + font-size: var(--font-size-large); +} + +nav ul li a:hover, +nav ul li a.active { + color: var(--nav-link-hover-color); +} + +.warning { + color: var(--warning-color); + margin-bottom: var(--margin-small); + padding: var(--padding-tiny); + display: none; +} + +#toggle-theme { + border: none; + background: none; + cursor: pointer; + font-size: var(--font-size-large); + margin-right: var(--margin-medium); +} + +#toggle-theme:focus { + outline: none; +} + +#client-list { + position: absolute; + top: calc(0 + var(--nav-height)); + overflow-y: auto; + overflow: hidden; + transition: opacity 0.3s ease-out, visibility 0.3s ease-out; + border: 1px solid var(--table-border-color); + background-color: var(--nav-bg-color); + z-index: 1; + opacity: 0; + visibility: hidden; + width: auto; +} + +#client-list.show { + opacity: 1; + visibility: visible; +} + +#connected-list-div { + display: inline-block; +} + +.client { + padding: var(--margin-small); + border-bottom: 1px solid var(--table-border-color); + cursor: pointer; +} + +.client-details { + max-height: 0; + overflow: hidden; + padding-left: var(--padding-small); + transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out; + background-color: var(--nav-bg-color); + margin-top: var(--margin-tiny); + opacity: 0; +} + +.client-details.show { + max-height: none; + opacity: 1; +} + +.client-detail { + border-bottom: 1px solid var(--table-border-color); + padding: var(--padding-small); +} + +#show-connected-button { + width: auto; + background-color: var(--bg-color); + border: none; + font-size: var(--base-font-size); +} + +#show-connected-button:hover, +.scroll-button:hover { + cursor: pointer; +} + +.scroll-button { + font-size: var(--base-font-size); + margin-left: var(--margin-tiny); + margin-top: var(--margin-small); +} + + +.left { + flex: 1; + background-color: var(--bg-color); + display: flex; + flex-direction: column; + padding: 20px; +} + +.right { + flex: 4; + background-color: var(--bg-color); + border: 1px solid var(--border-color); +} + +#svg-container { + height: 100%; +} + +.panel { + background-color: var(--nav-bg-color); + border: 1px solid var(--border-color); + padding: 10px; + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.card-header, +.card-body { + background-color: var(--bg-color); + border: 1px solid var(--border-color); + overflow: auto; +} + +.card { + background-color: var(--nav-bg-color); + overflow: auto; +} + +li.svg-name { + list-style-type: none; +} + +#dropdown-menu:focus { + outline: none; +} + +/* remove bootstrap css confilcts */ +*, +::after, +::before { + box-sizing: unset; +} + +.btn, +.btn:hover { + color: var(--nav-link-hover-color); +} \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css b/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css index 1256b09080..f19ab94c28 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css +++ b/src/blenderbim/blenderbim/bim/data/webui/static/css/gantt.css @@ -1,13 +1,17 @@ :root { --font-family: Arial, sans-serif; + --base-font-size: 16px; + --margin-tiny: 0.125rem; --margin-small: 0.625rem; --margin-medium: 1.25rem; --margin-large: 2.5rem; - --padding-small: 0.125rem; + --padding-tiny: 0.125rem; + --padding-small: 0.625rem; --padding-medium: 1rem; --font-size-small: 1rem; --font-size-large: 1.2rem; --logo-height: 2.5rem; + --nav-height: 2.5rem; --folder-collapse-font-size: 0.75rem; --folder-collapse-font-family: Courier, "Courier New", monospace; --box-shadow: 0 0 0.7rem #5f5f5f66; @@ -31,6 +35,7 @@ --group-item-bg-color: #4b4b4b; --input-bg-color: #3b3b3b; --input-border-color: #000; + --details-border-color: #464444; } :root.light { @@ -42,6 +47,7 @@ --nav-link-color: #000000; --nav-link-hover-color: #38a63d; --warning-color: #FF4500; + --details-border-color: #222; } html { @@ -53,9 +59,13 @@ body { color: var(--primary-text-color); margin: 0; font-family: var(--font-family); + display: flex; + flex-direction: column; + min-height: 100vh; } #container { + flex: 1; margin-top: var(--margin-medium); margin-left: var(--margin-small); margin-right: var(--margin-small); @@ -107,7 +117,7 @@ nav ul li a.active { .warning { color: var(--warning-color); margin-bottom: var(--margin-small); - padding: var(--padding-small); + padding: var(--padding-tiny); display: none; } @@ -141,6 +151,87 @@ nav ul li a.active { outline: none; } +#client-list { + position: absolute; + top: calc(0 + var(--nav-height)); + overflow-y: auto; + overflow: hidden; + transition: opacity 0.3s ease-out, visibility 0.3s ease-out; + border: 1px solid var(--table-border-color); + background-color: var(--nav-bg-color); + z-index: 1; + opacity: 0; + visibility: hidden; + width: auto; +} + +#client-list.show { + opacity: 1; + visibility: visible; +} + +#connected-list-div { + display: inline-block; +} + +.client { + padding: var(--margin-small); + border-bottom: 1px solid var(--table-border-color); + cursor: pointer; +} + +.client-details { + max-height: 0; + overflow: hidden; + padding-left: var(--padding-small); + transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out; + background-color: var(--nav-bg-color); + margin-top: var(--margin-tiny); + opacity: 0; +} + +.client-details.show { + max-height: none; + opacity: 1; +} + +.client-detail { + border-bottom: 1px solid var(--table-border-color); + padding: var(--padding-small); +} + +#show-connected-button { + width: auto; + background-color: var(--bg-color); + border: none; + font-size: var(--base-font-size); +} + +#show-connected-button:hover, +.scroll-button:hover { + cursor: pointer; +} + +.scroll-button { + font-size: var(--base-font-size); + margin-left: var(--margin-tiny); + margin-top: var(--margin-small); +} + +footer { + background-color: var(--nav-bg-color); + text-align: right; + padding: var(--padding-tiny); + border-top: 1px solid var(--nav-border-color); +} + +footer p { + margin: 0; + color: var(--text-color); + margin-right: var(--margin-small); + font-size: 0.8rem; +} + .gantt-info { margin: 0.5rem; font-size: var(--font-size-small); diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css b/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css index 7a480b42c8..37386ebea9 100644 --- a/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css +++ b/src/blenderbim/blenderbim/bim/data/webui/static/css/index.css @@ -1,12 +1,16 @@ :root { --font-family: Arial, sans-serif; + --base-font-size: 16px; + --margin-tiny: 0.125rem; --margin-small: 0.625rem; --margin-medium: 1.25rem; --margin-large: 2.5rem; - --padding-small: 0.125rem; + --padding-tiny: 0.125rem; + --padding-small: 0.625rem; --padding-medium: 1rem; --font-size-large: 1.2rem; --logo-height: 2.5rem; + --nav-height: 2.5rem; } :root.dark { @@ -18,6 +22,7 @@ --nav-link-color: #fff; --nav-link-hover-color: #3fb449; --warning-color: #FFDB8F; + --table-border-color: #464444; } :root.light { @@ -29,6 +34,7 @@ --nav-link-color: #000000; --nav-link-hover-color: #38a63d; --warning-color: #FF4500; + --table-border-color: #222; } html { @@ -40,29 +46,26 @@ body { color: var(--text-color); margin: 0; font-family: var(--font-family); + display: flex; + flex-direction: column; + min-height: 100vh; } #container { + flex: 1; margin-top: var(--margin-medium); margin-left: var(--margin-small); margin-right: var(--margin-small); margin-bottom: var(--margin-medium); } -.table-container { - margin-bottom: var(--margin-large); -} - -.csv-table { - margin-top: var(--margin-small); -} - h3 { margin-top: 0; } nav { background-color: var(--nav-bg-color); + height: var(--nav-height); padding: var(--padding-medium) 0; display: flex; align-items: center; @@ -102,7 +105,7 @@ nav ul li a.active { .warning { color: var(--warning-color); margin-bottom: var(--margin-small); - padding: var(--padding-small); + padding: var(--padding-tiny); display: none; } @@ -118,6 +121,95 @@ nav ul li a.active { outline: none; } +#client-list { + position: absolute; + top: calc(0 + var(--nav-height)); + overflow-y: auto; + overflow: hidden; + transition: opacity 0.3s ease-out, visibility 0.3s ease-out; + border: 1px solid var(--table-border-color); + background-color: var(--nav-bg-color); + z-index: 1; + opacity: 0; + visibility: hidden; + width: auto; +} + +#client-list.show { + opacity: 1; + visibility: visible; +} + +#connected-list-div { + display: inline-block; +} + +.client { + padding: var(--margin-small); + border-bottom: 1px solid var(--table-border-color); + cursor: pointer; +} + +.client-details { + max-height: 0; + overflow: hidden; + padding-left: var(--padding-small); + transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out; + background-color: var(--nav-bg-color); + margin-top: var(--margin-tiny); + opacity: 0; +} + +.client-details.show { + max-height: none; + opacity: 1; +} + +.client-detail { + border-bottom: 1px solid var(--table-border-color); + padding: var(--padding-small); +} + +#show-connected-button { + width: auto; + background-color: var(--bg-color); + border: none; + font-size: var(--base-font-size); +} + +#show-connected-button:hover, +.scroll-button:hover { + cursor: pointer; +} + +.scroll-button { + font-size: var(--base-font-size); + margin-left: var(--margin-tiny); + margin-top: var(--margin-small); +} + +footer { + background-color: var(--nav-bg-color); + text-align: right; + padding: var(--padding-tiny); + border-top: 1px solid var(--nav-border-color); +} + +footer p { + margin: 0; + color: var(--text-color); + margin-right: var(--margin-small); + font-size: 0.8rem; +} + +.table-container { + margin-bottom: var(--margin-large); +} + +.csv-table { + margin-top: var(--margin-small); +} + /* ------------ Overwriting Tabulator CSS rules ------------ */ :root.light .tabulator-header, @@ -127,6 +219,6 @@ nav ul li a.active { } :root.light .tabulator { - border-top: 1px solid #222; - border-bottom: 2px solid #222; + border-top: 1px solid var(--table-border-color); + border-bottom: 2px solid var(--table-border-color); } \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/data/webui/static/js/drawings.js b/src/blenderbim/blenderbim/bim/data/webui/static/js/drawings.js new file mode 100644 index 0000000000..8a865dcab1 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/data/webui/static/js/drawings.js @@ -0,0 +1,263 @@ +// keeps track of blenders connected +const connectedClients = {}; +const allDrawings = {}; +let socket; + +// Document ready function +$(document).ready(function () { + var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches + ? "light" + : "dark"; + var theme = localStorage.getItem("theme") || systemTheme; + setTheme(theme); + + $("#dropdown-menu").change(function () { + const blenderId = $(this).attr("id"); + const ifcFile = $(this).val(); + displayDrawingsNames(blenderId, ifcFile); + }); + + 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("connect", handleWebConnect); + socket.on("drawings_data", handleDrawingsData); + socket.on("svg_data", handleSvgData); + // socket.on("default_data", handleDefaultData); +} + +// function used to get drawings data from blenderbim +function handleWebConnect() { + const msg = { + sourcePage: "drawings", + operator: { + type: "getDrawings", + }, + }; + socket.emit("web_operator", msg); +} + +// Function to handle 'blender_connect' event +function handleBlenderConnect(blenderId) { + console.log("blender_connect: ", blenderId); + if (!connectedClients.hasOwnProperty(blenderId)) { + connectedClients[blenderId] = { + shown: false, + ifc_file: "", + }; + } +} + +// Function to handle 'blender_disconnect' event +function handleBlenderDisconnect(blenderId) { + console.log("blender_disconnect: ", blenderId); + if (connectedClients.hasOwnProperty(blenderId)) { + delete connectedClients[blenderId]; + // remove(blenderId); + } + + $("#blender-count").text(function (i, text) { + return parseInt(text, 10) - 1; + }); +} + +function handleDrawingsData(data) { + const blenderId = data["blenderId"]; + + console.log(data); + + const filename = data["data"]["ifc_file"]; + const drawings = data["data"]["drawings_data"]["drawings"]; + const sheets = data["data"]["drawings_data"]["sheets"]; + + allDrawings[filename] = { drawings: drawings, sheets: sheets }; + + console.log(connectedClients); + + if (connectedClients.hasOwnProperty(blenderId)) { + if (!connectedClients[blenderId].shown) { + connectedClients[blenderId] = { + shown: true, + ifc_file: filename, + }; + addSelectOption(blenderId, filename); + } else { + // update(blenderId, data, filename); + } + } else { + connectedClients[blenderId] = { + shown: true, + ifc_file: filename, + }; + addSelectOption(blenderId, filename); + } +} + +function handleSvgData(data) { + const svgContainer = document.getElementById("svg-container"); + svgContainer.innerHTML = ""; + svgContainer.style.backgroundColor = "white"; + + const dom = SVG(svgContainer).svg(data); + const svgElement = dom.node.children[0]; + + // to make the svg not grow outside the svg container + svgElement.setAttribute("width", "100%"); + svgElement.setAttribute("height", "100%"); + svgElement.style.maxWidth = "100%"; + svgElement.style.maxHeight = "100%"; + svgElement.style.width = "100%"; + svgElement.style.height = "100%"; + svgElement.style.boxSizing = "border-box"; // Ensure padding and borders are included in the element's total width and height + + const panZoomControls = svgPanZoom(svgElement, { + zoomEnabled: true, + controlIconsEnabled: true, + fit: true, + center: true, + }); +} + +function addSelectOption(blenderId, filename) { + const filenameOption = $("") + .attr("id", "blender-" + blenderId) + .val(filename) + .text(filename); + $("#dropdown-menu").append(filenameOption); +} + +function displayDrawingsNames(blenderId, ifcFile) { + drawings = allDrawings[ifcFile].drawings; + sheets = allDrawings[ifcFile].sheets; + + function createSvgNames(text, index, type) { + var label = $("