{
  "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
}