diff --git a/README.rst b/README.rst index 18bb3b7..626e907 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Installation # We need a VTK that has its wasm counterpart # This is the first version available with it # For ParaView (not yet supported), VTK don't need to be installed - pip install "vtk==9.3.20240525.dev0" --extra-index-url https://wheels.vtk.org + pip install "vtk==9.3.20240817.dev0" --extra-index-url https://wheels.vtk.org Development @@ -64,7 +64,7 @@ Running examples # We need a VTK that has its wasm counterpart # This is the first version available with it # For ParaView (not yet supported), VTK don't need to be installed - pip install "vtk==9.3.20240418.dev0" --extra-index-url https://wheels.vtk.org + pip install "vtk==9.3.20240810.dev0" --extra-index-url https://wheels.vtk.org # regular trame app python ./examples/vtk/cone.py @@ -72,3 +72,13 @@ Running examples Some example are meant to test and validate WASM rendering. Some will default for remote rendering but if you want to force them to use WASM just run `export USE_WASM=1` before executing them. + +SharedArrayBuffer +---------------------------------------- + +To enable SharedArrayBuffer within trame you can run the following. + +.. code-block:: console + + export WSLINK_HTTP_HEADERS=./http_headers.json + python ./examples/vtk/cone.py \ No newline at end of file diff --git a/examples/pv/README.md b/examples/pv/README.md index 482c065..79d3bfd 100644 --- a/examples/pv/README.md +++ b/examples/pv/README.md @@ -1,3 +1,7 @@ +# ParaView support + +Not Supported yet but we hope to get it working with 5.14 or nightly after 5.13 release. + ## Setup ``` diff --git a/examples/pv/cone.py b/examples/pv/cone.py index ab5b56b..06e930f 100644 --- a/examples/pv/cone.py +++ b/examples/pv/cone.py @@ -16,6 +16,10 @@ class DemoApp: def __init__(self, server=None): self.server = get_server(server, client_type=CLIENT_TYPE) + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.cone = simple.Cone() self.representation = simple.Show() self.view = simple.Render() diff --git a/examples/pyvista/basic.py b/examples/pyvista/basic.py index 5dea363..df93b29 100644 --- a/examples/pyvista/basic.py +++ b/examples/pyvista/basic.py @@ -1,4 +1,4 @@ -import os +# import os import pyvista as pv from pyvista import examples @@ -7,7 +7,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -32,6 +32,10 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window, self.widgets = setup_pyvista() self.html_view = None self.ui = self._ui() diff --git a/examples/pyvista/bg_image.py b/examples/pyvista/bg_image.py index 3de9f46..2f1c963 100644 --- a/examples/pyvista/bg_image.py +++ b/examples/pyvista/bg_image.py @@ -1,4 +1,4 @@ -import os +# import os import pyvista as pv from pyvista import examples @@ -7,7 +7,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -17,9 +17,10 @@ def setup_pyvista(): p.add_mesh(examples.load_airplane(), smooth_shading=True) p.add_background_image(examples.mapfile) p.reset_camera() - # p.show_axes() # FIXME + p.show_axes() + widgets = [r.axes_widget for r in p.renderers if hasattr(r, "axes_widget")] - return p.ren_win + return p.ren_win, widgets # ----------------------------------------------------------------------------- @@ -28,7 +29,11 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") - self.render_window = setup_pyvista() + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + + self.render_window, self.widgets = setup_pyvista() self.html_view = None self.ui = self._ui() @@ -40,6 +45,8 @@ def _ui(self): ): if WASM: self.html_view = vtklocal.LocalView(self.render_window) + for w in self.widgets: + self.html_view.register_widget(w) else: self.html_view = vtk_widgets.VtkRemoteView( self.render_window, interactive_ratio=1 diff --git a/examples/pyvista/drap2dsurf.py b/examples/pyvista/drap2dsurf.py index 8a04723..3d01aca 100644 --- a/examples/pyvista/drap2dsurf.py +++ b/examples/pyvista/drap2dsurf.py @@ -1,4 +1,4 @@ -import os +# import os import numpy as np import pyvista as pv from pyvista import examples @@ -8,7 +8,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -34,9 +34,10 @@ def setup_pyvista(): p.add_mesh(grid, cmap="seismic", clim=[-1, 1]) p.add_mesh(pv.PolyData(path), color="orange") p.reset_camera() - # p.show_axes() # FIXME + p.show_axes() + widgets = [r.axes_widget for r in p.renderers if hasattr(r, "axes_widget")] - return p.ren_win + return p.ren_win, widgets # ----------------------------------------------------------------------------- @@ -45,7 +46,11 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") - self.render_window = setup_pyvista() + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + + self.render_window, self.widgets = setup_pyvista() self.html_view = None self.ui = self._ui() @@ -57,6 +62,8 @@ def _ui(self): ): if WASM: self.html_view = vtklocal.LocalView(self.render_window) + for w in self.widgets: + self.html_view.register_widget(w) else: self.html_view = vtk_widgets.VtkRemoteView( self.render_window, interactive_ratio=1 diff --git a/examples/pyvista/eye_dome_lighting.py b/examples/pyvista/eye_dome_lighting.py index d6bd9dd..386d656 100644 --- a/examples/pyvista/eye_dome_lighting.py +++ b/examples/pyvista/eye_dome_lighting.py @@ -1,4 +1,4 @@ -import os +# import os import pyvista as pv from pyvista import examples @@ -7,7 +7,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -20,8 +20,9 @@ def setup_pyvista(): p.enable_eye_dome_lighting() p.reset_camera() p.show_axes() + widgets = [r.axes_widget for r in p.renderers if hasattr(r, "axes_widget")] - return p.ren_win + return p.ren_win, widgets # ----------------------------------------------------------------------------- @@ -30,7 +31,11 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") - self.render_window = setup_pyvista() + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + + self.render_window, self.widgets = setup_pyvista() self.html_view = None self.ui = self._ui() @@ -42,6 +47,8 @@ def _ui(self): ): if WASM: self.html_view = vtklocal.LocalView(self.render_window) + for w in self.widgets: + self.html_view.register_widget(w) else: self.html_view = vtk_widgets.VtkRemoteView( self.render_window, interactive_ratio=1 diff --git a/examples/pyvista/glyph.py b/examples/pyvista/glyph.py index 34abea5..084c699 100644 --- a/examples/pyvista/glyph.py +++ b/examples/pyvista/glyph.py @@ -32,6 +32,10 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_pyvista() self.html_view = None self.ui = self._ui() diff --git a/examples/pyvista/isolines.py b/examples/pyvista/isolines.py index 50b0f2f..e06157b 100644 --- a/examples/pyvista/isolines.py +++ b/examples/pyvista/isolines.py @@ -1,4 +1,4 @@ -import os +# import os import pyvista as pv from pyvista import examples @@ -7,7 +7,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -31,6 +31,10 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_pyvista() self.html_view = None self.ui = self._ui() diff --git a/examples/pyvista/labels.py b/examples/pyvista/labels.py index 64a6c97..ffcd3d5 100644 --- a/examples/pyvista/labels.py +++ b/examples/pyvista/labels.py @@ -1,4 +1,3 @@ -import os import numpy as np import pyvista as pv @@ -7,7 +6,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -40,7 +39,7 @@ def get_point_along_spline(distance): p = pv.Plotter() p.add_mesh(spline, scalars="arc_length", render_lines_as_tubes=True, line_width=10) - p.add_point_labels( + labels = p.add_point_labels( label_points, labels, always_visible=True, @@ -61,9 +60,19 @@ def get_point_along_spline(distance): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_pyvista() self.html_view = None self.ui = self._ui() + self.server.state.change("scale")(self.update_scale) + + def update_scale(self, scale, **_): + print(scale) + # self.text.SetTextScaleModeToViewport() + # self.html_view.update() def _ui(self): with DivLayout(self.server) as layout: @@ -77,6 +86,14 @@ def _ui(self): self.html_view = vtk_widgets.VtkRemoteView( self.render_window, interactive_ratio=1 ) + html.Input( + type="range", + min=0.5, + max=4, + step=0.1, + v_model=("scale", 1), + style="position: absolute; top: 10px; left: 10px; z-index: 100; width: calc(100vw - 20px);", + ) return layout diff --git a/examples/pyvista/linked_view.py b/examples/pyvista/linked_view.py index b20435d..da53d82 100644 --- a/examples/pyvista/linked_view.py +++ b/examples/pyvista/linked_view.py @@ -1,4 +1,4 @@ -import os +# import os import pyvista as pv from pyvista import examples @@ -7,7 +7,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -25,9 +25,10 @@ def setup_pyvista(): p.add_mesh(decimated, color=True, show_edges=True) p.link_views() # link all the views p.reset_camera() - # p.show_axes() # FIXME + p.show_axes() + widgets = [r.axes_widget for r in p.renderers if hasattr(r, "axes_widget")] - return p.ren_win + return p.ren_win, widgets # ----------------------------------------------------------------------------- @@ -36,7 +37,11 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") - self.render_window = setup_pyvista() + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + + self.render_window, self.widgets = setup_pyvista() self.html_view = None self.ui = self._ui() @@ -48,6 +53,8 @@ def _ui(self): ): if WASM: self.html_view = vtklocal.LocalView(self.render_window) + for w in self.widgets: + self.html_view.register_widget(w) else: self.html_view = vtk_widgets.VtkRemoteView( self.render_window, interactive_ratio=1 diff --git a/examples/pyvista/pbr.py b/examples/pyvista/pbr.py index a0b0172..2172412 100644 --- a/examples/pyvista/pbr.py +++ b/examples/pyvista/pbr.py @@ -1,4 +1,4 @@ -import os +# import os import pyvista as pv from pyvista import examples @@ -7,7 +7,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -33,6 +33,10 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_pyvista() self.html_view = None self.ui = self._ui() diff --git a/examples/pyvista/requirements.txt b/examples/pyvista/requirements.txt index 8c0e590..1d48100 100644 --- a/examples/pyvista/requirements.txt +++ b/examples/pyvista/requirements.txt @@ -1,2 +1,3 @@ pyvista trame-vtk +trame-server>=3.1.0 diff --git a/examples/pyvista/spline.py b/examples/pyvista/spline.py index b11db6d..dd8e6f1 100644 --- a/examples/pyvista/spline.py +++ b/examples/pyvista/spline.py @@ -41,6 +41,10 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_pyvista() self.html_view = None self.ui = self._ui() diff --git a/examples/pyvista/texture.py b/examples/pyvista/texture.py index eef952a..9056ce7 100644 --- a/examples/pyvista/texture.py +++ b/examples/pyvista/texture.py @@ -1,4 +1,4 @@ -import os +# import os import pyvista as pv from pyvista import examples @@ -7,7 +7,7 @@ from trame.widgets import html, client, vtk as vtk_widgets from trame_vtklocal.widgets import vtklocal -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ # ----------------------------------------------------------------------------- @@ -37,6 +37,10 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_pyvista() self.html_view = None self.ui = self._ui() diff --git a/examples/pyvista/widget.py b/examples/pyvista/widget.py index 72119b0..f280b08 100644 --- a/examples/pyvista/widget.py +++ b/examples/pyvista/widget.py @@ -27,6 +27,10 @@ def setup_pyvista(): class TrameApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_pyvista() self.html_view = None self.ui = self._ui() diff --git a/examples/vtk/axes_actor.py b/examples/vtk/axes_actor.py index f771432..d432146 100644 --- a/examples/vtk/axes_actor.py +++ b/examples/vtk/axes_actor.py @@ -51,6 +51,10 @@ def setup_vtk(): class App: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = setup_vtk() self.ui = self._build_ui() diff --git a/examples/vtk/charts_scatter.py b/examples/vtk/charts_scatter.py index 0172b70..1c5ada2 100644 --- a/examples/vtk/charts_scatter.py +++ b/examples/vtk/charts_scatter.py @@ -1,5 +1,5 @@ -import math import os +import math from vtkmodules.vtkChartsCore import vtkChart, vtkChartXY, vtkPlotPoints from vtkmodules.vtkCommonColor import vtkNamedColors from vtkmodules.vtkCommonCore import vtkFloatArray @@ -87,6 +87,10 @@ def create_vtk_pipeline(): class App: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = create_vtk_pipeline() self.html_view = None self.ui = self._ui() diff --git a/examples/vtk/cone.py b/examples/vtk/cone.py index c187892..ea177ea 100644 --- a/examples/vtk/cone.py +++ b/examples/vtk/cone.py @@ -57,6 +57,10 @@ def create_vtk_pipeline(): class DemoApp: def __init__(self, server=None): self.server = get_server(server, client_type=CLIENT_TYPE) + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window, self.cone = create_vtk_pipeline() self.server.state.update(dict(mem_blob=0, mem_vtk=0)) self.html_view = None diff --git a/examples/vtk/flow.py b/examples/vtk/flow.py index 9466b2e..c1f17ad 100644 --- a/examples/vtk/flow.py +++ b/examples/vtk/flow.py @@ -136,6 +136,10 @@ def create_vtk_pipeline(): class DemoApp: def __init__(self, server=None): self.server = get_server(server, client_type=CLIENT_TYPE) + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window = create_vtk_pipeline() self.html_view = None self.ui = self._ui() diff --git a/examples/vtk/glyph.py b/examples/vtk/glyph.py index f103525..6cfa76d 100644 --- a/examples/vtk/glyph.py +++ b/examples/vtk/glyph.py @@ -22,6 +22,8 @@ import vtkmodules.vtkRenderingOpenGL2 # noqa from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch # noqa +import json + def setup_vtk(): colors = vtkNamedColors() @@ -117,11 +119,17 @@ def setup_vtk(): class App: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window, self.renderer, self.cone, self.sphere = setup_vtk() self.view_local = None self.view_remote = None self.ui = self._build_ui() + self.server.state.camera = None + @property def ctrl(self): return self.server.controller @@ -133,11 +141,23 @@ def on_resolution_change(self, resolution, **kwargs): self.view_remote.update() self.view_local.update() + @change("camera") + def on_camera_change(self, camera, **kwargs): + if camera is not None: + self.view_local.object_manager.UpdateObjectFromState(json.dumps(camera)) + self.view_remote.update() + def reset_camera(self): self.renderer.ResetCamera() self.view_local.update() self.view_remote.update() + def update_client(self, reset_camera=False): + if reset_camera: + self.renderer.ResetCamera() + self.ctrl.rview_reset_camera() + self.ctrl.view_update(push_camera=True) + def _build_ui(self): with SinglePageLayout(self.server) as layout: layout.icon.click = self.reset_camera @@ -151,7 +171,12 @@ def _build_ui(self): dense=True, hide_details=True, ) - vuetify.VBtn("Update", click=self.ctrl.view_update) + vuetify.VBtn("S => C", click=self.update_client) + vuetify.VBtn(icon="mdi-crop-free", click=self.ctrl.view_reset_camera) + vuetify.VBtn( + icon="mdi-panorama-variant-outline", + click=(self.update_client, "[true]"), + ) with layout.content: with vuetify.VContainer( @@ -164,14 +189,17 @@ def _build_ui(self): self.view_local = vtklocal.LocalView( self.render_window, eager_sync=True, + camera="camera = $event", ) self.ctrl.view_update = self.view_local.update + self.ctrl.view_reset_camera = self.view_local.reset_camera with vuetify.VContainer( fluid=True, classes="pa-0 fill-height", style="width: 50%;" ): self.view_remote = VtkRemoteView( self.render_window, interactive_ratio=1 ) + self.ctrl.rview_reset_camera = self.view_remote.reset_camera # hide footer layout.footer.hide() diff --git a/examples/vtk/requirements.txt b/examples/vtk/requirements.txt index 39c2cef..f55dd6a 100644 --- a/examples/vtk/requirements.txt +++ b/examples/vtk/requirements.txt @@ -1,2 +1,3 @@ trame-vuetify trame-vtk +trame-server>=3.1 diff --git a/examples/vtk/silhouette.py b/examples/vtk/silhouette.py index 2f1cc69..c00d31c 100644 --- a/examples/vtk/silhouette.py +++ b/examples/vtk/silhouette.py @@ -56,6 +56,10 @@ def setup_vtk(): class SilhouetteApp: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window, self.cone_source = setup_vtk() self.ui = self._build_ui() @@ -105,6 +109,7 @@ def _build_ui(self): with vuetify3.VCol(classes="pa-0 fill-height"): view = vtklocal.LocalView( self.render_window, + eager_sync=True, ) self.ctrl.view_update = view.update self.ctrl.view_reset_camera = view.reset_camera diff --git a/examples/vtk/volume.py b/examples/vtk/volume.py index 326e782..a8978ff 100644 --- a/examples/vtk/volume.py +++ b/examples/vtk/volume.py @@ -78,6 +78,10 @@ def setup_vtk(): class App: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.local_view = None self.render_window = setup_vtk() self.ui = self._build_ui() diff --git a/examples/vtk/widgets_box.py b/examples/vtk/widgets_box.py index 9024ea0..c299fc7 100644 --- a/examples/vtk/widgets_box.py +++ b/examples/vtk/widgets_box.py @@ -78,6 +78,10 @@ def create_vtk_pipeline(): class App: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.render_window, self.widget = create_vtk_pipeline() self.html_view = None self.ui = self._ui() @@ -90,6 +94,7 @@ def _ui(self): ): if WASM: self.html_view = vtklocal.LocalView(self.render_window) + self.html_view.register_widget(self.widget) else: self.html_view = vtk_widgets.VtkRemoteView(self.render_window) diff --git a/examples/vtk/widgets_camera.py b/examples/vtk/widgets_camera.py index 49dfaa4..236faff 100644 --- a/examples/vtk/widgets_camera.py +++ b/examples/vtk/widgets_camera.py @@ -1,4 +1,4 @@ -import os +# import os from pathlib import Path from trame.app import get_server @@ -22,7 +22,7 @@ vtkRenderer, ) -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ def create_vtk_pipeline(path): @@ -75,6 +75,10 @@ def create_vtk_pipeline(path): class App: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.server.cli.add_argument("--data") args, _ = self.server.cli.parse_known_args() self.render_window = create_vtk_pipeline(args.data) diff --git a/examples/vtk/widgets_plane.py b/examples/vtk/widgets_plane.py index fd81b4f..d30a58d 100644 --- a/examples/vtk/widgets_plane.py +++ b/examples/vtk/widgets_plane.py @@ -1,4 +1,4 @@ -import os +# import os from pathlib import Path from trame.app import get_server @@ -29,7 +29,7 @@ vtkRenderer, ) -WASM = "USE_WASM" in os.environ +WASM = True # "USE_WASM" in os.environ def create_vtk_pipeline(file_to_load): @@ -132,6 +132,10 @@ def __call__(self, caller, ev): class App: def __init__(self, server=None): self.server = get_server(server, client_type="vue3") + + # enable shared array buffer + self.server.http_headers.shared_array_buffer = True + self.server.cli.add_argument("--data") args, _ = self.server.cli.parse_known_args() self.render_window, self.widget = create_vtk_pipeline(args.data) diff --git a/http_headers.json b/http_headers.json new file mode 100644 index 0000000..9b8c0ca --- /dev/null +++ b/http_headers.json @@ -0,0 +1,6 @@ +{ + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + "Access-Control-Allow-Origin": "*", + "Cache-Control": "no-store" +} \ No newline at end of file diff --git a/trame_vtklocal/module/__init__.py b/trame_vtklocal/module/__init__.py index 4544c27..62190b5 100644 --- a/trame_vtklocal/module/__init__.py +++ b/trame_vtklocal/module/__init__.py @@ -30,4 +30,6 @@ def get_helper(server): def setup(trame_server, **kwargs): global HELPERS_PER_SERVER HELPERS_PER_SERVER[trame_server.name] = ObjectManagerHelper(trame_server) - trame_server.enable_module(register_wasm(serve_path)) + trame_server.enable_module( + register_wasm(serve_path, trame_server.http_headers.shared_array_buffer) + ) diff --git a/trame_vtklocal/module/protocol.py b/trame_vtklocal/module/protocol.py index f16602e..3c2f227 100644 --- a/trame_vtklocal/module/protocol.py +++ b/trame_vtklocal/module/protocol.py @@ -1,3 +1,4 @@ +# import json from wslink import register as export_rpc from wslink.websocket import LinkProtocol @@ -15,13 +16,34 @@ def __init__(self, *args, **kwargs): self.vtk_object_manager = vtkObjectManager() self.vtk_object_manager.Initialize() self._subscriptions = {} + self._widgets = {} self._last_publish_states = {} self._last_publish_hash = set() + self._push_camera = False self._debug_state = False self._debug_state_counter = 1 - def update(self): + def register_widget(self, root_obj, dep_obj): + self.vtk_object_manager.RegisterObject(dep_obj) + root_id = self.vtk_object_manager.GetId(root_obj) + dep_id = self.vtk_object_manager.GetId(dep_obj) + if root_id not in self._widgets: + self._widgets[root_id] = set() + + self._widgets[root_id].add(dep_id) + print(f"Register widget: {dep_obj.GetClassName()}={dep_id}") + + def unregister_widget(self, root_obj, dep_obj): + self.vtk_object_manager.UnRegisterObject(dep_obj) + root_id = self.vtk_object_manager.GetId(root_obj) + dep_id = self.vtk_object_manager.GetId(dep_obj) + if root_id in self._widgets: + self._widgets[root_id].discard(dep_id) + + def update(self, push_camera=False, **_): + self._push_camera = push_camera + self.vtk_object_manager.UpdateStatesFromObjects() if self._debug_state: self.vtk_object_manager.Export(f"snapshot-{self._debug_state_counter}") @@ -72,10 +94,26 @@ def update_subscription(self, obj_id, delta): self._last_publish_states.clear() self._last_publish_hash.clear() + # Keep track of widgets as well + if obj_id in self._widgets: + for w_id in self._widgets[obj_id]: + self.update_subscription(w_id, delta) + @export_rpc("vtklocal.get.state") def get_state(self, obj_id): - # print(f"get_state {obj_id} {self.vtk_object_manager.GetObjectAtId(obj_id).GetClassName()}") state = self.vtk_object_manager.GetState(obj_id) + + # ------------------------------------------------- + # DEBUG - Helper for dynamic state patching + # ------------------------------------------------- + # state = json.loads(state) + # if state["ClassName"] == "vtkTextProperty": + # state["FontSize"] *= 2 + # elif state["ClassName"] == "vtkCubeAxesActor": + # state["ScreenSize"] *= 2 + # state = json.dumps(state) + # ------------------------------------------------- + return state @export_rpc("vtklocal.get.hash") @@ -87,16 +125,37 @@ def get_hash(self, hash): def get_status(self, obj_id): # print("get_status", obj_id) ids = self.vtk_object_manager.GetAllDependencies(obj_id) + + # Add widgets ids without duplicate + ids_width_deps = list(ids) + if obj_id in self._widgets: + for dep_id in self._widgets[obj_id]: + ids_width_deps += list( + self.vtk_object_manager.GetAllDependencies(dep_id) + ) + ids = list(set(ids_width_deps)) + hashes = self.vtk_object_manager.GetBlobHashes(ids) renderWindow = self.vtk_object_manager.GetObjectAtId(obj_id) ids_mtime = [map_id_mtime(self.vtk_object_manager, v) for v in ids] ignore_ids = [] + cameras = [] if renderWindow: + interactor = self.vtk_object_manager.GetId(renderWindow.interactor) renderers = renderWindow.GetRenderers() for renderer in renderers: activeCamera = renderer.GetActiveCamera() - ignore_ids.append(self.vtk_object_manager.GetId(activeCamera)) - return dict(ids=ids_mtime, hashes=hashes, ignore_ids=ignore_ids) + cid = self.vtk_object_manager.GetId(activeCamera) + if not self._push_camera: + ignore_ids.append(cid) + cameras.append(cid) + return dict( + ids=ids_mtime, + hashes=hashes, + ignore_ids=ignore_ids, + cameras=cameras, + interactor=interactor, + ) class ObjectManagerHelper: diff --git a/trame_vtklocal/module/wasm.py b/trame_vtklocal/module/wasm.py index b60909a..b625e58 100644 --- a/trame_vtklocal/module/wasm.py +++ b/trame_vtklocal/module/wasm.py @@ -54,24 +54,26 @@ async def setup_wasm_directory(target_directory, wasm_url): with tarfile.open(dest_file) as tgz: tgz.extractall(dest_folder) - print(f"Downloaded WASM in {dest_folder}") + print(f"Downloaded WASM:\n - from: {wasm_url}\n - to: {dest_folder}") Path(dest_file).unlink() -def get_wasm_info(): +def get_wasm_info(use_thread=False): from vtkmodules.vtkCommonCore import vtkVersion vtk_version = vtkVersion() - version = vtk_version.GetVTKVersion() wasm_bits = "wasm32" - return ( - version, - f"https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-{wasm_bits}-emscripten/{version}/vtk-{version}-{wasm_bits}-emscripten.tar.gz", - ) + version = vtk_version.GetVTKVersion() + url_addon = "-threads" if use_thread else "" + url = f"https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-{wasm_bits}-emscripten{url_addon}/{version}/vtk-{version}-{wasm_bits}-emscripten{url_addon}.tar.gz" + if use_thread: + version += "-threads" + + return version, url -def register_wasm(serve_path): - version, wasm_url = get_wasm_info() +def register_wasm(serve_path, use_thread=False): + version, wasm_url = get_wasm_info(use_thread) BASE_URL = f"__trame_vtklocal/wasm/{version}" dest_directory = Path(serve_path) / "wasm" / version diff --git a/trame_vtklocal/widgets/vtklocal.py b/trame_vtklocal/widgets/vtklocal.py index 053f530..b3e449e 100644 --- a/trame_vtklocal/widgets/vtklocal.py +++ b/trame_vtklocal/widgets/vtklocal.py @@ -75,6 +75,7 @@ def __init__(self, render_window, **kwargs): "updated", ("memory_vtk", "memory-vtk"), ("memory_arrays", "memory-arrays"), + ("camera", "camera"), ] @property @@ -85,18 +86,19 @@ def api(self): def object_manager(self): return self.api.vtk_object_manager - def update(self): - self.api.update() + def update(self, push_camera=False): + self.api.update(push_camera=push_camera) self.server.js_call(self.__ref, "update") def register_widget(self, w): if w not in self.__registered_obj: + self.api.register_widget(self._render_window, w) self.__registered_obj.append(w) - self.object_manager.RegisterObject(w) + self.api.update() def uregister_widgets(self): for w in self.__registered_obj: - self.object_manager.UnRegisterObject(w) + self.api.unregister(self._render_window, w) self.__registered_obj.clear() diff --git a/vue-components/src/components/VtkLocal.js b/vue-components/src/components/VtkLocal.js index 90a21f9..f2f6f83 100644 --- a/vue-components/src/components/VtkLocal.js +++ b/vue-components/src/components/VtkLocal.js @@ -2,7 +2,7 @@ import { inject, ref, unref, onMounted, onBeforeUnmount } from "vue"; import { createModule } from "../utils"; export default { - emits: ["updated", "memory-vtk", "memory-arrays"], + emits: ["updated", "memory-vtk", "memory-arrays", "camera"], props: { renderWindow: { type: Number, @@ -22,6 +22,8 @@ export default { setup(props, { emit }) { const trame = inject("trame"); const wasmURL = trame.state.get("__trame_vtklocal_wasm_url"); + const cameraIds = []; + const observerTags = []; const container = ref(null); const canvas = ref(null); const client = props.wsClient || trame?.client; @@ -192,6 +194,11 @@ export default { // console.log("skip", vtkId); } }); + + // For listeners + cameraIds.push(...serverStatus.cameras); + // interactorId = serverStatus.interactor; + serverStatus.ignore_ids.forEach((vtkId) => { sceneManager.unRegisterState(vtkId); }); @@ -244,11 +251,20 @@ export default { subscribe(); } await update(); - // sceneManager.addObserver(props.renderWindow, "StartEvent", (id, eventName) => { - // eventName = sceneManager.UTF8ToString(eventName); - // console.log(`${eventName} emitted from ${id}`); - // }); - // sceneManager.addObserver(props.renderWindow, "EndEvent", () => { console.log("vtkRenderWindow EndEvent"); }); + + // Camera listener + for (let i = 0; i < cameraIds.length; i++) { + const cid = cameraIds[i]; + observerTags.push([ + cid, + sceneManager.addObserver(cid, "ModifiedEvent", () => { + sceneManager.updateStateFromObject(cid); + const cameraState = sceneManager.getState(cid); + emit("camera", cameraState); + }), + ]); + } + sceneManager.startEventLoop(props.renderWindow); if (resizeObserver) { resizeObserver.observe(unref(container)); @@ -259,6 +275,13 @@ export default { if (subscription) { unsubscribe(); } + + // Camera listeners + while (observerTags.length) { + const [cid, tag] = observerTags.pop(); + sceneManager.removeObserver(cid, tag); + } + // console.log("vtkLocal::unmounted"); sceneManager.stopEventLoop(props.renderWindow); if (resizeObserver) {