{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "import pyvista as pv\n",
    "from pyvista import examples\n",
    "pn.extension('vtk', sizing_mode=\"stretch_width\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For this example we use the pyvista library to load a dataset and generate easily a VTK scene"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = examples.download_st_helens().warp_by_scalar()\n",
    "\n",
    "# default camera position\n",
    "cpos = [(567000.9232163235, 5119147.423216323, 6460.423216322832),\n",
    " (562835.0, 5114981.5, 2294.5),\n",
    " (-0.4082482904638299, -0.40824829046381844, 0.8164965809277649)]\n",
    "\n",
    "# pyvista plotter\n",
    "pl = pv.Plotter(notebook=True);\n",
    "actor = pl.add_mesh(m, smooth_shading=True, lighting=True)\n",
    "pl.camera_position = cpos #set camera position\n",
    "\n",
    "# save initial camera properties\n",
    "renderer = list(pl.ren_win.GetRenderers())[0]\n",
    "initial_camera = renderer.GetActiveCamera()\n",
    "initial_camera_pos = {\"focalPoint\": initial_camera.GetFocalPoint(),\n",
    "                      \"position\": initial_camera.GetPosition(),\n",
    "                      \"viewUp\": initial_camera.GetViewUp()}\n",
    "\n",
    "# Panel creation using the VTK Scene created by the plotter pyvista\n",
    "orientation_widget = True\n",
    "enable_keybindings = True\n",
    "vtkpan = pn.panel(pl.ren_win, sizing_mode='stretch_both', orientation_widget=orientation_widget,\n",
    "                  enable_keybindings=enable_keybindings, height=600)\n",
    "vtkpan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "WidgetBox with colorbars and actor selection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Creation of a mapping between Custom name and the VTK object reference\n",
    "actor_ref = [\"None\", actor.__this__]\n",
    "actor_names = [\"None\", 'St Helen']\n",
    "actor_opts = {k:v for k,v in zip(actor_names, actor_ref)}\n",
    "\n",
    "options = {}\n",
    "actor_selection = pn.widgets.Select(value=None, options = actor_opts , name=\"Actor Selection\")\n",
    "actor_selection"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "WidgetBoxes with general parameters of the vtk Scene (Widgets, Background, Lights,...)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Scene Layout\n",
    "scene_props = pn.WidgetBox()\n",
    "bind_and_orient = pn.widgets.CheckBoxGroup(value=['Orientation Widget', 'Key Bindings'], options=['Orientation Widget', 'Key Bindings']) #initialisation => coherence with panel params\n",
    "reset_camera = pn.widgets.Button(name='Reset Camera')\n",
    "background_color = pn.widgets.ColorPicker(value=''.join(['#'] + ['{:02x}'.format(int(v*255)) for v in pl.background_color]),\n",
    "                                          name='Background Color')\n",
    "[scene_props.append(w) for w in [bind_and_orient, reset_camera, background_color]]\n",
    "\n",
    "# Light properties\n",
    "light_props = pn.WidgetBox()\n",
    "light_box_title = pn.widgets.StaticText(value='Light properties')\n",
    "light_type = pn.widgets.Select(value='HeadLight', options=['HeadLight','SceneLight','CameraLight'])\n",
    "light_intensity = pn.widgets.FloatSlider(start=0, end=1, value=1, name=\"Intensity\")\n",
    "\n",
    "\n",
    "[light_props.append(w) for w in [light_box_title, light_type, light_intensity]]\n",
    "pn.Row(scene_props, light_props)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "WidgetBox with properties of the Actors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#layout actor props\n",
    "actor_props = pn.WidgetBox()\n",
    "opacity = pn.widgets.FloatSlider(value=1, start=0, end=1, name='Opacity', disabled=True)\n",
    "lighting = pn.widgets.Toggle(value=True, name='Lighting', disabled=True)\n",
    "interpolation = pn.widgets.Select(value='Phong', options=['Flat','Phong'], name='Interpolation', disabled=True)\n",
    "edges = pn.widgets.Toggle(value=False, name='Show Edges', disabled=True)\n",
    "edges_color = pn.widgets.ColorPicker(value='#ffffff', name='Edges Color', disabled=True)\n",
    "representation = pn.widgets.Select(value='Surface', options=['Points','Wireframe','Surface'], name='Representation', disabled=True)\n",
    "frontface_culling = pn.widgets.Toggle(value=False, name='Frontface Culling', disabled=True)\n",
    "backface_culling = pn.widgets.Toggle(value=False, name='Backface Culling', disabled=True)\n",
    "ambient = pn.widgets.FloatSlider(value=0, start=-1, end=1, name='Ambient', disabled=True)\n",
    "diffuse = pn.widgets.FloatSlider(value=1, start=0, end=2, name='Diffuse', disabled=True)\n",
    "specular = pn.widgets.FloatSlider(value=0, start=0, end=10, name='Specular', disabled=True)\n",
    "specular_power = pn.widgets.FloatSlider(value=100, start=0, end=100, name='Specular Power', disabled=True)\n",
    "[actor_props.append(w) for w in [opacity, lighting, interpolation, edges, edges_color,\n",
    "                                 representation,frontface_culling,backface_culling, ambient,\n",
    "                                 diffuse, specular, specular_power]]\n",
    "actor_props"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Linking all widgets together using jslinks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Linking\n",
    "light_type.jslink(vtkpan, code={'value':\"\"\"\n",
    "const light = target.renderer_el.getRenderer().getLights()[0]\n",
    "if (source.value == 'HeadLight')\n",
    "    light.setLightTypeToHeadLight()\n",
    "else if (source.value == 'CameraLight')\n",
    "    light.setLightTypeToCameraLight()\n",
    "else if (source.value == 'SceneLight')\n",
    "    light.setLightTypeToSceneLight()\n",
    "target.renderer_el.getRenderWindow().render()\n",
    "\"\"\"})\n",
    "\n",
    "light_intensity.jslink(vtkpan, code={'value':\"\"\"\n",
    "const light = target.renderer_el.getRenderer().getLights()[0]\n",
    "light.setIntensity(source.value)\n",
    "target.renderer_el.getRenderWindow().render()\n",
    "\"\"\"})\n",
    "\n",
    "\n",
    "bind_and_orient.jslink(vtkpan, code = {'active':\"\"\"\n",
    "target.orientation_widget = source.active.includes(0)\n",
    "target.enable_keybindings = source.active.includes(1)\n",
    "\"\"\"})\n",
    "\n",
    "reset_camera.js_on_click(args={'target': vtkpan, 'initial_camera':initial_camera_pos},\n",
    "                         code=\"target.camera = initial_camera\");\n",
    "\n",
    "background_color.jslink(vtkpan, code={'value':\"\"\"\n",
    "const hextoarr = (color) => {return [parseInt(color.slice(1,3),16)/255, parseInt(color.slice(3,5),16)/255, parseInt(color.slice(5,7),16)/255]}\n",
    "target.renderer_el.getRenderer().setBackground(hextoarr(source.color))\n",
    "target.renderer_el.getRenderWindow().render()\n",
    "\"\"\"});\n",
    "\n",
    "opacity.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setOpacity(source.value)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "lighting.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setLighting(source.active)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "edges.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setEdgeVisibility(source.active)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "interpolation.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    if(source.value==\"Flat\"){\n",
    "        actor.getProperty().setInterpolationToFlat()\n",
    "    }else{\n",
    "        actor.getProperty().setInterpolationToPhong()\n",
    "    }\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "edges_color.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const hextoarr = (color) => {return [parseInt(color.slice(1,3),16)/255, parseInt(color.slice(3,5),16)/255, parseInt(color.slice(5,7),16)/255]}\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setEdgeColor(hextoarr(source.color))\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "representation.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    if(source.value==\"Points\"){\n",
    "        actor.getProperty().setRepresentationToPoints()\n",
    "    }else if(source.value==\"Wireframe\"){\n",
    "        actor.getProperty().setRepresentationToWireframe()\n",
    "    }else if(source.value==\"Surface\"){\n",
    "        actor.getProperty().setRepresentationToSurface()\n",
    "    }\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "frontface_culling.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setFrontfaceCulling(source.active)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "backface_culling.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setBackfaceCulling(source.active)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "ambient.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setAmbient(source.value)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "diffuse.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setDiffuse(source.value)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "specular.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setSpecular(source.value)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "specular_power.jscallback(args={\"target\":vtkpan, \"actor_selection\":actor_selection}, value=\"\"\"\n",
    "if (actor_selection.value!=\"None\"){\n",
    "    const actor = target.getActors(actor_selection.value)[0]\n",
    "    actor.getProperty().setSpecularPower(source.value)\n",
    "    target.renderer_el.getRenderWindow().render()\n",
    "}\n",
    "\"\"\")\n",
    "\n",
    "actor_selection.jslink(target=vtkpan, code = {'value' : \"\"\"\n",
    "if (source.value!=\"None\"){\n",
    "    const actor = target.getActors(source.value)[0]\n",
    "    target.outline.setInputData(actor.getMapper().getInputData())\n",
    "    target.renderer_el.getRenderer().addActor(target.outline_actor)\n",
    "    \n",
    "    //synchronize actor props and widgets values\n",
    "    const properties = actor.getProperty()\n",
    "    opacity.setv({value: properties.getOpacity()}, {silent: true})\n",
    "    lighting.setv({active: !!properties.getLighting()}, {silent: true})\n",
    "    edges.active = !!properties.getEdgeVisibility()\n",
    "    const actor_color = \"#\" + properties.getEdgeColor().map((c) => (\"0\" + Math.round(255*c).toString(16,2)).slice(-2)).join('')\n",
    "    edges_color.setv({color: actor_color}, {silent: true})\n",
    "    const interp_string = properties.getInterpolationAsString()\n",
    "    interpolation.setv({value: interp_string[0] + interp_string.slice(1).toLocaleLowerCase()}, {silent: true})\n",
    "    const repr_string = properties.getRepresentationAsString()\n",
    "    representation.setv({value: repr_string[0] + repr_string.slice(1).toLocaleLowerCase()}, {silent: true})\n",
    "    frontface_culling.setv({active: !!properties.getFrontfaceCulling()}, {silent: true})\n",
    "    backface_culling.setv({active: !!properties.getBackfaceCulling()}, {silent: true})\n",
    "    ambient.setv({value: properties.getAmbient()}, {silent: true})\n",
    "    diffuse.setv({value: properties.getDiffuse()}, {silent: true})\n",
    "    specular.setv({value: properties.getSpecular()}, {silent: true})\n",
    "    specular_power.setv({value: properties.getSpecularPower()}, {silent: true})\n",
    "    //enable widgets modifications\n",
    "    opacity.disabled = false\n",
    "    lighting.disabled = false\n",
    "    interpolation.disabled = false\n",
    "    edges.disabled = false\n",
    "    edges_color.disabled = false\n",
    "    representation.disabled = false\n",
    "    frontface_culling.disabled = false\n",
    "    backface_culling.disabled = false\n",
    "    ambient.disabled = false\n",
    "    diffuse.disabled = false\n",
    "    specular.disabled = false\n",
    "    specular_power.disabled = false\n",
    "} else {\n",
    "    target.renderer_el.getRenderer().removeActor(target.outline_actor)\n",
    "    opacity.disabled = true\n",
    "    lighting.disabled = true\n",
    "    interpolation.disabled = true\n",
    "    edges.disabled = true\n",
    "    edges_color.disabled = true\n",
    "    representation.disabled = true\n",
    "    frontface_culling.disabled = true\n",
    "    backface_culling.disabled = true\n",
    "    ambient.disabled = true\n",
    "    diffuse.disabled = true\n",
    "    specular.disabled = true\n",
    "    specular_power.disabled = true\n",
    "}\n",
    "target.renderer_el.getRenderWindow().render()\n",
    "\n",
    "\"\"\"}, args={\"opacity\":opacity, \"lighting\":lighting, \"interpolation\": interpolation, \"edges\": edges, \"edges_color\": edges_color,\n",
    "            \"representation\": representation, \"frontface_culling\": frontface_culling, \"backface_culling\": backface_culling,\n",
    "            \"ambient\": ambient, \"diffuse\": diffuse, \"specular\": specular, \"specular_power\": specular_power});\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Display all together"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "settings = pn.Column(actor_selection,pn.Tabs(('Scene controller', pn.Column(scene_props, light_props)), ('Actor properties',actor_props)))\n",
    "pn.Row(vtkpan, settings)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## App\n",
    "\n",
    "Lets wrap it into nice template that can be served via `panel serve VTKInteractive.ipynb`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "description=\"This example demonstrates the use of **VTK and pyvista** to display a *scene*\"\n",
    "pn.template.FastListTemplate(site=\"Panel\", title=\"VTK Interactive\", sidebar=[settings], main=[description, vtkpan]).servable();"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
