vispy 0.9.5__cp38-cp38-win_amd64.whl → 0.14.0__cp38-cp38-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vispy might be problematic. Click here for more details.

Files changed (103) hide show
  1. vispy/app/backends/_glfw.py +2 -2
  2. vispy/app/backends/_pyglet.py +8 -2
  3. vispy/app/backends/_qt.py +88 -63
  4. vispy/app/backends/_wx.py +6 -1
  5. vispy/app/canvas.py +4 -2
  6. vispy/app/tests/test_canvas.py +52 -1
  7. vispy/app/tests/test_context.py +5 -3
  8. vispy/color/color_array.py +8 -1
  9. vispy/color/colormap.py +5 -25
  10. vispy/geometry/meshdata.py +76 -38
  11. vispy/geometry/rect.py +6 -0
  12. vispy/geometry/tests/test_meshdata.py +72 -0
  13. vispy/gloo/buffer.py +12 -0
  14. vispy/gloo/gl/_constants.py +9 -5
  15. vispy/gloo/gl/_es2.py +8 -4
  16. vispy/gloo/gl/_gl2.py +2 -3
  17. vispy/gloo/gl/_proxy.py +1 -1
  18. vispy/gloo/gl/_pyopengl2.py +12 -7
  19. vispy/gloo/gl/tests/test_names.py +3 -0
  20. vispy/gloo/glir.py +26 -13
  21. vispy/gloo/program.py +39 -22
  22. vispy/gloo/tests/test_program.py +9 -2
  23. vispy/gloo/tests/test_texture.py +19 -2
  24. vispy/gloo/texture.py +46 -16
  25. vispy/gloo/wrappers.py +4 -2
  26. vispy/glsl/build_spatial_filters.py +241 -293
  27. vispy/glsl/misc/spatial-filters.frag +1299 -254
  28. vispy/io/_data/spatial-filters.npy +0 -0
  29. vispy/io/datasets.py +2 -2
  30. vispy/io/image.py +1 -1
  31. vispy/io/stl.py +3 -3
  32. vispy/scene/cameras/base_camera.py +6 -2
  33. vispy/scene/cameras/panzoom.py +10 -14
  34. vispy/scene/cameras/perspective.py +6 -0
  35. vispy/scene/cameras/tests/test_cameras.py +27 -0
  36. vispy/scene/cameras/tests/test_perspective.py +37 -0
  37. vispy/scene/cameras/turntable.py +39 -23
  38. vispy/scene/canvas.py +9 -5
  39. vispy/scene/events.py +9 -0
  40. vispy/scene/node.py +19 -2
  41. vispy/scene/tests/test_canvas.py +30 -1
  42. vispy/scene/tests/test_visuals.py +113 -0
  43. vispy/scene/visuals.py +6 -1
  44. vispy/scene/widgets/viewbox.py +3 -2
  45. vispy/testing/_runners.py +6 -12
  46. vispy/testing/_testing.py +3 -4
  47. vispy/util/check_environment.py +4 -4
  48. vispy/util/gallery_scraper.py +50 -32
  49. vispy/util/tests/test_gallery_scraper.py +2 -0
  50. vispy/util/transforms.py +1 -1
  51. vispy/util/wrappers.py +1 -1
  52. vispy/version.py +2 -3
  53. vispy/visuals/__init__.py +2 -0
  54. vispy/visuals/_scalable_textures.py +20 -17
  55. vispy/visuals/collections/array_list.py +3 -3
  56. vispy/visuals/collections/base_collection.py +1 -1
  57. vispy/visuals/ellipse.py +1 -1
  58. vispy/visuals/filters/__init__.py +3 -2
  59. vispy/visuals/filters/base_filter.py +120 -0
  60. vispy/visuals/filters/clipping_planes.py +24 -12
  61. vispy/visuals/filters/markers.py +28 -0
  62. vispy/visuals/filters/mesh.py +61 -6
  63. vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
  64. vispy/visuals/graphs/graph.py +1 -1
  65. vispy/visuals/image.py +114 -26
  66. vispy/visuals/image_complex.py +130 -0
  67. vispy/visuals/instanced_mesh.py +152 -0
  68. vispy/visuals/isocurve.py +1 -1
  69. vispy/visuals/line/dash_atlas.py +46 -41
  70. vispy/visuals/line/line.py +2 -5
  71. vispy/visuals/markers.py +310 -384
  72. vispy/visuals/mesh.py +2 -2
  73. vispy/visuals/shaders/function.py +3 -0
  74. vispy/visuals/shaders/tests/test_function.py +6 -0
  75. vispy/visuals/tests/test_axis.py +2 -2
  76. vispy/visuals/tests/test_image.py +92 -2
  77. vispy/visuals/tests/test_image_complex.py +36 -0
  78. vispy/visuals/tests/test_instanced_mesh.py +50 -0
  79. vispy/visuals/tests/test_markers.py +6 -0
  80. vispy/visuals/tests/test_mesh.py +17 -0
  81. vispy/visuals/tests/test_text.py +11 -0
  82. vispy/visuals/tests/test_volume.py +218 -12
  83. vispy/visuals/text/_sdf_cpu.cp38-win_amd64.pyd +0 -0
  84. vispy/visuals/text/_sdf_cpu.pyx +21 -23
  85. vispy/visuals/text/text.py +9 -3
  86. vispy/visuals/tube.py +2 -2
  87. vispy/visuals/visual.py +144 -3
  88. vispy/visuals/volume.py +300 -131
  89. {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/LICENSE.txt +1 -1
  90. {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/METADATA +218 -198
  91. {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/RECORD +93 -96
  92. {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/WHEEL +1 -1
  93. vispy/glsl/antialias/__init__.py +0 -0
  94. vispy/glsl/arrowheads/__init__.py +0 -0
  95. vispy/glsl/arrows/__init__.py +0 -0
  96. vispy/glsl/collections/__init__.py +0 -0
  97. vispy/glsl/colormaps/__init__.py +0 -0
  98. vispy/glsl/lines/__init__.py +0 -0
  99. vispy/glsl/markers/__init__.py +0 -0
  100. vispy/glsl/math/__init__.py +0 -0
  101. vispy/glsl/misc/__init__.py +0 -0
  102. vispy/glsl/transforms/__init__.py +0 -0
  103. {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/top_level.txt +0 -0
Binary file
vispy/io/datasets.py CHANGED
@@ -81,8 +81,8 @@ def load_spatial_filters(packed=True):
81
81
  Respective interpolation names, plus "Nearest" which does
82
82
  not require a filter but can still be used
83
83
  """
84
- names = ("Bilinear", "Hanning", "Hamming", "Hermite",
85
- "Kaiser", "Quadric", "Bicubic", "CatRom",
84
+ names = ("Linear", "Hanning", "Hamming", "Hermite",
85
+ "Kaiser", "Quadric", "Cubic", "CatRom",
86
86
  "Mitchell", "Spline16", "Spline36", "Gaussian",
87
87
  "Bessel", "Sinc", "Lanczos", "Blackman", "Nearest")
88
88
 
vispy/io/image.py CHANGED
@@ -35,7 +35,7 @@ def _make_png(data, level=6):
35
35
  PNG formatted array
36
36
  """
37
37
  # Eventually we might want to use ext/png.py for this, but this
38
- # routine *should* be faster b/c it's speacialized for our use case
38
+ # routine *should* be faster b/c it's specialized for our use case
39
39
 
40
40
  def mkchunk(data, name):
41
41
  if isinstance(data, np.ndarray):
vispy/io/stl.py CHANGED
@@ -33,7 +33,7 @@ def load_stl(file_obj, file_type=None):
33
33
  file_type: not used
34
34
 
35
35
  Returns
36
- ----------
36
+ -------
37
37
  loaded: kwargs for a Trimesh constructor with keys:
38
38
  vertices: (n,3) float, vertices
39
39
  faces: (m,3) int, indexes of vertices
@@ -65,7 +65,7 @@ def load_stl_binary(file_obj):
65
65
  file_obj: open file- like object
66
66
 
67
67
  Returns
68
- ----------
68
+ -------
69
69
  loaded: kwargs for a Trimesh constructor with keys:
70
70
  vertices: (n,3) float, vertices
71
71
  faces: (m,3) int, indexes of vertices
@@ -124,7 +124,7 @@ def load_stl_ascii(file_obj):
124
124
  file_obj: open file- like object
125
125
 
126
126
  Returns
127
- ----------
127
+ -------
128
128
  loaded: kwargs for a Trimesh constructor with keys:
129
129
  vertices: (n,3) float, vertices
130
130
  faces: (m,3) int, indexes of vertices
@@ -133,6 +133,8 @@ class BaseCamera(Node):
133
133
  viewbox.events.mouse_release.connect(self.viewbox_mouse_event)
134
134
  viewbox.events.mouse_move.connect(self.viewbox_mouse_event)
135
135
  viewbox.events.mouse_wheel.connect(self.viewbox_mouse_event)
136
+ viewbox.events.gesture_zoom.connect(self.viewbox_mouse_event)
137
+ viewbox.events.gesture_rotate.connect(self.viewbox_mouse_event)
136
138
  viewbox.events.resize.connect(self.viewbox_resize_event)
137
139
  # todo: also add key events! (and also on viewbox (they're missing)
138
140
 
@@ -144,6 +146,8 @@ class BaseCamera(Node):
144
146
  viewbox.events.mouse_release.disconnect(self.viewbox_mouse_event)
145
147
  viewbox.events.mouse_move.disconnect(self.viewbox_mouse_event)
146
148
  viewbox.events.mouse_wheel.disconnect(self.viewbox_mouse_event)
149
+ viewbox.events.gesture_zoom.disconnect(self.viewbox_mouse_event)
150
+ viewbox.events.gesture_rotate.disconnect(self.viewbox_mouse_event)
147
151
  viewbox.events.resize.disconnect(self.viewbox_resize_event)
148
152
 
149
153
  @property
@@ -224,8 +228,8 @@ class BaseCamera(Node):
224
228
  @fov.setter
225
229
  def fov(self, fov):
226
230
  fov = float(fov)
227
- if fov < 0 or fov >= 180:
228
- raise ValueError("fov must be between 0 and 180.")
231
+ if fov < 0 or fov > 180:
232
+ raise ValueError("fov must be 0 <= fov <= 180.")
229
233
  self._fov = fov
230
234
  self.view_changed()
231
235
 
@@ -132,8 +132,11 @@ class PanZoomCamera(BaseCamera):
132
132
 
133
133
  @property
134
134
  def rect(self):
135
- """The rectangular border of the ViewBox visible area, expressed in
136
- the coordinate system of the scene.
135
+ """The rectangular border of the ViewBox visible area.
136
+
137
+ This is expressed in the coordinate system of the scene.
138
+ See :class:`~vispy.geometry.rect.Rect` for different ways this can
139
+ be specified.
137
140
 
138
141
  Note that the rectangle can have negative width or height, in
139
142
  which case the corresponding dimension is flipped (this flipping
@@ -162,17 +165,7 @@ class PanZoomCamera(BaseCamera):
162
165
  if not (isinstance(center, (tuple, list)) and len(center) in (2, 3)):
163
166
  raise ValueError('center must be a 2 or 3 element tuple')
164
167
  rect = Rect(self.rect) or Rect(*DEFAULT_RECT_TUPLE)
165
- # Get half-ranges
166
- x2 = rect.center[0]
167
- y2 = rect.center[1]
168
- # Apply new ranges
169
- x1 = center[0]
170
- y1 = center[1]
171
- rect.left = x1 - x2
172
- rect.right = x1 + x2
173
- rect.bottom = y1 - y2
174
- rect.top = y1 + y2
175
- #
168
+ rect.center = center[:2]
176
169
  self.rect = rect
177
170
 
178
171
  def _set_range(self, init):
@@ -214,7 +207,10 @@ class PanZoomCamera(BaseCamera):
214
207
  center = self._scene_transform.imap(event.pos)
215
208
  self.zoom((1 + self.zoom_factor)**(-event.delta[1] * 30), center)
216
209
  event.handled = True
217
-
210
+ elif event.type == 'gesture_zoom':
211
+ center = self._scene_transform.imap(event.pos)
212
+ self.zoom(1 - event.scale, center)
213
+ event.handled = True
218
214
  elif event.type == 'mouse_move':
219
215
  if event.press_event is None:
220
216
  return
@@ -62,6 +62,12 @@ class PerspectiveCamera(BaseCamera):
62
62
  if self._distance is not None:
63
63
  self._distance *= s
64
64
  self.view_changed()
65
+ elif event.type == 'gesture_zoom':
66
+ s = 1 - event.scale
67
+ self._scale_factor *= s
68
+ if self._distance is not None:
69
+ self._distance *= s
70
+ self.view_changed()
65
71
 
66
72
  @property
67
73
  def scale_factor(self):
@@ -0,0 +1,27 @@
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
+ import numpy as np
7
+ import pytest
8
+ from vispy.scene.cameras import TurntableCamera
9
+ from vispy.testing import run_tests_if_main
10
+
11
+
12
+ @pytest.mark.parametrize(
13
+ "elevation, azimuth, roll, expected",
14
+ [
15
+ [0, 0, 0, np.eye(4)],
16
+ [90, 0, 0, [[1, 0, 0, 0], [0, 0, -1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]],
17
+ [0, 90, 0, [[0, 1, 0, 0], [-1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]],
18
+ [0, 0, 90, [[0, 0, -1, 0], [0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1]]],
19
+ ],
20
+ )
21
+ def test_turntable_camera_transform(elevation, azimuth, roll, expected):
22
+ camera = TurntableCamera(elevation=elevation, azimuth=azimuth, roll=roll)
23
+ matrix = camera._get_rotation_tr()
24
+ np.testing.assert_allclose(matrix, expected, atol=1e-5)
25
+
26
+
27
+ run_tests_if_main()
@@ -82,4 +82,41 @@ def test_panzoom_center():
82
82
  assert v.camera.center == (-12.8, -12.8, 0)
83
83
 
84
84
 
85
+ @requires_application()
86
+ def test_panzoom_gesture_zoom():
87
+ with TestingCanvas(size=(120, 200)) as canvas:
88
+ view = canvas.central_widget.add_view()
89
+ imdata = io.load_crate().astype('float32') / 255
90
+ scene.visuals.Image(imdata, parent=view.scene)
91
+ view.camera = scene.PanZoomCamera(aspect=1)
92
+
93
+ assert view.camera.rect.size == (1, 1)
94
+
95
+ canvas.events.touch(
96
+ type="gesture_zoom",
97
+ pos=(60, 100),
98
+ scale=-1.0,
99
+ )
100
+
101
+ assert view.camera.rect.size == (2, 2)
102
+
103
+
104
+ @requires_application()
105
+ def test_turntable_gesture_zoom():
106
+ with TestingCanvas(size=(120, 200)) as canvas:
107
+ view = canvas.central_widget.add_view()
108
+ imdata = io.load_crate().astype('float32') / 255
109
+ scene.visuals.Image(imdata, parent=view.scene)
110
+ view.camera = scene.TurntableCamera()
111
+
112
+ initial_scale_factor = view.camera.scale_factor
113
+ canvas.events.touch(
114
+ type="gesture_zoom",
115
+ pos=(60, 100),
116
+ scale=-1.0,
117
+ )
118
+
119
+ assert view.camera.scale_factor == 2 * initial_scale_factor
120
+
121
+
85
122
  run_tests_if_main()
@@ -18,19 +18,26 @@ class TurntableCamera(Base3DRotationCamera):
18
18
  the ``center`` indicates the position to put at the center of the
19
19
  view.
20
20
 
21
+ When ``elevation`` and ``azimuth`` are set to 0, the camera
22
+ points along the +y axis.
23
+
21
24
  Parameters
22
25
  ----------
23
26
  fov : float
24
- Field of view. Zero (default) means orthographic projection.
27
+ Field of view. 0.0 means orthographic projection,
28
+ default is 45.0 (some perspective)
25
29
  elevation : float
26
- Elevation angle in degrees. Positive angles place the camera
27
- above the cente point, negative angles place the camera below
28
- the center point.
30
+ Elevation angle in degrees. The elevation angle represents a
31
+ rotation of the camera around the current scene x-axis. The
32
+ camera points along the x-y plane when the angle is 0.
29
33
  azimuth : float
30
- Azimuth angle in degrees. Zero degrees places the camera on the
31
- positive x-axis, pointing in the negative x direction.
34
+ Azimuth angle in degrees. The azimuth angle represents a
35
+ rotation of the camera around the scene z-axis according to the
36
+ right-hand screw rule. The camera points along the y-z plane when
37
+ the angle is 0.
32
38
  roll : float
33
- Roll angle in degrees
39
+ Roll angle in degrees. The roll angle represents a rotation of
40
+ the camera around the current scene y-axis.
34
41
  distance : float | None
35
42
  The distance of the camera from the rotation point (only makes sense
36
43
  if fov > 0). If None (default) the distance is determined from the
@@ -51,24 +58,32 @@ class TurntableCamera(Base3DRotationCamera):
51
58
 
52
59
  """
53
60
 
54
- _state_props = Base3DRotationCamera._state_props + ('elevation',
55
- 'azimuth', 'roll')
56
-
57
- def __init__(self, fov=45.0, elevation=30.0, azimuth=30.0, roll=0.0,
58
- distance=None, translate_speed=1.0, **kwargs):
61
+ _state_props = Base3DRotationCamera._state_props + ("elevation", "azimuth", "roll")
62
+
63
+ def __init__(
64
+ self,
65
+ fov=45.0,
66
+ elevation=30.0,
67
+ azimuth=30.0,
68
+ roll=0.0,
69
+ distance=None,
70
+ translate_speed=1.0,
71
+ **kwargs
72
+ ):
59
73
  super(TurntableCamera, self).__init__(fov=fov, **kwargs)
60
74
 
61
75
  # Set camera attributes
62
76
  self.azimuth = azimuth
63
77
  self.elevation = elevation
64
- self.roll = roll # interaction not implemented yet
78
+ self.roll = roll
65
79
  self.distance = distance # None means auto-distance
66
80
  self.translate_speed = translate_speed
67
81
 
68
82
  @property
69
83
  def elevation(self):
70
- """The angle of the camera in degrees above the horizontal (x, z)
71
- plane.
84
+ """Get the camera elevation angle in degrees.
85
+
86
+ The camera points along the x-y plane when the angle is 0.
72
87
  """
73
88
  return self._elevation
74
89
 
@@ -80,8 +95,9 @@ class TurntableCamera(Base3DRotationCamera):
80
95
 
81
96
  @property
82
97
  def azimuth(self):
83
- """The angle of the camera in degrees around the y axis. An angle of
84
- 0 places the camera within the (y, z) plane.
98
+ """Get the camera azimuth angle in degrees.
99
+
100
+ The camera points along the y-z plane when the angle is 0.
85
101
  """
86
102
  return self._azimuth
87
103
 
@@ -97,9 +113,7 @@ class TurntableCamera(Base3DRotationCamera):
97
113
 
98
114
  @property
99
115
  def roll(self):
100
- """The angle of the camera in degrees around the z axis. An angle of
101
- 0 places puts the camera upright.
102
- """
116
+ """Get the camera roll angle in degrees."""
103
117
  return self._roll
104
118
 
105
119
  @roll.setter
@@ -138,10 +152,12 @@ class TurntableCamera(Base3DRotationCamera):
138
152
  def _get_rotation_tr(self):
139
153
  """Return a rotation matrix based on camera parameters"""
140
154
  up, forward, right = self._get_dim_vectors()
141
- return np.dot(
142
- transforms.rotate(self.elevation, -right),
143
- transforms.rotate(self.azimuth, up)
155
+ matrix = (
156
+ transforms.rotate(self.elevation, -right)
157
+ .dot(transforms.rotate(self.azimuth, up))
158
+ .dot(transforms.rotate(self.roll, forward))
144
159
  )
160
+ return matrix
145
161
 
146
162
  def _dist_to_trans(self, dist):
147
163
  """Convert mouse x, y movement into x, y, z translations"""
vispy/scene/canvas.py CHANGED
@@ -140,6 +140,7 @@ class SceneCanvas(app.Canvas, Frozen):
140
140
  self.events.mouse_move.connect(self._process_mouse_event)
141
141
  self.events.mouse_release.connect(self._process_mouse_event)
142
142
  self.events.mouse_wheel.connect(self._process_mouse_event)
143
+ self.events.touch.connect(self._process_mouse_event)
143
144
 
144
145
  self.scene = SubScene()
145
146
  self.freeze()
@@ -344,7 +345,12 @@ class SceneCanvas(app.Canvas, Frozen):
344
345
 
345
346
  def _process_mouse_event(self, event):
346
347
  prof = Profiler() # noqa
347
- deliver_types = ['mouse_press', 'mouse_wheel']
348
+ deliver_types = [
349
+ 'mouse_press',
350
+ 'mouse_wheel',
351
+ 'gesture_zoom',
352
+ 'gesture_rotate',
353
+ ]
348
354
  if self._send_hover_events:
349
355
  deliver_types += ['mouse_move']
350
356
 
@@ -487,11 +493,8 @@ class SceneCanvas(app.Canvas, Frozen):
487
493
  than triggering transform updates across the scene with every
488
494
  click.
489
495
  """
490
- try:
491
- self._scene.picking = True
496
+ with self._scene.set_picking():
492
497
  img = self.render(bgcolor=(0, 0, 0, 0), crop=crop)
493
- finally:
494
- self._scene.picking = False
495
498
  img = img.astype('int32') * [2**0, 2**8, 2**16, 2**24]
496
499
  id_ = img.sum(axis=2).astype('int32')
497
500
  return id_
@@ -524,6 +527,7 @@ class SceneCanvas(app.Canvas, Frozen):
524
527
  self.events.mouse_move.disconnect(self._process_mouse_event)
525
528
  self.events.mouse_release.disconnect(self._process_mouse_event)
526
529
  self.events.mouse_wheel.disconnect(self._process_mouse_event)
530
+ self.events.touch.disconnect(self._process_mouse_event)
527
531
 
528
532
  # -------------------------------------------------- transform handling ---
529
533
  def push_viewport(self, viewport):
vispy/scene/events.py CHANGED
@@ -71,6 +71,15 @@ class SceneMouseEvent(Event):
71
71
  """The increment by which the mouse wheel has moved."""
72
72
  return self.mouse_event.delta
73
73
 
74
+ @property
75
+ def scale(self):
76
+ """The scale of a gesture_zoom event"""
77
+ try:
78
+ return self.mouse_event.scale
79
+ except AttributeError:
80
+ errmsg = f"SceneMouseEvent type '{self.type}' has no scale"
81
+ raise TypeError(errmsg)
82
+
74
83
  def copy(self):
75
84
  ev = self.__class__(self.mouse_event, self.visual)
76
85
  return ev
vispy/scene/node.py CHANGED
@@ -5,6 +5,7 @@
5
5
  from __future__ import division
6
6
 
7
7
  import weakref
8
+ from contextlib import contextmanager
8
9
 
9
10
  from ..util.event import Event, EmitterGroup
10
11
  from ..visuals.transforms import (NullTransform, BaseTransform,
@@ -63,7 +64,8 @@ class Node(object):
63
64
  # Add some events to the emitter groups:
64
65
  events = ['canvas_change', 'parent_change', 'children_change',
65
66
  'transform_change', 'mouse_press', 'mouse_move',
66
- 'mouse_release', 'mouse_wheel', 'key_press', 'key_release']
67
+ 'mouse_release', 'mouse_wheel', 'key_press', 'key_release',
68
+ 'gesture_zoom', 'gesture_rotate']
67
69
  # Create event emitter if needed (in subclasses that inherit from
68
70
  # Visual, we already have an emitter to share)
69
71
  if not hasattr(self, 'events'):
@@ -423,7 +425,7 @@ class Node(object):
423
425
  If true, add information about node transform types.
424
426
 
425
427
  Returns
426
- ----------
428
+ -------
427
429
  tree : str
428
430
  The tree diagram.
429
431
  """
@@ -625,3 +627,18 @@ class Node(object):
625
627
  for c in self.children:
626
628
  c.picking = p
627
629
  self._picking = p
630
+
631
+ @contextmanager
632
+ def set_picking(self, *, picking=True):
633
+ """Context manager to temporarily set picking for this node and its children.
634
+
635
+ Note that this function will not alter the picking mode unless/until
636
+ the context manager is entered (using the `with` statement). Use
637
+ :py:attr:`~.picking` for setting the picking mode directly.
638
+ """
639
+ old_picking = self.picking
640
+ try:
641
+ self.picking = picking
642
+ yield self.picking
643
+ finally:
644
+ self.picking = old_picking
@@ -2,7 +2,7 @@
2
2
  # Copyright (c) Vispy Development Team. All Rights Reserved.
3
3
  # Distributed under the (new) BSD License. See LICENSE.txt for more info.
4
4
 
5
- from vispy import scene
5
+ from vispy import gloo, scene
6
6
  from vispy.testing import requires_application, TestingCanvas
7
7
  from vispy.visuals.transforms import STTransform
8
8
 
@@ -88,3 +88,32 @@ def test_picking_basic():
88
88
  assert len(picked_visuals) == 2
89
89
  assert any(isinstance(vis, scene.ViewBox) for vis in picked_visuals)
90
90
  assert any(isinstance(vis, scene.Line) for vis in picked_visuals)
91
+
92
+
93
+ @requires_application()
94
+ @pytest.mark.parametrize(
95
+ 'preset',
96
+ [
97
+ 'opaque', 'additive', 'translucent',
98
+ ])
99
+ def test_blend_presets(preset):
100
+ """Test blending presets render a canvas to an array.
101
+
102
+ Different blending presets are used to test that they properly set
103
+ blend equations.
104
+
105
+ """
106
+ with TestingCanvas(size=(125, 125), show=True, title='run') as c:
107
+ view = c.central_widget.add_view()
108
+ im1 = np.zeros((100, 100, 4)).astype(np.float32)
109
+ im1[:, :, 1] = 1
110
+ im1[:, :, 3] = .4
111
+ # Create the image
112
+ image1 = scene.visuals.Image(im1, parent=view.scene)
113
+ image1.transform = STTransform(translate=(20, 20, -1))
114
+
115
+ gloo.set_state(blend_equation='min')
116
+ image1.set_gl_state(preset)
117
+
118
+ rgba_result = c.render()
119
+ assert not np.allclose(rgba_result[..., :3], 0)
@@ -1,3 +1,5 @@
1
+ import pytest
2
+
1
3
  from vispy.scene import visuals, Node
2
4
  from vispy.scene.visuals import VisualNode
3
5
  import vispy.visuals
@@ -26,3 +28,114 @@ def test_visual_node_generation():
26
28
  vis_node = getattr(visuals, name[:-6])
27
29
  assert issubclass(vis_node, Node)
28
30
  assert issubclass(vis_node, obj)
31
+
32
+
33
+ def test_push_gl_state():
34
+ node = vispy.visuals.MeshVisual()
35
+ og_gl_state = node._vshare.gl_state.copy()
36
+ node.push_gl_state(blend=False, depth_test=False)
37
+ assert node._vshare.gl_state != og_gl_state
38
+ # preset is always set, unset kwargs should be absent
39
+ assert node._vshare.gl_state == {
40
+ "preset": None,
41
+ "blend": False,
42
+ "depth_test": False,
43
+ }
44
+ node.pop_gl_state()
45
+ assert node._vshare.gl_state == og_gl_state
46
+
47
+
48
+ def test_push_gl_state_update():
49
+ node = vispy.visuals.MeshVisual()
50
+ og_gl_state = node._vshare.gl_state.copy()
51
+ assert "blend" not in og_gl_state
52
+ assert node._vshare.gl_state["depth_test"]
53
+
54
+ node.push_gl_state_update(blend=False, depth_test=False)
55
+ assert node._vshare.gl_state != og_gl_state
56
+ assert not node._vshare.gl_state["blend"]
57
+ assert not node._vshare.gl_state["depth_test"]
58
+
59
+ node.pop_gl_state()
60
+ assert node._vshare.gl_state == og_gl_state
61
+
62
+
63
+ def test_pop_empty_gl_state():
64
+ node = vispy.visuals.MeshVisual()
65
+ assert node._prev_gl_state == []
66
+ og_gl_state = node._vshare.gl_state.copy()
67
+ node.pop_gl_state()
68
+ assert node._vshare.gl_state == og_gl_state
69
+
70
+
71
+ def test_update_gl_state():
72
+ node = vispy.visuals.MeshVisual()
73
+
74
+ og_gl_state = node._vshare.gl_state.copy()
75
+ assert og_gl_state
76
+ og_gl_state["blend"] = False
77
+
78
+ node.update_gl_state(blend=True)
79
+
80
+ # check that the state was updated
81
+ assert node._vshare.gl_state.pop("blend") != og_gl_state.pop("blend")
82
+ # the rest of the state should be unchanged
83
+ assert node._vshare.gl_state == og_gl_state
84
+
85
+
86
+ def test_update_gl_state_context_manager():
87
+ node = vispy.visuals.MeshVisual()
88
+
89
+ node.set_gl_state(blend=False)
90
+ og_gl_state = node._vshare.gl_state.copy()
91
+
92
+ with node.update_gl_state(blend=True):
93
+ # check that the state was updated
94
+ assert node._vshare.gl_state == {**og_gl_state, "blend": True}
95
+
96
+ # the update should be reverted once out of the context
97
+ assert node._vshare.gl_state == og_gl_state
98
+
99
+
100
+ def test_set_gl_state():
101
+ node = vispy.visuals.MeshVisual()
102
+
103
+ node.set_gl_state(blend=False, depth_test=False)
104
+ # preset is always set, unset kwargs should be absent
105
+ assert node._vshare.gl_state == {
106
+ "preset": None,
107
+ "blend": False,
108
+ "depth_test": False,
109
+ }
110
+
111
+ node.set_gl_state(blend=False)
112
+ # preset is always set, unset kwargs should be absent
113
+ assert node._vshare.gl_state == {"preset": None, "blend": False}
114
+
115
+
116
+ def test_set_gl_state_context_manager():
117
+ node = vispy.visuals.MeshVisual()
118
+
119
+ node.set_gl_state(blend=False)
120
+ og_gl_state = node._vshare.gl_state.copy()
121
+
122
+ with node.set_gl_state(blend=True):
123
+ # preset is always set, unset kwargs should be absent
124
+ assert node._vshare.gl_state == {"preset": None, "blend": True}
125
+
126
+ # the update should be reverted once out of the context
127
+ assert node._vshare.gl_state == og_gl_state
128
+
129
+
130
+ @pytest.mark.parametrize("enable_picking", [True, False])
131
+ def test_picking_context(enable_picking):
132
+ mesh = visuals.Mesh()
133
+ mesh.picking = not enable_picking
134
+
135
+ assert mesh.picking != enable_picking
136
+
137
+ with mesh.set_picking(picking=enable_picking) as p:
138
+ assert p == enable_picking
139
+ assert mesh.picking == enable_picking
140
+
141
+ assert mesh.picking != enable_picking
vispy/scene/visuals.py CHANGED
@@ -71,7 +71,10 @@ class VisualNode(Node):
71
71
  return
72
72
  self._picking = p
73
73
  self._picking_filter.enabled = p
74
- self.update_gl_state(blend=not p)
74
+ if p:
75
+ self.push_gl_state_update(blend=False)
76
+ else:
77
+ self.pop_gl_state()
75
78
 
76
79
  def _update_trsys(self, event):
77
80
  """Transform object(s) have changed for this Node; assign these to the
@@ -242,7 +245,9 @@ GridLines = create_visual_node(visuals.GridLinesVisual)
242
245
  GridMesh = create_visual_node(visuals.GridMeshVisual)
243
246
  Histogram = create_visual_node(visuals.HistogramVisual)
244
247
  Image = create_visual_node(visuals.ImageVisual)
248
+ ComplexImage = create_visual_node(visuals.ComplexImageVisual)
245
249
  InfiniteLine = create_visual_node(visuals.InfiniteLineVisual)
250
+ InstancedMesh = create_visual_node(visuals.InstancedMeshVisual)
246
251
  Isocurve = create_visual_node(visuals.IsocurveVisual)
247
252
  Isoline = create_visual_node(visuals.IsolineVisual)
248
253
  Isosurface = create_visual_node(visuals.IsosurfaceVisual)
@@ -3,6 +3,7 @@
3
3
  # Distributed under the (new) BSD License. See LICENSE.txt for more info.
4
4
 
5
5
  from __future__ import division
6
+ from typing import Union
6
7
 
7
8
  import numpy as np
8
9
 
@@ -67,7 +68,7 @@ class ViewBox(Widget):
67
68
  raise TypeError('Argument "camera" must be None, str, or Camera.')
68
69
 
69
70
  @property
70
- def camera(self):
71
+ def camera(self) -> BaseCamera:
71
72
  """Get/set the Camera in use by this ViewBox
72
73
 
73
74
  If a string is given (e.g. 'panzoom', 'turntable', 'fly'). A
@@ -84,7 +85,7 @@ class ViewBox(Widget):
84
85
  return self._camera
85
86
 
86
87
  @camera.setter
87
- def camera(self, cam):
88
+ def camera(self, cam: Union[str, BaseCamera]):
88
89
  if isinstance(cam, str):
89
90
  # Try to select an existing camera
90
91
  for child in self.scene.children:
vispy/testing/_runners.py CHANGED
@@ -219,24 +219,18 @@ try:
219
219
  faulthandler.enable()
220
220
  except Exception:
221
221
  pass
222
+ from vispy.util.gallery_scraper import get_canvaslike_from_globals, FrameGrabber
222
223
  os.environ['VISPY_IGNORE_OLD_VERSION'] = 'true'
224
+ os.environ['_VISPY_RUNNING_GALLERY_EXAMPLES'] = 'true'
223
225
  import {0}
224
226
 
225
- if hasattr({0}, 'canvas'):
226
- canvas = {0}.canvas
227
- elif hasattr({0}, 'Canvas'):
228
- canvas = {0}.Canvas()
229
- elif hasattr({0}, 'fig'):
230
- canvas = {0}.fig
231
- else:
227
+ canvas_or_widget = get_canvaslike_from_globals({0}.__dict__)
228
+ if canvas_or_widget is None:
232
229
  raise RuntimeError('Bad example formatting: fix or add `# vispy: testskip`'
233
230
  ' to the top of the file.')
234
231
 
235
- with canvas as c:
236
- for _ in range(5):
237
- c.update()
238
- c.app.process_events()
239
- time.sleep(1./60.)
232
+ frame_grabber = FrameGrabber(canvas_or_widget, [1, 2, 3, 4, 5])
233
+ frame_grabber.collect_frames()
240
234
  """
241
235
 
242
236
  bad_examples = []