Skip to content

Commit d3efbb5

Browse files
committed
feat(examples): add webxr example
1 parent 7396811 commit d3efbb5

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

examples/demo/webxr.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import vtk
2+
3+
from trame.app import get_server
4+
from trame.ui.html import DivLayout
5+
from trame.widgets import html, client, vtklocal
6+
from trame.decorators import TrameApp, change
7+
8+
FULL_SCREEN = "position:absolute; left:0; top:0; width:100vw; height:100vh;"
9+
TOP_RIGHT = "position: absolute; top: 1rem; right: 1rem; z-index: 999999;"
10+
TOP_LEFT = "position: absolute; top: 1rem; left: 1rem; z-index: 999999;"
11+
TOP_CENTER = "position: absolute; top: 1rem; left: 50%; z-index: 999999; transform: translateX(-50%);"
12+
OFF_SCREEN = "position: none;"
13+
14+
15+
def create_vtk_pipeline():
16+
renderer = vtk.vtkWebXRRenderer()
17+
rw = vtk.vtkWebXRRenderWindow()
18+
rw.AddRenderer(renderer)
19+
20+
# WebXR Emulator doesn't work with MultiSamples>0
21+
rw.SetMultiSamples(0)
22+
23+
rwi = vtk.vtkWebXRRenderWindowInteractor()
24+
rwi.SetRenderWindow(rw)
25+
26+
cone = vtk.vtkConeSource()
27+
28+
mapper = vtk.vtkPolyDataMapper(input_connection=cone.output_port)
29+
actor = vtk.vtkActor(mapper=mapper)
30+
31+
renderer.AddActor(actor)
32+
renderer.background = (0.1, 0.2, 0.4)
33+
# Reset camera to place actors a little bit higher
34+
renderer.ResetCamera()
35+
# Prevent camera issues when no actors are visible in the viewport
36+
renderer.GetCullers().RemoveAllItems()
37+
38+
light = vtk.vtkLight()
39+
light.SetColor(1, 1, 1)
40+
light.SetPosition(0, 3, 0)
41+
light.SetIntensity(1)
42+
light.SetLightTypeToSceneLight()
43+
renderer.AddLight(light)
44+
45+
return rw, cone, actor
46+
47+
48+
@TrameApp()
49+
class WasmApp:
50+
def __init__(self, server=None):
51+
self.server = get_server(server)
52+
self.render_window, self.cone, self.cone_actor = create_vtk_pipeline()
53+
self._build_ui()
54+
self.state.cone_actor = {"position": None}
55+
56+
@property
57+
def state(self):
58+
return self.server.state
59+
60+
@property
61+
def ctrl(self):
62+
return self.server.controller
63+
64+
@change("resolution")
65+
def on_resolution_change(self, resolution, **_):
66+
self.cone.SetResolution(int(resolution))
67+
self.ctrl.view_update()
68+
69+
def startxr(self):
70+
self.ctrl.startxr()
71+
72+
def stopxr(self):
73+
self.ctrl.stopxr()
74+
75+
def _build_ui(self):
76+
with DivLayout(self.server):
77+
client.Style("body { margin: 0; }")
78+
79+
with html.Div(style=TOP_CENTER):
80+
html.Button(
81+
"StartXR",
82+
click=self.startxr,
83+
)
84+
html.Button(
85+
"StopXR",
86+
click=self.stopxr,
87+
)
88+
html.Input(
89+
type="range",
90+
v_model=("resolution", 6),
91+
min=3,
92+
max=60,
93+
step=1,
94+
style=TOP_LEFT,
95+
)
96+
97+
# use style=OFF_SCREEN when not using the WebXR Emulator to hide the canvas
98+
with html.Div(style=FULL_SCREEN):
99+
with vtklocal.LocalView(
100+
self.render_window,
101+
v_if=("enable_view", True),
102+
# use auto_resize=False when using WebXR (except when using the WebXR Emulator)
103+
auto_resize=False,
104+
) as view:
105+
view.update_throttle.rate = 20 # max update rate
106+
self.ctrl.view_update = view.update_throttle
107+
self.ctrl.startxr = view.startXR
108+
self.ctrl.stopxr = view.stopXR
109+
cone_id = view.register_vtk_object(self.cone_actor)
110+
view.listeners = (
111+
"listeners",
112+
{
113+
cone_id: {
114+
"ModifiedEvent": {
115+
"cone_actor": {
116+
"position": (cone_id, "Position"),
117+
},
118+
},
119+
},
120+
},
121+
)
122+
123+
124+
def main():
125+
app = WasmApp()
126+
app.server.start()
127+
128+
129+
if __name__ == "__main__":
130+
main()

0 commit comments

Comments
 (0)