prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Update volume_plane.py to allow timer to be stopped
  I noticed the volume_plane.py has keyboard handling for moving the plane through the volume, but it doesn't allow you to stop the animation so these events don't actually mean much.
  
  @alisterburt do you have time to fix this? Tradition is to use the spacebar (`" "`) to toggle the timer start/stop
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `examples/scene/volume_plane.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 # -----------------------------------------------------------------------------
  3 # Copyright (c) Vispy Development Team. All Rights Reserved.
  4 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
  5 # -----------------------------------------------------------------------------
  6 # vispy: gallery 10:200:5
  7 """
  8 Rendering Planes through 3D Data
  9 ================================
  10 
  11 Controls:
  12 * 1  - toggle between volume rendering methods
  13 * 2 -  toggle between volume rendering modes ('volume', 'plane')
  14 * [] - shift plane along plane normal
  15 * {} - decrease/increase plane thickness
  16 
  17 * x/y/z/o - set plane normal along x/y/z or [1,1,1] oblique axis
  18 """
  19 import sys
  20 
  21 import numpy as np
  22 
  23 from vispy import app, scene, io
  24 from vispy.visuals.transforms import STTransform
  25 
  26 # Read volume
  27 vol = np.load(io.load_data_file('volume/stent.npz'))['arr_0']
  28 
  29 # Prepare canvas
  30 canvas = scene.SceneCanvas(keys='interactive', show=True)
  31 view = canvas.central_widget.add_view()
  32 
  33 # Create the volume visual for plane rendering
  34 plane = scene.visuals.Volume(
  35     vol,
  36     parent=view.scene,
  37     raycasting_mode='plane',
  38     method='mip',
  39     plane_thickness=3.0,
  40     plane_position=(128, 60, 64),
  41     plane_normal=(1, 0, 0),
  42 )
  43 
  44 volume = scene.visuals.Volume(
  45     vol,
  46     parent=view.scene,
  47     raycasting_mode='volume',
  48     method='mip',
  49 )
  50 volume.set_gl_state('additive')
  51 volume.opacity = 0.25
  52 
  53 # Create a camera
  54 cam = scene.cameras.TurntableCamera(
  55     parent=view.scene, fov=60.0, azimuth=-42.0, elevation=30.0
  56 )
  57 view.camera = cam
  58 
  59 # Create an XYZAxis visual
  60 axis = scene.visuals.XYZAxis(parent=view)
  61 s = STTransform(translate=(50, 50), scale=(50, 50, 50, 1))
  62 affine = s.as_matrix()
  63 axis.transform = affine
  64 
  65 
  66 def update_axis_visual():
  67     """Sync XYZAxis visual with camera angles"""
  68     axis.transform.reset()
  69 
  70     axis.transform.rotate(cam.roll, (0, 0, 1))
  71     axis.transform.rotate(cam.elevation, (1, 0, 0))
  72     axis.transform.rotate(cam.azimuth, (0, 1, 0))
  73     axis.transform.scale((50, 50, 0.001))
  74     axis.transform.translate((50., 50.))
  75 
  76     axis.update()
  77 
  78 
  79 update_axis_visual()
  80 
  81 
  82 @canvas.events.mouse_move.connect
  83 def on_mouse_move(event):
  84     if event.button == 1 and event.is_dragging:
  85         update_axis_visual()
  86 
  87 
  88 # Implement key presses
  89 @canvas.events.key_press.connect
  90 def on_key_press(event):
  91     if event.text == '1':
  92         methods = ['mip', 'average']
  93         method = methods[(methods.index(plane.method) + 1) % 2]
  94         print("Volume render method: %s" % method)
  95         plane.method = method
  96     elif event.text == '2':
  97         modes = ['volume', 'plane']
  98         if plane.raycasting_mode == modes[0]:
  99             plane.raycasting_mode = modes[1]
  100             print(modes[1])
  101         else:
  102             plane.raycasting_mode = modes[0]
  103             print(modes[0])
  104     elif event.text != '' and event.text in '{}':
  105         t = -1 if event.text == '{' else 1
  106         plane.plane_thickness += t
  107         plane.plane_thickness += t
  108         print(f"plane thickness: {plane.plane_thickness}")
  109     elif event.text != '' and event.text in '[]':
  110         shift = plane.plane_normal / np.linalg.norm(plane.plane_normal)
  111         if event.text == '[':
  112             plane.plane_position -= 2 * shift
  113         elif event.text == ']':
  114             plane.plane_position += 2 * shift
  115         print(f"plane position: {plane.plane_position}")
  116 
  117     elif event.text == 'x':
  118         plane.plane_normal = [0, 0, 1]
  119     elif event.text == 'y':
  120         plane.plane_normal = [0, 1, 0]
  121     elif event.text == 'z':
  122         plane.plane_normal = [1, 0, 0]
  123     elif event.text == 'o':
  124         plane.plane_normal = [1, 1, 1]
  125 
  126 
  127 def move_plane(event):
  128     z_pos = plane.plane_position[0]
  129     if z_pos < 32:
  130         plane.plane_position = plane.plane_position + [1, 0, 0]
  131     elif 32 < z_pos <= 220:
  132         plane.plane_position = plane.plane_position - [1, 0, 0]
  133     else:
  134         plane.plane_position = (220, 64, 64)
  135 
  136 
  137 timer = app.Timer('auto', connect=move_plane, start=True)
  138 
  139 if __name__ == '__main__':
  140     canvas.show()
  141     print(__doc__)
  142     if sys.flags.interactive == 0:
  143         plane.plane_position = (220, 64, 64)
  144         app.run()
  145
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/examples/scene/volume_plane.py b/examples/scene/volume_plane.py
  --- a/examples/scene/volume_plane.py
  +++ b/examples/scene/volume_plane.py
  @@ -13,6 +13,7 @@
   * 2 -  toggle between volume rendering modes ('volume', 'plane')
   * [] - shift plane along plane normal
   * {} - decrease/increase plane thickness
  +* Spacebar - stop/start animation
   
   * x/y/z/o - set plane normal along x/y/z or [1,1,1] oblique axis
   """
  @@ -113,7 +114,6 @@
           elif event.text == ']':
               plane.plane_position += 2 * shift
           print(f"plane position: {plane.plane_position}")
  -
       elif event.text == 'x':
           plane.plane_normal = [0, 0, 1]
       elif event.text == 'y':
  @@ -122,6 +122,11 @@
           plane.plane_normal = [1, 0, 0]
       elif event.text == 'o':
           plane.plane_normal = [1, 1, 1]
  +    elif event.text == ' ':
  +        if timer.running:
  +            timer.stop()
  +        else:
  +            timer.start()
   
   
   def move_plane(event):
  
verification_info: {"golden_diff": "diff --git a/examples/scene/volume_plane.py b/examples/scene/volume_plane.py\n--- a/examples/scene/volume_plane.py\n+++ b/examples/scene/volume_plane.py\n@@ -13,6 +13,7 @@\n * 2 -  toggle between volume rendering modes ('volume', 'plane')\n * [] - shift plane along plane normal\n * {} - decrease/increase plane thickness\n+* Spacebar - stop/start animation\n \n * x/y/z/o - set plane normal along x/y/z or [1,1,1] oblique axis\n \"\"\"\n@@ -113,7 +114,6 @@\n         elif event.text == ']':\n             plane.plane_position += 2 * shift\n         print(f\"plane position: {plane.plane_position}\")\n-\n     elif event.text == 'x':\n         plane.plane_normal = [0, 0, 1]\n     elif event.text == 'y':\n@@ -122,6 +122,11 @@\n         plane.plane_normal = [1, 0, 0]\n     elif event.text == 'o':\n         plane.plane_normal = [1, 1, 1]\n+    elif event.text == ' ':\n+        if timer.running:\n+            timer.stop()\n+        else:\n+            timer.start()\n \n \n def move_plane(event):\n", "issue": "Update volume_plane.py to allow timer to be stopped\nI noticed the volume_plane.py has keyboard handling for moving the plane through the volume, but it doesn't allow you to stop the animation so these events don't actually mean much.\r\n\r\n@alisterburt do you have time to fix this? Tradition is to use the spacebar (`\" \"`) to toggle the timer start/stop\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# -----------------------------------------------------------------------------\n# Copyright (c) Vispy Development Team. All Rights Reserved.\n# Distributed under the (new) BSD License. See LICENSE.txt for more info.\n# -----------------------------------------------------------------------------\n# vispy: gallery 10:200:5\n\"\"\"\nRendering Planes through 3D Data\n================================\n\nControls:\n* 1  - toggle between volume rendering methods\n* 2 -  toggle between volume rendering modes ('volume', 'plane')\n* [] - shift plane along plane normal\n* {} - decrease/increase plane thickness\n\n* x/y/z/o - set plane normal along x/y/z or [1,1,1] oblique axis\n\"\"\"\nimport sys\n\nimport numpy as np\n\nfrom vispy import app, scene, io\nfrom vispy.visuals.transforms import STTransform\n\n# Read volume\nvol = np.load(io.load_data_file('volume/stent.npz'))['arr_0']\n\n# Prepare canvas\ncanvas = scene.SceneCanvas(keys='interactive', show=True)\nview = canvas.central_widget.add_view()\n\n# Create the volume visual for plane rendering\nplane = scene.visuals.Volume(\n    vol,\n    parent=view.scene,\n    raycasting_mode='plane',\n    method='mip',\n    plane_thickness=3.0,\n    plane_position=(128, 60, 64),\n    plane_normal=(1, 0, 0),\n)\n\nvolume = scene.visuals.Volume(\n    vol,\n    parent=view.scene,\n    raycasting_mode='volume',\n    method='mip',\n)\nvolume.set_gl_state('additive')\nvolume.opacity = 0.25\n\n# Create a camera\ncam = scene.cameras.TurntableCamera(\n    parent=view.scene, fov=60.0, azimuth=-42.0, elevation=30.0\n)\nview.camera = cam\n\n# Create an XYZAxis visual\naxis = scene.visuals.XYZAxis(parent=view)\ns = STTransform(translate=(50, 50), scale=(50, 50, 50, 1))\naffine = s.as_matrix()\naxis.transform = affine\n\n\ndef update_axis_visual():\n    \"\"\"Sync XYZAxis visual with camera angles\"\"\"\n    axis.transform.reset()\n\n    axis.transform.rotate(cam.roll, (0, 0, 1))\n    axis.transform.rotate(cam.elevation, (1, 0, 0))\n    axis.transform.rotate(cam.azimuth, (0, 1, 0))\n    axis.transform.scale((50, 50, 0.001))\n    axis.transform.translate((50., 50.))\n\n    axis.update()\n\n\nupdate_axis_visual()\n\n\n@canvas.events.mouse_move.connect\ndef on_mouse_move(event):\n    if event.button == 1 and event.is_dragging:\n        update_axis_visual()\n\n\n# Implement key presses\n@canvas.events.key_press.connect\ndef on_key_press(event):\n    if event.text == '1':\n        methods = ['mip', 'average']\n        method = methods[(methods.index(plane.method) + 1) % 2]\n        print(\"Volume render method: %s\" % method)\n        plane.method = method\n    elif event.text == '2':\n        modes = ['volume', 'plane']\n        if plane.raycasting_mode == modes[0]:\n            plane.raycasting_mode = modes[1]\n            print(modes[1])\n        else:\n            plane.raycasting_mode = modes[0]\n            print(modes[0])\n    elif event.text != '' and event.text in '{}':\n        t = -1 if event.text == '{' else 1\n        plane.plane_thickness += t\n        plane.plane_thickness += t\n        print(f\"plane thickness: {plane.plane_thickness}\")\n    elif event.text != '' and event.text in '[]':\n        shift = plane.plane_normal / np.linalg.norm(plane.plane_normal)\n        if event.text == '[':\n            plane.plane_position -= 2 * shift\n        elif event.text == ']':\n            plane.plane_position += 2 * shift\n        print(f\"plane position: {plane.plane_position}\")\n\n    elif event.text == 'x':\n        plane.plane_normal = [0, 0, 1]\n    elif event.text == 'y':\n        plane.plane_normal = [0, 1, 0]\n    elif event.text == 'z':\n        plane.plane_normal = [1, 0, 0]\n    elif event.text == 'o':\n        plane.plane_normal = [1, 1, 1]\n\n\ndef move_plane(event):\n    z_pos = plane.plane_position[0]\n    if z_pos < 32:\n        plane.plane_position = plane.plane_position + [1, 0, 0]\n    elif 32 < z_pos <= 220:\n        plane.plane_position = plane.plane_position - [1, 0, 0]\n    else:\n        plane.plane_position = (220, 64, 64)\n\n\ntimer = app.Timer('auto', connect=move_plane, start=True)\n\nif __name__ == '__main__':\n    canvas.show()\n    print(__doc__)\n    if sys.flags.interactive == 0:\n        plane.plane_position = (220, 64, 64)\n        app.run()\n", "path": "examples/scene/volume_plane.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# -----------------------------------------------------------------------------\n# Copyright (c) Vispy Development Team. All Rights Reserved.\n# Distributed under the (new) BSD License. See LICENSE.txt for more info.\n# -----------------------------------------------------------------------------\n# vispy: gallery 10:200:5\n\"\"\"\nRendering Planes through 3D Data\n================================\n\nControls:\n* 1  - toggle between volume rendering methods\n* 2 -  toggle between volume rendering modes ('volume', 'plane')\n* [] - shift plane along plane normal\n* {} - decrease/increase plane thickness\n* Spacebar - stop/start animation\n\n* x/y/z/o - set plane normal along x/y/z or [1,1,1] oblique axis\n\"\"\"\nimport sys\n\nimport numpy as np\n\nfrom vispy import app, scene, io\nfrom vispy.visuals.transforms import STTransform\n\n# Read volume\nvol = np.load(io.load_data_file('volume/stent.npz'))['arr_0']\n\n# Prepare canvas\ncanvas = scene.SceneCanvas(keys='interactive', show=True)\nview = canvas.central_widget.add_view()\n\n# Create the volume visual for plane rendering\nplane = scene.visuals.Volume(\n    vol,\n    parent=view.scene,\n    raycasting_mode='plane',\n    method='mip',\n    plane_thickness=3.0,\n    plane_position=(128, 60, 64),\n    plane_normal=(1, 0, 0),\n)\n\nvolume = scene.visuals.Volume(\n    vol,\n    parent=view.scene,\n    raycasting_mode='volume',\n    method='mip',\n)\nvolume.set_gl_state('additive')\nvolume.opacity = 0.25\n\n# Create a camera\ncam = scene.cameras.TurntableCamera(\n    parent=view.scene, fov=60.0, azimuth=-42.0, elevation=30.0\n)\nview.camera = cam\n\n# Create an XYZAxis visual\naxis = scene.visuals.XYZAxis(parent=view)\ns = STTransform(translate=(50, 50), scale=(50, 50, 50, 1))\naffine = s.as_matrix()\naxis.transform = affine\n\n\ndef update_axis_visual():\n    \"\"\"Sync XYZAxis visual with camera angles\"\"\"\n    axis.transform.reset()\n\n    axis.transform.rotate(cam.roll, (0, 0, 1))\n    axis.transform.rotate(cam.elevation, (1, 0, 0))\n    axis.transform.rotate(cam.azimuth, (0, 1, 0))\n    axis.transform.scale((50, 50, 0.001))\n    axis.transform.translate((50., 50.))\n\n    axis.update()\n\n\nupdate_axis_visual()\n\n\n@canvas.events.mouse_move.connect\ndef on_mouse_move(event):\n    if event.button == 1 and event.is_dragging:\n        update_axis_visual()\n\n\n# Implement key presses\n@canvas.events.key_press.connect\ndef on_key_press(event):\n    if event.text == '1':\n        methods = ['mip', 'average']\n        method = methods[(methods.index(plane.method) + 1) % 2]\n        print(\"Volume render method: %s\" % method)\n        plane.method = method\n    elif event.text == '2':\n        modes = ['volume', 'plane']\n        if plane.raycasting_mode == modes[0]:\n            plane.raycasting_mode = modes[1]\n            print(modes[1])\n        else:\n            plane.raycasting_mode = modes[0]\n            print(modes[0])\n    elif event.text != '' and event.text in '{}':\n        t = -1 if event.text == '{' else 1\n        plane.plane_thickness += t\n        plane.plane_thickness += t\n        print(f\"plane thickness: {plane.plane_thickness}\")\n    elif event.text != '' and event.text in '[]':\n        shift = plane.plane_normal / np.linalg.norm(plane.plane_normal)\n        if event.text == '[':\n            plane.plane_position -= 2 * shift\n        elif event.text == ']':\n            plane.plane_position += 2 * shift\n        print(f\"plane position: {plane.plane_position}\")\n    elif event.text == 'x':\n        plane.plane_normal = [0, 0, 1]\n    elif event.text == 'y':\n        plane.plane_normal = [0, 1, 0]\n    elif event.text == 'z':\n        plane.plane_normal = [1, 0, 0]\n    elif event.text == 'o':\n        plane.plane_normal = [1, 1, 1]\n    elif event.text == ' ':\n        if timer.running:\n            timer.stop()\n        else:\n            timer.start()\n\n\ndef move_plane(event):\n    z_pos = plane.plane_position[0]\n    if z_pos < 32:\n        plane.plane_position = plane.plane_position + [1, 0, 0]\n    elif 32 < z_pos <= 220:\n        plane.plane_position = plane.plane_position - [1, 0, 0]\n    else:\n        plane.plane_position = (220, 64, 64)\n\n\ntimer = app.Timer('auto', connect=move_plane, start=True)\n\nif __name__ == '__main__':\n    canvas.show()\n    print(__doc__)\n    if sys.flags.interactive == 0:\n        plane.plane_position = (220, 64, 64)\n        app.run()\n", "path": "examples/scene/volume_plane.py"}]}