From 75372b069560111f2b9354f4e4f6fb05b78684e5 Mon Sep 17 00:00:00 2001 From: Matthias Hochsteger Date: Fri, 6 Dec 2024 16:03:34 +0100 Subject: [PATCH] wip - move code from webgui_jupyter_widgets project to Netgen --- python/CMakeLists.txt | 2 +- python/jupyter.py | 103 ++++++++++++++++++++++++++++++++++++++++++ python/webgui.py | 76 ++++++++++++++----------------- 3 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 python/jupyter.py diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index c95c4430..3a125311 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -20,7 +20,7 @@ install(FILES __main__.py __init__.py meshing.py csg.py geom2d.py stl.py gui.py NgOCC.py occ.py read_gmsh.py read_meshio.py - webgui.py + webgui.py jupyter.py DESTINATION ${NG_INSTALL_DIR_PYTHON}/${NG_INSTALL_SUFFIX} COMPONENT netgen ) diff --git a/python/jupyter.py b/python/jupyter.py new file mode 100644 index 00000000..b0333d6f --- /dev/null +++ b/python/jupyter.py @@ -0,0 +1,103 @@ +import json +import os + +import requests +from IPython.display import HTML, DisplayHandle, Javascript, display + +_id = 0 # counter for unique id for each generated div element + +# VSCode has a strange meaning of "vh" in jupyter notebooks, use pixels instead +_default_height = "500px" if "VSCODE_PID" in os.environ else "50vh" + +_webgui_loaded = False + + +def load_webgui(url: str = "", embed_code: bool = False): + global _webgui_loaded + if _webgui_loaded: + return + _webgui_loaded = True + + url = url or "https://cdn.jsdelivr.net/npm/webgui@0.2.38/dist/webgui.js" + + if embed_code: + if url.startswith("http"): + webgui_code = requests.get(url).text + else: + with open(url, "r") as f: + webgui_code = f.read() + display(HTML(f"")) + else: + display(HTML(f"")) + + +def render( + data, width="100%", height=_default_height, handle: DisplayHandle | None = None +): + load_webgui() + if handle is None: + global _id + _id += 1 + id = _id + else: + id = handle.display_id.split("_")[-1] + + el_id = f"_webgui_root_{id}" + + js_code = _render_js_template.replace("{{data}}", json.dumps(data)) + js_code = js_code.replace("{{id}}", str(id)) + js_code = js_code.replace("{{el_id}}", el_id) + if handle: + # this is a redraw call + handle.update(Javascript(js_code)) + else: + # this is a first draw call -> create the root div element for the webgui + display( + HTML( + f'
' + ) + ) + return display(Javascript(js_code), display_id=f"webgui_render_{id}") + + +_render_js_template = """ +{ + if(window._webgui_scenes === undefined) { + window._webgui_scenes = {}; + } + const waitForWebgui = (callback, counter) => { + counter = counter || 0; + if (window.webgui) { + callback(); + } else { + if (counter < 20) { + setTimeout(() => { + waitForWebgui(callback, counter + 1); + }, 200); + } + else { + console.log("Error: Webgui not loaded"); + } + } + }; + + const draw = () => { + const data = JSON.parse(`{{data}}`); + let scene = window._webgui_scenes[{{id}}]; + // console.log("have data, scene = ", scene); + if(scene === undefined) { + console.log("init scene"); + const root = document.getElementById("{{el_id}}"); + console.log("root element", root); + scene = new webgui.Scene(); + scene.init(root, data); + window._webgui_scenes[{{id}}] = scene; + } + else { + scene.updateRenderData(data); + } + // console.log("scene", scene); + } + waitForWebgui(draw); +} +""" diff --git a/python/webgui.py b/python/webgui.py index 7d5ce705..be8b7b62 100644 --- a/python/webgui.py +++ b/python/webgui.py @@ -1,17 +1,17 @@ import math import numpy as np -from time import time import os try: - import webgui_jupyter_widgets - from webgui_jupyter_widgets import BaseWebGuiScene, WebGuiDocuWidget - import webgui_jupyter_widgets.widget as wg -except ImportError: - class BaseWebGuiScene: - pass + __IPYTHON__ + _IN_IPYTHON = True - wg = None + from .jupyter import render as _render +except NameError: + _IN_IPYTHON = False + + def _render(*args, **kwargs): + pass def encodeData( data, dtype=None, encoding='b64' ): import numpy as np @@ -25,18 +25,8 @@ def encodeData( data, dtype=None, encoding='b64' ): else: raise RuntimeError("unknown encoding" + str(encoding)) -from packaging.version import parse - import netgen.meshing as ng -if wg is not None and parse(webgui_jupyter_widgets.__version__) >= parse("0.2.18"): - _default_width = None - _default_height = None -else: - _default_width = "100%" - _default_height = "50vh" - - _registered_draw_types = {} @@ -217,7 +207,7 @@ def GetData(mesh, args, kwargs): d[name] = pnew return d -class WebGLScene(BaseWebGuiScene): +class WebGLScene: class Widget: def __init__(self): self.value = {} @@ -228,6 +218,7 @@ class WebGLScene(BaseWebGuiScene): self.args = args self.kwargs = kwargs self.encoding = "b64" + self.handle = None def Redraw(self, *args, **kwargs): if args or kwargs: @@ -238,7 +229,8 @@ class WebGLScene(BaseWebGuiScene): self.obj = new_scene.obj self.args = new_scene.args self.kwargs = new_scene.kwargs - super().Redraw() + _render(self.GetData(), handle=self.handle) + # super().Redraw() def GetData(self, set_minmax=True): self.kwargs["encoding"] = self.encoding @@ -389,8 +381,6 @@ def _get_draw_default_args(): settings={}, fullscreen=False, scale=1.0, - width=_default_width, - height=_default_height, ) @@ -399,27 +389,29 @@ def Draw(obj, *args, show=True, **kwargs): kwargs_with_defaults.update(kwargs) scene = WebGLScene(obj, args, kwargs_with_defaults) - if show and wg is not None and wg._IN_IPYTHON: - if wg._IN_GOOGLE_COLAB: - from IPython.display import display, HTML - - html = scene.GenerateHTML() - display(HTML(html)) - return - else: - import webgui_jupyter_widgets as wjw - from packaging.version import parse - - # render scene using widgets.DOMWidget - if parse(wjw.__version__) < parse("0.2.15"): - scene.Draw() - else: - scene.Draw( - kwargs_with_defaults["width"], kwargs_with_defaults["height"] - ) - if "filename" in kwargs_with_defaults: - scene.GenerateHTML(filename=kwargs_with_defaults["filename"]) + scene.handle = _render(scene.GetData()) return scene + # if show and wg is not None and wg._IN_IPYTHON: + # if wg._IN_GOOGLE_COLAB: + # from IPython.display import display, HTML + # + # html = scene.GenerateHTML() + # display(HTML(html)) + # return + # else: + # import webgui_jupyter_widgets as wjw + # from packaging.version import parse + # + # # render scene using widgets.DOMWidget + # if parse(wjw.__version__) < parse("0.2.15"): + # scene.Draw() + # else: + # scene.Draw( + # kwargs_with_defaults["width"], kwargs_with_defaults["height"] + # ) + # if "filename" in kwargs_with_defaults: + # scene.GenerateHTML(filename=kwargs_with_defaults["filename"]) + # return scene async def _MakeScreenshot(data, png_file, width=1200, height=600): """Uses playwright to make a screenshot of the given html file."""