Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions examples/demo/webxr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import vtk

from trame.app import TrameApp
from trame.ui.html import DivLayout
from trame.widgets import html, client, vtklocal
from trame.decorators import change

FULL_SCREEN = "position:absolute; left:0; top:0; width:100vw; height:100vh;"
TOP_RIGHT = "position: absolute; top: 1rem; right: 1rem; z-index: 999999;"
TOP_LEFT = "position: absolute; top: 1rem; left: 1rem; z-index: 999999;"
TOP_CENTER = "position: absolute; top: 1rem; left: 50%; z-index: 999999; transform: translateX(-50%);"
OFF_SCREEN = "position: none;"


def create_vtk_pipeline():
renderer = vtk.vtkWebXRRenderer()
rw = vtk.vtkWebXRRenderWindow()
rw.AddRenderer(renderer)

# WebXR Emulator doesn't work with MultiSamples>0
rw.SetMultiSamples(0)

rwi = vtk.vtkWebXRRenderWindowInteractor()
rwi.SetRenderWindow(rw)

cone = vtk.vtkConeSource()

mapper = vtk.vtkPolyDataMapper(input_connection=cone.output_port)
actor = vtk.vtkActor(mapper=mapper)

renderer.AddActor(actor)
renderer.background = (0.1, 0.2, 0.4)
# Reset camera to place actors a little bit higher
renderer.ResetCamera()
# Prevent camera issues when no actors are visible in the viewport
renderer.GetCullers().RemoveAllItems()

light = vtk.vtkLight()
light.SetColor(1, 1, 1)
light.SetPosition(0, 3, 0)
light.SetIntensity(1)
light.SetLightTypeToSceneLight()
renderer.AddLight(light)

return rw, cone, actor


class WasmApp(TrameApp):
def __init__(self, name=None):
super().__init__(name)
self.render_window, self.cone, self.cone_actor = create_vtk_pipeline()
self._build_ui()
self.state.cone_actor = {"position": None}

@change("resolution")
def on_resolution_change(self, resolution, **_):
self.cone.SetResolution(int(resolution))
self.ctrl.view_update()

def start_xr(self):
self.ctrl.start_xr()

def stop_xr(self):
self.ctrl.stop_xr()

def _build_ui(self):
with DivLayout(self.server):
client.Style("body { margin: 0; }")

with html.Div(style=TOP_CENTER):
html.Button(
"StartXR",
click=self.start_xr,
)
html.Button(
"StopXR",
click=self.stop_xr,
)
html.Input(
type="range",
v_model=("resolution", 6),
min=3,
max=60,
step=1,
style=TOP_LEFT,
)

# use style=OFF_SCREEN when not using the WebXR Emulator to hide the canvas
with html.Div(style=FULL_SCREEN):
with vtklocal.LocalView(
self.render_window,
v_if=("enable_view", True),
# use auto_resize=False when using WebXR (except when using the WebXR Emulator)
auto_resize=False,
) as view:
view.update_throttle.rate = 20 # max update rate
self.ctrl.view_update = view.update_throttle
self.ctrl.start_xr = view.start_web_xr
self.ctrl.stop_xr = view.stop_web_xr
cone_id = view.register_vtk_object(self.cone_actor)
view.listeners = (
"listeners",
{
cone_id: {
"ModifiedEvent": {
"cone_actor": {
"position": (cone_id, "Position"),
},
},
},
},
)


def main():
app = WasmApp()
app.server.start()


if __name__ == "__main__":
main()
21 changes: 21 additions & 0 deletions src/trame_vtklocal/widgets/vtklocal.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ class LocalView(HtmlElement):
}
emit_memory (bool):
Emit memory information events. By default it is skipped.
auto_resize (bool):
Enabled by default. If disabled, the render window will not
automatically resize when the canvas is resized.
updated (event):
Emitted after each completed client side update.
memory_vtk (event):
Expand Down Expand Up @@ -149,6 +152,7 @@ def __init__(self, render_window, throttle_rate=10, **kwargs):
("progress_enabled", "progressEnabled"),
("progress_delay", "progressDelay"),
("emit_memory", "emitMemory"),
("auto_resize", "autoResize"),
]
self._event_names += [
"updated",
Expand Down Expand Up @@ -308,6 +312,23 @@ def reset_camera(self, renderer_or_render_window=None, **kwargs):
id_to_reset_camera = self.get_wasm_id(renderer_or_render_window)
self.server.js_call(self.__ref, "resetCamera", id_to_reset_camera)

def start_web_xr(self, mode=1, required_features=1, optional_features=2):
"""Start WebXR session

:param mode: 0 (inline), 1 (VR) or 2 (AR)
"""
if not is_vtk_version_newer(9, 6, 20260326):
raise NotImplementedError("You need VTK>=9.7 to use WebXR (>=9.6.20260327)")
self.server.js_call(
self.__ref, "startWebXR", mode, required_features, optional_features
)

def stop_web_xr(self):
"""Stop WebXR session"""
if not is_vtk_version_newer(9, 6, 20260326):
raise NotImplementedError("You need VTK>=9.7 to use WebXR (>=9.6.20260327)")
self.server.js_call(self.__ref, "stopWebXR")

@property
def ref_name(self):
"""Return the assigned name as a vue.js ref"""
Expand Down
8 changes: 4 additions & 4 deletions vue-components/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vue-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"vue": "^2.7.0 || >=3.0.0"
},
"dependencies": {
"@kitware/vtk-wasm": "^1.7.4",
"@kitware/vtk-wasm": "^1.7.5",
"jszip": "3.10.1"
},
"devDependencies": {
Expand Down
22 changes: 20 additions & 2 deletions vue-components/src/components/VtkLocal.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export default {
// }
// }
},
autoResize: {
type: Boolean,
default: true,
}
},
setup(props, { emit }) {
// Create global WASM handler if missing
Expand Down Expand Up @@ -213,7 +217,7 @@ export default {

function handleMessage([event]) {
if (event.type === "state") {
wasmManager.pushState(event.content);
wasmManager.patchState(event.content);
}
if (event.type === "blob") {
wasmManager.pushHash(event.hash, event.content);
Expand Down Expand Up @@ -243,7 +247,7 @@ export default {
const h = Math.floor(height * window.devicePixelRatio + 0.5);
await wasmManager.setSize(props.renderWindow, w, h);
}
let resizeObserver = new ResizeObserver(resize);
let resizeObserver = props.autoResize && new ResizeObserver(resize);

// Memory -----------------------------------------------------------------

Expand Down Expand Up @@ -307,6 +311,18 @@ export default {
wasmManager.sceneManager.printSceneManagerInformation();
}

// startWebXR ----------------------------------------------------------------

function startWebXR(mode, requiredFeatures, optionalFeatures) {
Comment thread
jourdain marked this conversation as resolved.
wasmManager.sceneManager.startWebXR(mode, requiredFeatures, optionalFeatures);
}

// stopWebXR ----------------------------------------------------------------

function stopWebXR() {
Comment thread
jourdain marked this conversation as resolved.
wasmManager.sceneManager.stopWebXR();
}

// Life Cycles ------------------------------------------------------------

onMounted(async () => {
Expand Down Expand Up @@ -529,6 +545,8 @@ export default {
statePercent,
hashPercent,
wasmLoading,
startWebXR,
stopWebXR,
};
},
template: `<div ref="container" style="position: relative; width: 100%; height: 100%;">
Expand Down
Loading