diff --git a/.externals.sha256 b/.externals.sha256 index a5e4b9d..530f1e7 100644 --- a/.externals.sha256 +++ b/.externals.sha256 @@ -1,2 +1,2 @@ -1903a27bff9c2d7a6b3896e08c2cb19efd38b43a98f6089992d6df6719a3d187 ./src/trame_vtk/tools/static_viewer.html -42656ef471a77ba90930a3991fcf3cb2af647eb4c8eb29a434183596ed7c68da ./src/trame_vtk/modules/common/serve/trame-vtk.js +03e0b143a4fdd86fb0f3ad8ea2d25b450deaa514beb6a9a5b6574d32ede82748 ./src/trame_vtk/tools/static_viewer.html +0e90343e47c368d671716df64ed93b6596cd0b70518bb76b428b1cb501c8beab ./src/trame_vtk/modules/common/serve/trame-vtk.js diff --git a/.fetch_externals.sh b/.fetch_externals.sh index 293d5e5..1dd5286 100755 --- a/.fetch_externals.sh +++ b/.fetch_externals.sh @@ -3,7 +3,7 @@ set -e mkdir -p ./src/trame_vtk/modules/common/serve -curl https://unpkg.com/vue-vtk-js@3.2.2 -Lo ./src/trame_vtk/modules/common/serve/trame-vtk.js +curl https://unpkg.com/vue-vtk-js@3.3.1 -Lo ./src/trame_vtk/modules/common/serve/trame-vtk.js curl https://kitware.github.io/vtk-js/examples/OfflineLocalView/OfflineLocalView.html -Lo ./src/trame_vtk/tools/static_viewer.html if ! sha256sum --check .externals.sha256 ; then diff --git a/README.rst b/README.rst index 5255f93..91ad296 100644 --- a/README.rst +++ b/README.rst @@ -121,6 +121,7 @@ The vtkRenderWindow is only used to retrieve the scene data and parameters (colo By relying on the same vtkRenderWindow, you can easily switch from a `VtkRemoteView` to a `VtkLocalView` or vice-versa. This component gives you controls on how you want to map mouse interaction with the camera. The default setting mimic default VTK interactor style so you will rarely have to override to the `interactor_settings`. +The VtkLocalView supports WebXR thanks to the `VtkWebXRHelper` component. Please refer to the examples for details on how to use it. How to use it? ``````````````````````````````````````````````````````````` @@ -323,4 +324,4 @@ Examples JavaScript dependency ----------------------------------------------------------- -This Python package bundle the ``vue-vtk-js@3.2.2`` JavaScript library. If you would like us to upgrade it, `please reach out `_. +This Python package bundle the ``vue-vtk-js@3.3.0`` JavaScript library. If you would like us to upgrade it, `please reach out `_. diff --git a/examples/vue3/cone-webxr.py b/examples/vue3/cone-webxr.py new file mode 100644 index 0000000..a9c3334 --- /dev/null +++ b/examples/vue3/cone-webxr.py @@ -0,0 +1,111 @@ +from trame.app import get_server +from trame.widgets import vuetify3, vtk as vtk_widgets +from trame.ui.vuetify3 import SinglePageLayout + +from vtkmodules.vtkFiltersSources import vtkConeSource +from vtkmodules.vtkRenderingCore import ( + vtkRenderer, + vtkRenderWindow, + vtkRenderWindowInteractor, + vtkPolyDataMapper, + vtkActor, +) + +# VTK factory initialization +from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch # noqa +import vtkmodules.vtkRenderingOpenGL2 # noqa + +# ----------------------------------------------------------------------------- +# Trame initialization +# ----------------------------------------------------------------------------- + +server = get_server() +server.client_type = "vue3" +state, ctrl = server.state, server.controller + +state.trame__title = "VTK Local rendering" +state.xr_active = False + +DEFAULT_RESOLUTION = 6 + +# ----------------------------------------------------------------------------- +# VTK code +# ----------------------------------------------------------------------------- + +renderer = vtkRenderer() +renderer.SetBackground(0.0231, 0.4194, 0.5592) +renderWindow = vtkRenderWindow() +renderWindow.AddRenderer(renderer) +renderWindow.OffScreenRenderingOn() # Prevent popup window + +renderWindowInteractor = vtkRenderWindowInteractor() +renderWindowInteractor.SetRenderWindow(renderWindow) +renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera() + +cone_source = vtkConeSource() +mapper = vtkPolyDataMapper() +actor = vtkActor() +mapper.SetInputConnection(cone_source.GetOutputPort()) +actor.SetMapper(mapper) +renderer.AddActor(actor) +renderer.ResetCamera() +renderWindow.Render() + + +@state.change("resolution") +def update_cone(resolution=DEFAULT_RESOLUTION, **kwargs): + cone_source.SetResolution(resolution) + ctrl.view_update() + + +def update_reset_resolution(): + state.resolution = DEFAULT_RESOLUTION + + +def toggle_xr(): + if state.xr_active: + ctrl.stop_xr() + else: + ctrl.start_xr(vtk_widgets.VtkWebXRHelper.XrSessionTypes.HmdVR) + + +with SinglePageLayout(server) as layout: + layout.title.set_text("WebXR Cone") + + with layout.toolbar: + vuetify3.VSpacer() + vuetify3.VSlider( + density="compact", + v_model=("resolution", DEFAULT_RESOLUTION), + min=3, + max=60, + step=1, + hide_details=True, + dense=True, + style="max-width: 300px", + ) + vuetify3.VDivider(vertical=True, classes="mx-2") + with vuetify3.VBtn(icon=True, click=toggle_xr): + vuetify3.VIcon("mdi-virtual-reality") + + with layout.content: + with vuetify3.VContainer( + fluid=True, + classes="pa-0 fill-height", + ): + with vtk_widgets.VtkLocalView(renderWindow) as view: + ctrl.view_update = view.update + + def on_enter_xr(): + state.xr_active = True + + def on_exit_xr(): + state.xr_active = False + + webxr_helper = vtk_widgets.VtkWebXRHelper( + draw_controllers_ray=True, enter_xr=on_enter_xr, exit_xr=on_exit_xr + ) + ctrl.start_xr = webxr_helper.start_xr + ctrl.stop_xr = webxr_helper.stop_xr + +server.start() diff --git a/src/trame_vtk/widgets/vtk/__init__.py b/src/trame_vtk/widgets/vtk/__init__.py index b4ebc18..64ea421 100644 --- a/src/trame_vtk/widgets/vtk/__init__.py +++ b/src/trame_vtk/widgets/vtk/__init__.py @@ -14,6 +14,7 @@ VtkPolyData, VtkReader, VtkShareDataset, + VtkWebXRHelper, ) __all__ = [ @@ -32,4 +33,5 @@ "VtkPolyData", "VtkReader", "VtkShareDataset", + "VtkWebXRHelper", ] diff --git a/src/trame_vtk/widgets/vtk/common.py b/src/trame_vtk/widgets/vtk/common.py index fabc5ac..1699e4c 100644 --- a/src/trame_vtk/widgets/vtk/common.py +++ b/src/trame_vtk/widgets/vtk/common.py @@ -1,6 +1,7 @@ import io import json import zipfile +from enum import IntEnum from trame_client.widgets.core import AbstractElement @@ -1130,3 +1131,60 @@ def set_camera(self, camera): @property def ref_name(self): return self._ref + + +class VtkWebXRHelper(HtmlElement): + """ + The VtkWebXRHelper component provides an easy way to add WebXR support to + a VtkView or a VtkLocalView. Just add this widget in a VtkView or + VtkLocalView widget and start an XR session with `start_xr()`. + This widget will fire the `enterXR` and `exitXR` events when an XR session + is effectively entered and exited. + + >>> webxr_helper = vtk.VtkWebXRHelper( + ... ref=..., # Identifier for this component + ... draw_controllers_ray=True, # Enable XR controllers rays + ... ) + """ + + _next_id = 0 + + class XrSessionTypes(IntEnum): + """ + WebXR Session Types supported by vtk.js + """ + + HmdVR = 0 + MobileAR = 1 + LookingGlassVR = 2 + HmdAR = 3 + + def __init__(self, ref=None, **kwargs): + super().__init__( + "vtk-webXR-helper", + **kwargs, + ) + + if ref is None: + VtkWebXRHelper._next_id += 1 + ref = f"trame__webxr_helper_{VtkWebXRHelper._next_id}" + + self.__ref = ref + self._attributes["ref"] = f'ref="{ref}"' + self._attr_names += [("draw_controllers_ray", "drawControllersRay")] + self._event_names += [ + ("enter_xr", "enterXR"), + ("exit_xr", "exitXR"), + ] + + def start_xr(self, session_type: XrSessionTypes): + """ + Start a WebXR session + """ + self.server.js_call(self.__ref, "startXR", session_type) + + def stop_xr(self): + """ + Stop the current WebXR session + """ + self.server.js_call(self.__ref, "stopXR") diff --git a/tests/refs/test_rendering_lut/ref3.png b/tests/refs/test_rendering_lut/ref3.png new file mode 100644 index 0000000..558e7b6 Binary files /dev/null and b/tests/refs/test_rendering_lut/ref3.png differ