Skip to content

Commit 6646477

Browse files
committed
docs(wasm): add example
1 parent 6898ffa commit 6646477

4 files changed

Lines changed: 259 additions & 0 deletions

File tree

examples/06_vtk/04_wasm/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Bike CFD example with local rendering using WASM
2+
3+
```
4+
python -m venv .venv
5+
source .venv/bin/activate
6+
pip install -r ./requirements.txt
7+
python ./app.py
8+
```
9+
10+
![](./cfd-bike.gif)

examples/06_vtk/04_wasm/app.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import vtk
2+
3+
from trame.app import TrameApp
4+
from trame.ui.vuetify3 import SinglePageWithDrawerLayout
5+
from trame.widgets import vtklocal, trame as tw, vuetify3 as v3
6+
from trame.decorators import change
7+
from trame.assets.remote import HttpFile
8+
from trame.assets.local import to_url
9+
10+
# -----------------------------------------------------------------------------
11+
# Fetch data / files
12+
# -----------------------------------------------------------------------------
13+
BIKE = HttpFile(
14+
"bike.vtp",
15+
"https://github.com/Kitware/trame-app-bike/raw/master/data/bike.vtp",
16+
)
17+
TUNNEL = HttpFile(
18+
"tunnel.vtu",
19+
"https://github.com/Kitware/trame-app-bike/raw/master/data/tunnel.vtu",
20+
)
21+
IMAGE = HttpFile(
22+
"seeds.jpg",
23+
"https://github.com/Kitware/trame-app-bike/raw/master/data/seeds.jpg",
24+
)
25+
26+
if not BIKE.local:
27+
BIKE.fetch()
28+
29+
if not TUNNEL.local:
30+
TUNNEL.fetch()
31+
32+
if not IMAGE.local:
33+
IMAGE.fetch()
34+
35+
# -----------------------------------------------------------------------------
36+
# Constants setup
37+
# -----------------------------------------------------------------------------
38+
P1 = [-0.4, 0, 0.05]
39+
P2 = [-0.4, 0, 1.5]
40+
41+
INITIAL_STATE = {
42+
"line_widget": {
43+
"p1": P1,
44+
"p2": P2,
45+
},
46+
"trame__title": "Bike CFD",
47+
"trame__favicon": to_url(IMAGE.path),
48+
}
49+
50+
51+
# -----------------------------------------------------------------------------
52+
# VTK pipeline
53+
# -----------------------------------------------------------------------------
54+
def create_vtk_pipeline():
55+
K_RANGE = [0.0, 15.6]
56+
resolution = 50
57+
58+
renderer = vtk.vtkRenderer()
59+
renderWindow = vtk.vtkRenderWindow()
60+
renderWindow.AddRenderer(renderer)
61+
renderWindow.OffScreenRenderingOn()
62+
63+
renderWindowInteractor = vtk.vtkRenderWindowInteractor()
64+
renderWindowInteractor.SetRenderWindow(renderWindow)
65+
renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
66+
67+
bikeReader = vtk.vtkXMLPolyDataReader()
68+
bikeReader.SetFileName(BIKE.path)
69+
70+
tunnelReader = vtk.vtkXMLUnstructuredGridReader()
71+
tunnelReader.SetFileName(TUNNEL.path)
72+
tunnelReader.Update()
73+
74+
lineSeed = vtk.vtkLineSource()
75+
lineSeed.SetPoint1(*P1)
76+
lineSeed.SetPoint2(*P2)
77+
lineSeed.SetResolution(resolution)
78+
lineSeed.Update()
79+
80+
lineWidget = vtk.vtkLineWidget2()
81+
lineWidgetRep = lineWidget.GetRepresentation()
82+
lineWidgetRep.SetPoint1WorldPosition(P1)
83+
lineWidgetRep.SetPoint2WorldPosition(P2)
84+
lineWidget.SetInteractor(renderWindowInteractor)
85+
86+
streamTracer = vtk.vtkStreamTracer()
87+
streamTracer.SetInputConnection(tunnelReader.GetOutputPort())
88+
streamTracer.SetSourceConnection(lineSeed.GetOutputPort())
89+
streamTracer.SetIntegrationDirectionToForward()
90+
streamTracer.SetIntegratorTypeToRungeKutta45()
91+
streamTracer.SetMaximumPropagation(3)
92+
streamTracer.SetIntegrationStepUnit(2)
93+
streamTracer.SetInitialIntegrationStep(0.2)
94+
streamTracer.SetMinimumIntegrationStep(0.01)
95+
streamTracer.SetMaximumIntegrationStep(0.5)
96+
streamTracer.SetMaximumError(0.000001)
97+
streamTracer.SetMaximumNumberOfSteps(2000)
98+
streamTracer.SetTerminalSpeed(0.00000000001)
99+
100+
tubeFilter = vtk.vtkTubeFilter()
101+
tubeFilter.SetInputConnection(streamTracer.GetOutputPort())
102+
tubeFilter.SetRadius(0.01)
103+
tubeFilter.SetNumberOfSides(6)
104+
tubeFilter.CappingOn()
105+
tubeFilter.Update()
106+
107+
bike_mapper = vtk.vtkPolyDataMapper()
108+
bike_actor = vtk.vtkActor()
109+
bike_mapper.SetInputConnection(bikeReader.GetOutputPort())
110+
bike_actor.SetMapper(bike_mapper)
111+
renderer.AddActor(bike_actor)
112+
113+
stream_mapper = vtk.vtkPolyDataMapper()
114+
stream_actor = vtk.vtkActor()
115+
stream_mapper.SetInputConnection(tubeFilter.GetOutputPort())
116+
stream_actor.SetMapper(stream_mapper)
117+
renderer.AddActor(stream_actor)
118+
119+
lut = vtk.vtkLookupTable()
120+
lut.SetHueRange(0.7, 0)
121+
lut.SetSaturationRange(1.0, 0)
122+
lut.SetValueRange(0.5, 1.0)
123+
124+
stream_mapper.SetLookupTable(lut)
125+
stream_mapper.SetColorModeToMapScalars()
126+
stream_mapper.SetScalarModeToUsePointData()
127+
stream_mapper.SetArrayName("k")
128+
stream_mapper.SetScalarRange(K_RANGE)
129+
130+
renderWindow.Render()
131+
renderer.ResetCamera()
132+
renderer.SetBackground(0.4, 0.4, 0.4)
133+
134+
lineWidget.On()
135+
136+
return renderWindow, lineSeed, lineWidget, bike_actor
137+
138+
139+
# -----------------------------------------------------------------------------
140+
# Trame app
141+
# -----------------------------------------------------------------------------
142+
class App(TrameApp):
143+
def __init__(self, server=None):
144+
super().__init__(server)
145+
146+
# VTK setup
147+
self.rw, self.seed, self.widget, self.bike_actor = create_vtk_pipeline()
148+
149+
# GUI setup
150+
self._build_ui()
151+
152+
# Initial state
153+
self.state.update(INITIAL_STATE)
154+
155+
@change("bike_opacity")
156+
def _on_opacity(self, bike_opacity, **_):
157+
self.bike_actor.property.opacity = bike_opacity
158+
self.ctrl.view_update()
159+
160+
@change("line_widget")
161+
def _on_widget_update(self, line_widget, **_):
162+
if line_widget is None:
163+
return
164+
165+
p1 = line_widget.get("p1")
166+
p2 = line_widget.get("p2")
167+
168+
self.seed.SetPoint1(p1)
169+
self.seed.SetPoint2(p2)
170+
171+
if line_widget.get("widget_update", False):
172+
self.widget.representation.point1_world_position = p1
173+
self.widget.representation.point2_world_position = p2
174+
175+
self.ctrl.view_update()
176+
177+
def _build_ui(self):
178+
with SinglePageWithDrawerLayout(self.server, full_height=True) as layout:
179+
self.ui = layout # for jupyter integration
180+
181+
# Toolbar
182+
with layout.toolbar as toolbar:
183+
toolbar.density = "compact"
184+
layout.title.set_text("Bike CFD")
185+
v3.VSpacer()
186+
v3.VSlider(
187+
v_model=("bike_opacity", 1),
188+
min=0,
189+
max=1,
190+
step=0.05,
191+
density="compact",
192+
hide_details=True,
193+
)
194+
v3.VBtn(icon="mdi-crop-free", click=self.ctrl.view_reset_camera)
195+
196+
# Drawer
197+
with layout.drawer:
198+
tw.LineSeed(
199+
image=to_url(IMAGE.path),
200+
point_1=("line_widget.p1",),
201+
point_2=("line_widget.p2",),
202+
bounds=("[-0.399, 1.80, -1.12, 1.11, -0.43, 1.79]",),
203+
update_seed="line_widget = { ...$event, widget_update: 1 }",
204+
n_sliders=2,
205+
)
206+
207+
# Content
208+
with layout.content:
209+
with vtklocal.LocalView(self.rw, throttle_rate=20) as view:
210+
self.ctrl.view_update = view.update_throttle
211+
self.ctrl.view_reset_camera = view.reset_camera
212+
213+
# Bind state to 3D widget interaction event
214+
widget_id = view.register_vtk_object(self.widget)
215+
view.listeners = (
216+
"wasm_listeners",
217+
{
218+
widget_id: {
219+
"InteractionEvent": {
220+
"line_widget": {
221+
"p1": (
222+
widget_id,
223+
"WidgetRepresentation",
224+
"Point1WorldPosition",
225+
),
226+
"p2": (
227+
widget_id,
228+
"WidgetRepresentation",
229+
"Point2WorldPosition",
230+
),
231+
}
232+
},
233+
},
234+
},
235+
)
236+
237+
238+
def main():
239+
app = App()
240+
app.server.start()
241+
242+
243+
if __name__ == "__main__":
244+
main()
1.05 MB
Loading
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
trame>=3.9
2+
trame-vuetify
3+
trame-components
4+
trame-vtklocal>=0.9
5+
vtk>=9.4.2

0 commit comments

Comments
 (0)