{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "pn.extension('vtk')" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "The ``VTK`` pane renders a VTK scene inside a panel, making it possible to interact with complex geometries in 3D.\n", "It allows keeping state synchronized between the ``vtkRenderWindow`` defined on the python side and the one displayed in the pane through vtk-js.\n", "Here Python acts as a server, sending information about the scene to the client.\n", "The synchronization is done in only one direction: Python => JavaScript. Modifications done on the JavaScript side are not reflected back to the Python ``vtkRenderWindow``.\n", "\n", "#### Parameters:\n", "\n", "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n", "\n", "* **``axes``** (dict): A dictionary with the parameters of the axes to construct in the 3d view.\n", " Must contain at least ``xticker``, ``yticker`` and ``zticker``.\n", " - ``[x|y|z]ticker`` is a dictionary which contains:\n", " - ``ticks`` (array of numbers) - required. Positions in the scene coordinates\n", " of the corresponding axe ticks\n", " - ``labels`` (array of strings) - optional. Label displayed respectively to\n", " the `ticks` positions.\n", "\n", " If `labels` are not defined they are inferred from the `ticks` array.\n", " - ``digits``: number of decimal digits when `ticks` are converted to `labels`.\n", " - ``fontsize``: size in pts of the ticks labels.\n", " - ``show_grid``: boolean. If true (default) the axes grid is visible.\n", " - ``grid_opacity``: float between 0-1. Defines the grid opacity.\n", " - ``axes_opacity``: float between 0-1. Defines the axes lines opacity.\n", "* **``camera``** (dict): A dictionary reflecting the current state of the VTK camera\n", "* **``enable_keybindings``** (bool): A boolean to activate/deactivate keybindings. Bound keys are:\n", " - s: set representation of all actors to *surface*\n", " - w: set representation of all actors to *wireframe*\n", " - v: set representation of all actors to *vertex*\n", " - r: center the actors and move the camera so that all actors are visible\n", " \n", " The mouse must be over the pane to work\n", "
**Warning**: These keybindings may not work as expected in a notebook context, if they interact with already bound keys\n", "* **``orientation_widget``** (bool): A boolean to activate/deactivate the orientation widget in the 3D pane.\n", "* **``ìnteractive_orientation_widget``** (bool): If True the orientation widget is clickable and allows to rotate the scene in one of the orthographic projections.\n", "
**Warning**: if set to True, synchronization capabilities of `VTKRenderWindowSynchronized` panes could not work.\n", "* **``object``** (object): Must be a ``vtkRenderWindow`` instance.\n", "\n", "#### Properties:\n", "\n", "* **``actors``** : return the list of the vtkActors in the scene\n", "\n", "* **``vtk_camera``** : return the vtkCamera of the renderer held by the panel\n", "\n", "#### Methods:\n", "\n", "Several helpers to work with the VTK scene are exposed:\n", "\n", "* **``set_background(r: float, g: float, b: float)``** : Set the color of the scene background as an RGB color, where `r`, `g`, and `b` are floats in the range (0, 1)\n", "\n", "* **``synchronize()``** : Synchronize the Python-side ``vtkRenderWindow`` object state with JavaScript\n", "\n", "* **``unlink_camera()``** : Create a new vtkCamera object, allowing the panel to have its own camera.\n", "\n", "* **``link_camera(other: VTK)``** : Set both panels to share the same camera; any change in either panel's camera will be applied to the other\n", "\n", "**Warning** after using any of these methods to modify the state of vtkRenderWindow, the `synchronize()` method must be called for those changes to affect the display\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Rendering VTK objects\n", "\n", "Compared to working with ``VTK`` directly, there are some differences when using it in Panel. As rendering of the objects and interactions with the view are handled by the VTK panel, we do not need to make a call to the `Render` method of the `vtkRenderWindow` (which would pop up the classical VTK window), nor do we need to specify a `vtkRenderWindowInteractor`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import vtk\n", "from vtk.util.colors import tomato\n", "\n", "# This creates a polygonal cylinder model with eight circumferential\n", "# facets.\n", "cylinder = vtk.vtkCylinderSource()\n", "cylinder.SetResolution(8)\n", "\n", "# The mapper is responsible for pushing the geometry into the graphics\n", "# library. It may also do color mapping, if scalars or other\n", "# attributes are defined.\n", "cylinderMapper = vtk.vtkPolyDataMapper()\n", "cylinderMapper.SetInputConnection(cylinder.GetOutputPort())\n", "\n", "# The actor is a grouping mechanism: besides the geometry (mapper), it\n", "# also has a property, transformation matrix, and/or texture map.\n", "# Here we set its color and rotate it -22.5 degrees.\n", "cylinderActor = vtk.vtkActor()\n", "cylinderActor.SetMapper(cylinderMapper)\n", "cylinderActor.GetProperty().SetColor(tomato)\n", "# We must set ScalarVisibilty to 0 to use tomato Color\n", "cylinderMapper.SetScalarVisibility(0)\n", "cylinderActor.RotateX(30.0)\n", "cylinderActor.RotateY(-45.0)\n", "\n", "# Create the graphics structure. The renderer renders into the render\n", "# window.\n", "ren = vtk.vtkRenderer()\n", "renWin = vtk.vtkRenderWindow()\n", "renWin.AddRenderer(ren)\n", "\n", "# Add the actors to the renderer, set the background and size\n", "ren.AddActor(cylinderActor)\n", "ren.SetBackground(0.1, 0.2, 0.4)\n", "\n", "geom_pane = pn.pane.VTK(renWin, width=500, height=500)\n", "\n", "geom_pane" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also add additional actors to the plot, then call the `synchronize` method to update the panel:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sphere = vtk.vtkSphereSource()\n", "\n", "sphereMapper = vtk.vtkPolyDataMapper()\n", "sphereMapper.SetInputConnection(sphere.GetOutputPort())\n", "\n", "sphereActor = vtk.vtkActor()\n", "sphereActor.SetMapper(sphereMapper)\n", "sphereActor.GetProperty().SetColor(tomato)\n", "sphereMapper.SetScalarVisibility(0)\n", "sphereActor.SetPosition(0.5, 0.5, 0.5)\n", "\n", "ren.AddActor(sphereActor)\n", "geom_pane.reset_camera()\n", "geom_pane.synchronize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Working with [`PyVista`](https://docs.pyvista.org/)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Most of these examples use the [PyVista](https://docs.pyvista.org/) library as a convenient interface to VTK.\n", "\n", "Though these examples can generally be rewritten to depend only on VTK itself, `pyvista` supports a Pythonic and concise syntax for the main features needed to work with VTK objects.\n", "\n", "For instance, the VTK example above can be rewritten using PyVista as follows:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " - Link PyVista and Panel:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pyvista as pv\n", "\n", "plotter = pv.Plotter() # we define a pyvista plotter\n", "plotter.background_color = (0.1, 0.2, 0.4)\n", "# we create a `VTK` panel around the render window\n", "geo_pan_pv = pn.panel(plotter.ren_win, width=500, height=500) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " - Create and add some objects to the PyVista plotter, displaying them using the VTK panel" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pvcylinder = pv.Cylinder(resolution=8, direction=(0,1,0))\n", "cylinder_actor = plotter.add_mesh(pvcylinder, color=tomato, smooth_shading=True)\n", "cylinder_actor.RotateX(30.0)\n", "cylinder_actor.RotateY(-45.0)\n", "sphere_actor = plotter.add_mesh(pv.Sphere(\n", " theta_resolution=8, phi_resolution=8,\n", " center=(0.5, 0.5, 0.5)),color=tomato, smooth_shading=True\n", ")\n", "# we can link camera of both panes\n", "geom_pane.jslink(geo_pan_pv, camera='camera', bidirectional=True)\n", "pn.Row(geom_pane, geo_pan_pv)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Export the scene\n", "\n", "- The scene can be exported and the file generated can be loaded by the official vtk-js scene importer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "filename = geo_pan_pv.export_scene(filename='geo_example')\n", "print(\"The file is exported in :\", os.path.join(os.getcwd(), filename))\n", "pn.pane.HTML('', sizing_mode='stretch_width')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Advanced usage and interactivity\n", "\n", "This examples will show :\n", " - where information about `scalars` used to color 3D objects can be found in the scene\n", " - utilization of the `orientation widget`\n", " - how to add `axes` in the scene\n", " - adding a `player` widget to create an animation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scene creation\n", "\n", "First we create a scene with a nice spline inspired by this examples : [Creating a Spline](https://docs.pyvista.org/examples/00-load/create-spline.html#creating-a-spline)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "def make_points():\n", " \"\"\"Helper to make XYZ points\"\"\"\n", " theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)\n", " z = np.linspace(-2, 2, 100)\n", " r = z**2 + 1\n", " x = r * np.sin(theta)\n", " y = r * np.cos(theta)\n", " return np.column_stack((x, y, z))\n", "\n", "def lines_from_points(points):\n", " \"\"\"Given an array of points, make a line set\"\"\"\n", " poly = pv.PolyData()\n", " poly.points = points\n", " cells = np.full((len(points)-1, 3), 2, dtype=np.int32)\n", " cells[:, 1] = np.arange(0, len(points)-1, dtype=np.int32)\n", " cells[:, 2] = np.arange(1, len(points), dtype=np.int32)\n", " poly.lines = cells\n", " return poly\n", "\n", "points = make_points()\n", "line = lines_from_points(points)\n", "line[\"scalars\"] = np.arange(line.n_points) # By default pyvista use viridis colormap\n", "tube = line.tube(radius=0.1) #=> the object we will represent in the scene\n", "\n", "\n", "pl = pv.Plotter()\n", "pl.camera_position = [(4.440892098500626e-16, -21.75168228149414, 4.258553981781006),\n", " (4.440892098500626e-16, 0.8581731039809382, 0),\n", " (0, 0.1850949078798294, 0.982720673084259)]\n", "\n", "pl.add_mesh(tube, smooth_shading=True)\n", "spline_pan = pn.panel(pl.ren_win, width=500, orientation_widget=True)\n", "spline_pan" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By clicking on the three dots, a colorbar can be displayed\n", "\n", "The orientation widget on the bottom right of the panel is clickable too, allowing you to orient the camera in a specific direction." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding axes to the VTK scene\n", "\n", "Using the `axes` parameter, it is possible to display axes in the 3D view.\n", "\n", "The `Axes` object (like `orientation widget` and `colorbar`) is a client-side object instantiated only on the JavaScript side, and is not synchronized back to the Python `vtkRenderWindow`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "axes = dict(\n", " origin = [-5, 5, -2],\n", " xticker = {'ticks': np.linspace(-5,5,5)},\n", " yticker = {'ticks': np.linspace(-5,5,5)},\n", " zticker = {'ticks': np.linspace(-2,2,5), 'labels': [''] + [str(int(item)) for item in np.linspace(-2,2,5)[1:]]},\n", " fontsize = 12,\n", " digits = 1,\n", " grid_opacity = 0.5,\n", " show_grid=True\n", ")\n", "spline_pan.axes = axes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Working with Volumes\n", "\n", "All previous panes use geometric examples, but `VTK` can work with both geometric and volume objects:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pyvista.examples as examples\n", "head = examples.download_head()\n", "\n", "plotter = pv.Plotter()\n", "plotter.camera_position = [\n", " [565, -340, 219],\n", " [47, 112, 52],\n", " [0, 0, 1],\n", "]\n", "plotter.add_volume(head)\n", "\n", "volume_pan = pn.panel(plotter.ren_win, width=500, orientation_widget=True)\n", "volume_pan" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As for other examples, we can add easily new objects in the pane.\n", "\n", "For example, we can create 3 orthogonal slices and add them to the scene:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "actor, mapper = plotter.add_mesh(head.slice_orthogonal(x=100, y=120, z=30))\n", "volume_pan.synchronize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create an animation\n", "\n", "We will reuse the panel used for the spline.\n", "\n", "For this animation, we will rotate the actor and roll the scalars on the spline tube:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pan_clone = spline_pan.clone() # we clone the panel to animate only this panel\n", "pan_clone.unlink_camera() # we don't want to share the camera with the previous panel\n", "player = pn.widgets.Player(name='Player', start=0, end=100, value=0, loop_policy='reflect', interval=100)\n", "scalars = tube[\"scalars\"]\n", "\n", "def animate(value):\n", " tube[\"scalars\"] = np.roll(scalars, 200*value)\n", " pan_clone.actors[0].SetOrientation(0, 0, -20*value)\n", " pan_clone.synchronize()\n", "\n", "pn.Column(pan_clone, player, pn.bind(animate, player)).servable()" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }