{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# VTK + WASM\n\nUse WASM local rendering. This requires VTK version greater than 9.3:\n\n``` bash\npip install --extra-index-url https://wheels.vtk.org vtk>=9.3\n```\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Required for vtk factory\nfrom trame.app import get_server\nfrom trame.decorators import TrameApp, change\nfrom trame.ui.vuetify3 import SinglePageLayout\nfrom trame.widgets import vuetify3 as vuetify\nfrom trame.widgets.vtk import VtkRemoteView\nfrom trame_vtklocal.widgets import vtklocal\nfrom vtkmodules.vtkCommonColor import vtkNamedColors\nfrom vtkmodules.vtkFiltersCore import vtkElevationFilter, vtkGlyph3D\nfrom vtkmodules.vtkFiltersSources import vtkConeSource, vtkCubeSource, vtkSphereSource\nfrom vtkmodules.vtkImagingCore import vtkRTAnalyticSource\nfrom vtkmodules.vtkImagingGeneral import vtkImageGradient\nfrom vtkmodules.vtkRenderingCore import (\n vtkActor,\n vtkPolyDataMapper,\n vtkRenderer,\n vtkRenderWindow,\n vtkRenderWindowInteractor,\n)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def setup_vtk(): # noqa: PLR0915\n colors = vtkNamedColors()\n\n # The Wavelet Source is nice for generating a test vtkImageData set\n rt = vtkRTAnalyticSource()\n rt.SetWholeExtent(-2, 2, -2, 2, 0, 0)\n\n # Take the gradient of the only scalar 'RTData' to get a vector attribute\n grad = vtkImageGradient()\n grad.SetDimensionality(3)\n grad.SetInputConnection(rt.GetOutputPort())\n\n # Elevation just to generate another scalar attribute that varies nicely over the data range\n elev = vtkElevationFilter()\n # Elevation values will range from 0 to 1 between the Low and High Points\n elev.SetLowPoint(-2, -2, 0)\n elev.SetHighPoint(2, 2, 0)\n elev.SetInputConnection(grad.GetOutputPort())\n\n # Create simple PolyData for glyph table\n cs = vtkCubeSource()\n cs.SetXLength(0.5)\n cs.SetYLength(1)\n cs.SetZLength(2)\n ss = vtkSphereSource()\n ss.SetRadius(0.25)\n cs2 = vtkConeSource()\n cs2.SetRadius(0.25)\n cs2.SetHeight(0.5)\n\n # Set up the glyph filter\n glyph = vtkGlyph3D()\n glyph.SetInputConnection(elev.GetOutputPort())\n\n # Here is where we build the glyph table\n # that will be indexed into according to the IndexMode\n glyph.SetSourceConnection(0, cs.GetOutputPort())\n glyph.SetSourceConnection(1, ss.GetOutputPort())\n glyph.SetSourceConnection(2, cs2.GetOutputPort())\n\n glyph.ScalingOn()\n glyph.SetScaleModeToScaleByScalar()\n glyph.SetVectorModeToUseVector()\n glyph.OrientOn()\n glyph.SetScaleFactor(1) # Overall scaling factor\n glyph.SetRange(0, 1) # Default is (0,1)\n\n # Tell it to index into the glyph table according to scalars\n glyph.SetIndexModeToScalar()\n\n # Tell glyph which attribute arrays to use for what\n glyph.SetInputArrayToProcess(0, 0, 0, 0, \"Elevation\") # scalars\n glyph.SetInputArrayToProcess(1, 0, 0, 0, \"RTDataGradient\") # vectors\n\n coloring_by = \"Elevation\"\n mapper = vtkPolyDataMapper()\n mapper.SetInputConnection(glyph.GetOutputPort())\n mapper.SetScalarModeToUsePointFieldData()\n mapper.SetColorModeToMapScalars()\n mapper.ScalarVisibilityOn()\n\n # GetRange() call doesn't work because attributes weren't copied to glyphs\n # as they should have been...\n # mapper.SetScalarRange(glyph.GetOutputDataObject(0).GetPointData().GetArray(coloring_by).GetRange())\n\n mapper.SelectColorArray(coloring_by)\n actor = vtkActor()\n actor.SetMapper(mapper)\n\n ren = vtkRenderer()\n ren.AddActor(actor)\n ren.SetBackground(colors.GetColor3d(\"DarkGray\"))\n\n renWin = vtkRenderWindow() # noqa: N806\n renWin.AddRenderer(ren)\n\n renderWindowInteractor = vtkRenderWindowInteractor() # noqa: N806\n renderWindowInteractor.SetRenderWindow(renWin)\n renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()\n\n ren.ResetCamera()\n\n return renWin, ren, cs2, ss"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"@TrameApp()\nclass App:\n def __init__(self, server=None) -> None:\n self.server = get_server(server, client_type=\"vue3\")\n self.render_window, self.renderer, self.cone, self.sphere = setup_vtk()\n self.view_local = None\n self.view_remote = None\n self.ui = self._build_ui()\n\n @property\n def ctrl(self):\n return self.server.controller\n\n @change(\"resolution\")\n def on_resolution_change(self, resolution, **kwargs) -> None:\n self.cone.SetResolution(int(resolution))\n self.sphere.SetStartTheta(int(resolution) * 6)\n self.view_remote.update()\n self.view_local.update()\n\n def reset_camera(self) -> None:\n self.renderer.ResetCamera()\n self.view_local.update()\n self.view_remote.update()\n\n def _build_ui(self):\n with SinglePageLayout(self.server) as layout:\n layout.icon.click = self.reset_camera\n with layout.toolbar:\n vuetify.VSpacer()\n vuetify.VSlider(\n v_model=(\"resolution\", 6),\n min=3,\n max=60,\n step=1,\n dense=True,\n hide_details=True,\n )\n vuetify.VBtn(\"Update\", click=self.ctrl.view_update)\n\n with (\n layout.content,\n vuetify.VContainer(\n fluid=True,\n classes=\"pa-0 fill-height\",\n ),\n ):\n with vuetify.VContainer(\n fluid=True, classes=\"pa-0 fill-height\", style=\"width: 50%;\"\n ):\n self.view_local = vtklocal.LocalView(\n self.render_window,\n eager_sync=True,\n )\n self.ctrl.view_update = self.view_local.update\n with vuetify.VContainer(\n fluid=True, classes=\"pa-0 fill-height\", style=\"width: 50%;\"\n ):\n self.view_remote = VtkRemoteView(self.render_window, interactive_ratio=1)\n\n # hide footer\n layout.footer.hide()\n\n return layout"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"app = App(\"wasm\")\nawait app.ui.ready"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Make sure to give room for the download of WASM bundle Only needed at\nfirst execution\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import asyncio\n\nawait asyncio.sleep(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"app.ui"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 0
}