vispy 0.12.1__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 (43) hide show
  1. vispy/app/backends/_glfw.py +1 -1
  2. vispy/app/backends/_qt.py +78 -53
  3. vispy/color/color_array.py +8 -1
  4. vispy/color/colormap.py +4 -24
  5. vispy/geometry/meshdata.py +76 -38
  6. vispy/geometry/tests/test_meshdata.py +72 -0
  7. vispy/gloo/gl/_constants.py +3 -3
  8. vispy/gloo/program.py +1 -1
  9. vispy/gloo/texture.py +9 -4
  10. vispy/scene/cameras/base_camera.py +4 -0
  11. vispy/scene/cameras/panzoom.py +4 -1
  12. vispy/scene/cameras/perspective.py +6 -0
  13. vispy/scene/cameras/tests/test_perspective.py +37 -0
  14. vispy/scene/canvas.py +9 -5
  15. vispy/scene/events.py +9 -0
  16. vispy/scene/node.py +18 -1
  17. vispy/scene/tests/test_visuals.py +113 -0
  18. vispy/scene/visuals.py +5 -1
  19. vispy/util/gallery_scraper.py +8 -0
  20. vispy/util/tests/test_gallery_scraper.py +2 -0
  21. vispy/version.py +2 -3
  22. vispy/visuals/__init__.py +1 -0
  23. vispy/visuals/filters/__init__.py +3 -2
  24. vispy/visuals/filters/base_filter.py +120 -0
  25. vispy/visuals/filters/markers.py +28 -0
  26. vispy/visuals/filters/mesh.py +61 -6
  27. vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
  28. vispy/visuals/instanced_mesh.py +152 -0
  29. vispy/visuals/line/dash_atlas.py +46 -41
  30. vispy/visuals/markers.py +49 -24
  31. vispy/visuals/mesh.py +2 -2
  32. vispy/visuals/tests/test_instanced_mesh.py +50 -0
  33. vispy/visuals/tests/test_mesh.py +17 -0
  34. vispy/visuals/text/_sdf_cpu.cp38-win_amd64.pyd +0 -0
  35. vispy/visuals/text/_sdf_cpu.pyx +21 -23
  36. vispy/visuals/tube.py +1 -1
  37. vispy/visuals/visual.py +142 -1
  38. vispy/visuals/volume.py +19 -10
  39. {vispy-0.12.1.dist-info → vispy-0.14.0.dist-info}/LICENSE.txt +1 -1
  40. {vispy-0.12.1.dist-info → vispy-0.14.0.dist-info}/METADATA +7 -6
  41. {vispy-0.12.1.dist-info → vispy-0.14.0.dist-info}/RECORD +43 -39
  42. {vispy-0.12.1.dist-info → vispy-0.14.0.dist-info}/WHEEL +1 -1
  43. {vispy-0.12.1.dist-info → vispy-0.14.0.dist-info}/top_level.txt +0 -0
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'):
@@ -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
@@ -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
@@ -244,6 +247,7 @@ Histogram = create_visual_node(visuals.HistogramVisual)
244
247
  Image = create_visual_node(visuals.ImageVisual)
245
248
  ComplexImage = create_visual_node(visuals.ComplexImageVisual)
246
249
  InfiniteLine = create_visual_node(visuals.InfiniteLineVisual)
250
+ InstancedMesh = create_visual_node(visuals.InstancedMeshVisual)
247
251
  Isocurve = create_visual_node(visuals.IsocurveVisual)
248
252
  Isoline = create_visual_node(visuals.IsolineVisual)
249
253
  Isosurface = create_visual_node(visuals.IsosurfaceVisual)
@@ -89,6 +89,7 @@ class VisPyGalleryScraper:
89
89
  frame_grabber.save_animation(image_path)
90
90
  else:
91
91
  frame_grabber.save_frame(image_path)
92
+ frame_grabber.cleanup()
92
93
  if 'images' in gallery_conf['compress_images']:
93
94
  optipng(image_path, gallery_conf['compress_images_args'])
94
95
  return [image_path]
@@ -176,6 +177,13 @@ class FrameGrabber:
176
177
  self._collected_images = []
177
178
  self._frames_to_grab = frame_grab_list[:] # copy so original list is preserved
178
179
 
180
+ def cleanup(self):
181
+ from PyQt5.QtWidgets import QApplication
182
+ for child_widget in QApplication.allWidgets():
183
+ if hasattr(child_widget, 'close'):
184
+ child_widget.close()
185
+ QApplication.processEvents()
186
+
179
187
  def on_draw(self, _):
180
188
  if self._done:
181
189
  return # Grab only once
@@ -16,6 +16,8 @@ except ImportError:
16
16
 
17
17
  from ..gallery_scraper import VisPyGalleryScraper
18
18
 
19
+ pytest.importorskip("PyQt5", reason="Gallery scraper only supports PyQt5")
20
+
19
21
 
20
22
  def _create_fake_block_vars(canvas):
21
23
  block_vars = {
vispy/version.py CHANGED
@@ -1,5 +1,4 @@
1
- # coding: utf-8
2
1
  # file generated by setuptools_scm
3
2
  # don't change, don't track in version control
4
- __version__ = version = '0.12.1'
5
- __version_tuple__ = version_tuple = (0, 12, 1)
3
+ __version__ = version = '0.14.0'
4
+ __version_tuple__ = version_tuple = (0, 14, 0)
vispy/visuals/__init__.py CHANGED
@@ -21,6 +21,7 @@ from .image_complex import ComplexImageVisual # noqa
21
21
  from .gridmesh import GridMeshVisual # noqa
22
22
  from .histogram import HistogramVisual # noqa
23
23
  from .infinite_line import InfiniteLineVisual # noqa
24
+ from .instanced_mesh import InstancedMeshVisual # noqa
24
25
  from .isocurve import IsocurveVisual # noqa
25
26
  from .isoline import IsolineVisual # noqa
26
27
  from .isosurface import IsosurfaceVisual # noqa
@@ -2,8 +2,9 @@
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 .base_filter import Filter # noqa
5
+ from .base_filter import Filter, PrimitivePickingFilter # noqa
6
6
  from .clipper import Clipper # noqa
7
7
  from .color import Alpha, ColorFilter, IsolineFilter, ZColormapFilter # noqa
8
8
  from .picking import PickingFilter # noqa
9
- from .mesh import TextureFilter, ShadingFilter, WireframeFilter # noqa
9
+ from .markers import MarkerPickingFilter # noqa
10
+ from .mesh import TextureFilter, ShadingFilter, InstancedShadingFilter, WireframeFilter, FacePickingFilter # noqa
@@ -2,6 +2,11 @@
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 abc import ABCMeta, abstractmethod
6
+
7
+ import numpy as np
8
+
9
+ from vispy.gloo import VertexBuffer
5
10
  from ..shaders import Function
6
11
 
7
12
 
@@ -120,3 +125,118 @@ class Filter(BaseFilter):
120
125
 
121
126
  self._attached = False
122
127
  self._visual = None
128
+
129
+
130
+ class PrimitivePickingFilter(Filter, metaclass=ABCMeta):
131
+ """Abstract base class for Visual-specific filters to implement a
132
+ primitive-picking mode.
133
+
134
+ Subclasses must (and usually only need to) implement
135
+ :py:meth:`_get_picking_ids`.
136
+ """
137
+
138
+ def __init__(self, fpos=9, *, discard_transparent=False):
139
+ # fpos is set to 9 by default to put it near the end, but before the
140
+ # default PickingFilter
141
+ vfunc = Function("""\
142
+ varying vec4 v_marker_picking_color;
143
+ void prepare_marker_picking() {
144
+ v_marker_picking_color = $ids;
145
+ }
146
+ """)
147
+ ffunc = Function("""\
148
+ varying vec4 v_marker_picking_color;
149
+ void marker_picking_filter() {
150
+ if ( $enabled != 1 ) {
151
+ return;
152
+ }
153
+ if ( $discard_transparent == 1 && gl_FragColor.a == 0.0 ) {
154
+ discard;
155
+ }
156
+ gl_FragColor = v_marker_picking_color;
157
+ }
158
+ """)
159
+
160
+ self._id_colors = VertexBuffer(np.zeros((0, 4), dtype=np.float32))
161
+ vfunc['ids'] = self._id_colors
162
+ self._n_primitives = 0
163
+ super().__init__(vcode=vfunc, fcode=ffunc, fpos=fpos)
164
+ self.enabled = False
165
+ self.discard_transparent = discard_transparent
166
+
167
+ @abstractmethod
168
+ def _get_picking_ids(self):
169
+ """Return a 1D array of picking IDs for the vertices in the visual.
170
+
171
+ Generally, this method should be implemented to:
172
+ 1. Calculate the number of primitives in the visual (may be
173
+ persisted in `self._n_primitives`).
174
+ 2. Calculate a range of picking ids for each primitive in the
175
+ visual. IDs should start from 1, reserving 0 for the background. If
176
+ primitives comprise multiple vertices (triangles), ids may need to
177
+ be repeated.
178
+
179
+ The return value should be an array of uint32 with shape
180
+ (num_vertices,).
181
+
182
+ If no change to the picking IDs is needed (for example, the number of
183
+ primitives has not changed), this method should return `None`.
184
+ """
185
+ raise NotImplementedError(self)
186
+
187
+ def _update_id_colors(self):
188
+ """Calculate the colors encoding the picking IDs for the visual.
189
+
190
+ For performance, this method will not update the id colors VertexBuffer
191
+ if :py:meth:`_get_picking_ids` returns `None`.
192
+ """
193
+ # this should remain untouched
194
+ ids = self._get_picking_ids()
195
+ if ids is not None:
196
+ id_colors = self._pack_ids_into_rgba(ids)
197
+ self._id_colors.set_data(id_colors)
198
+
199
+ @staticmethod
200
+ def _pack_ids_into_rgba(ids):
201
+ """Pack an array of uint32 primitive ids into float32 RGBA colors."""
202
+ if ids.dtype != np.uint32:
203
+ raise ValueError(f"ids must be uint32, got {ids.dtype}")
204
+
205
+ return np.divide(
206
+ ids.view(np.uint8).reshape(-1, 4),
207
+ 255,
208
+ dtype=np.float32
209
+ )
210
+
211
+ def _on_data_updated(self, event=None):
212
+ if not self.attached:
213
+ return
214
+ self._update_id_colors()
215
+
216
+ @property
217
+ def enabled(self):
218
+ return self._enabled
219
+
220
+ @enabled.setter
221
+ def enabled(self, e):
222
+ self._enabled = bool(e)
223
+ self.fshader['enabled'] = int(self._enabled)
224
+ self._on_data_updated()
225
+
226
+ @property
227
+ def discard_transparent(self):
228
+ return self._discard_transparent
229
+
230
+ @discard_transparent.setter
231
+ def discard_transparent(self, d):
232
+ self._discard_transparent = bool(d)
233
+ self.fshader['discard_transparent'] = int(self._discard_transparent)
234
+
235
+ def _attach(self, visual):
236
+ super()._attach(visual)
237
+ visual.events.data_updated.connect(self._on_data_updated)
238
+ self._on_data_updated()
239
+
240
+ def _detach(self, visual):
241
+ visual.events.data_updated.disconnect(self._on_data_updated)
242
+ super()._detach(visual)
@@ -0,0 +1,28 @@
1
+ import numpy as np
2
+
3
+ from vispy.visuals.filters import PrimitivePickingFilter
4
+
5
+
6
+ class MarkerPickingFilter(PrimitivePickingFilter):
7
+ """Filter used to color markers by a picking ID.
8
+
9
+ Note that the ID color uses the alpha channel, so this may not be used
10
+ with blending enabled.
11
+
12
+ Examples
13
+ --------
14
+ :ref:`sphx_glr_gallery_scene_marker_picking.py`
15
+ """
16
+
17
+ def _get_picking_ids(self):
18
+ if self._visual._data is None:
19
+ n_markers = 0
20
+ else:
21
+ n_markers = len(self._visual._data['a_position'])
22
+
23
+ # we only care about the number of markers changing
24
+ if self._n_primitives == n_markers:
25
+ return
26
+ self._n_primitives = n_markers
27
+
28
+ return np.arange(1, n_markers + 1, dtype=np.uint32)
@@ -7,7 +7,7 @@ import numpy as np
7
7
 
8
8
  from vispy.gloo import Texture2D, VertexBuffer
9
9
  from vispy.visuals.shaders import Function, Varying
10
- from vispy.visuals.filters import Filter
10
+ from vispy.visuals.filters import Filter, PrimitivePickingFilter
11
11
  from ...color import Color
12
12
 
13
13
 
@@ -177,6 +177,7 @@ void shade() {
177
177
  vec3 u = dFdx(v_pos_scene.xyz);
178
178
  vec3 v = dFdy(v_pos_scene.xyz);
179
179
  normal = cross(u, v);
180
+ } else {
180
181
  // Note(asnt): The normal calculated above always points in the
181
182
  // direction of the camera. Reintroduce the original orientation of the
182
183
  // face.
@@ -387,6 +388,10 @@ class ShadingFilter(Filter):
387
388
  <https://github.com/vispy/vispy/blob/main/examples/basics/scene/mesh_shading.py>`_
388
389
  example script.
389
390
  """
391
+ _shaders = {
392
+ 'vertex': shading_vertex_template,
393
+ 'fragment': shading_fragment_template,
394
+ }
390
395
 
391
396
  def __init__(self, shading='flat',
392
397
  ambient_coefficient=(1, 1, 1, 1),
@@ -412,8 +417,8 @@ class ShadingFilter(Filter):
412
417
 
413
418
  self._enabled = enabled
414
419
 
415
- vfunc = Function(shading_vertex_template)
416
- ffunc = Function(shading_fragment_template)
420
+ vfunc = Function(self._shaders['vertex'])
421
+ ffunc = Function(self._shaders['fragment'])
417
422
 
418
423
  self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32))
419
424
  vfunc['normal'] = self._normals
@@ -572,6 +577,29 @@ class ShadingFilter(Filter):
572
577
  super()._detach(visual)
573
578
 
574
579
 
580
+ instanced_shading_vertex_template = shading_vertex_template.replace(
581
+ "$normal",
582
+ "mat3($instance_transform_x, $instance_transform_y, $instance_transform_z) * $normal"
583
+ )
584
+
585
+
586
+ class InstancedShadingFilter(ShadingFilter):
587
+ """Shading filter modified for use with :class:`~vispy.visuals.InstancedMeshVisual`.
588
+
589
+ See :class:`ShadingFilter` for details and usage.
590
+ """
591
+ _shaders = {
592
+ 'vertex': instanced_shading_vertex_template,
593
+ 'fragment': ShadingFilter._shaders['fragment'],
594
+ }
595
+
596
+ def _attach(self, visual):
597
+ super()._attach(visual)
598
+ self.vshader['instance_transform_x'] = visual._instance_transforms_vbos[0]
599
+ self.vshader['instance_transform_y'] = visual._instance_transforms_vbos[1]
600
+ self.vshader['instance_transform_z'] = visual._instance_transforms_vbos[2]
601
+
602
+
575
603
  wireframe_vertex_template = """
576
604
  varying vec3 v_bc;
577
605
 
@@ -729,13 +757,40 @@ class WireframeFilter(Filter):
729
757
  bc = np.tile(bc[None, ...], (n_faces, 1, 1))
730
758
  self._bc.set_data(bc, convert=True)
731
759
 
732
- def on_mesh_data_updated(self, event):
760
+ def on_data_updated(self, event):
733
761
  self._update_data()
734
762
 
735
763
  def _attach(self, visual):
736
764
  super()._attach(visual)
737
- visual.events.data_updated.connect(self.on_mesh_data_updated)
765
+ visual.events.data_updated.connect(self.on_data_updated)
738
766
 
739
767
  def _detach(self, visual):
740
- visual.events.data_updated.disconnect(self.on_mesh_data_updated)
768
+ visual.events.data_updated.disconnect(self.on_data_updated)
741
769
  super()._detach(visual)
770
+
771
+
772
+ class FacePickingFilter(PrimitivePickingFilter):
773
+ """Filter used to color mesh faces by a picking ID.
774
+
775
+ Note that the ID color uses the alpha channel, so this may not be used
776
+ with blending enabled.
777
+
778
+ Examples
779
+ --------
780
+ :ref:`sphx_glr_gallery_scene_face_picking.py`
781
+ """
782
+
783
+ def _get_picking_ids(self):
784
+ if self._visual.mesh_data.is_empty():
785
+ n_faces = 0
786
+ else:
787
+ n_faces = len(self._visual.mesh_data.get_faces())
788
+
789
+ # we only care about the number of faces changing
790
+ if self._n_primitives == n_faces:
791
+ return None
792
+ self._n_primitives = n_faces
793
+
794
+ ids = np.arange(1, n_faces + 1, dtype=np.uint32)
795
+ ids = np.repeat(ids, 3, axis=0) # repeat id for each vertex
796
+ return ids
@@ -0,0 +1,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) Vispy Development Team. All Rights Reserved.
3
+ # Distributed under the (new) BSD License. See LICENSE.txt for more info.
4
+ import numpy as np
5
+
6
+ from vispy.geometry import create_plane
7
+ from vispy.scene.visuals import Markers, Mesh
8
+ from vispy.testing import requires_application, TestingCanvas
9
+ from vispy.visuals.filters import FacePickingFilter, MarkerPickingFilter
10
+
11
+
12
+ def test_empty_mesh_face_picking():
13
+ mesh = Mesh()
14
+ filter = FacePickingFilter()
15
+ mesh.attach(filter)
16
+ filter.enabled = True
17
+
18
+
19
+ @requires_application()
20
+ def test_mesh_face_picking():
21
+ vertices, faces, _ = create_plane(125, 125)
22
+ vertices = vertices["position"]
23
+ vertices[:, :2] += 125 / 2
24
+ mesh = Mesh(vertices=vertices, faces=faces)
25
+ filter = FacePickingFilter()
26
+ mesh.attach(filter)
27
+
28
+ with TestingCanvas(size=(125, 125)) as c:
29
+ view = c.central_widget.add_view()
30
+ view.add(mesh)
31
+ filter.enabled = True
32
+ mesh.update_gl_state(blend=False)
33
+ picking_render = c.render(bgcolor=(0, 0, 0, 0), alpha=True)
34
+
35
+ # unpack the IDs
36
+ ids = picking_render.view(np.uint32)
37
+ # the plane is made up of two triangles and nearly fills the view
38
+ # pick one point on each triangle
39
+ assert ids[125 // 2, 125 // 4] == 1
40
+ assert ids[125 // 2, 3 * 125 // 4] == 2
41
+
42
+
43
+ def test_empty_markers_picking():
44
+ markers = Markers()
45
+ filter = MarkerPickingFilter()
46
+ markers.attach(filter)
47
+ filter.enabled = True
48
+
49
+
50
+ @requires_application()
51
+ def test_markers_picking():
52
+ markers = Markers(
53
+ pos=np.array([[-0.5, -0.5], [0.5, 0.5]]),
54
+ size=5,
55
+ )
56
+ filter = MarkerPickingFilter()
57
+ markers.attach(filter)
58
+
59
+ with TestingCanvas(size=(125, 125)) as c:
60
+ view = c.central_widget.add_view(camera="panzoom")
61
+ view.camera.rect = (-1, -1, 2, 2)
62
+ view.add(markers)
63
+
64
+ filter.enabled = True
65
+ markers.update_gl_state(blend=False)
66
+ picking_render = c.render(bgcolor=(0, 0, 0, 0), alpha=True)
67
+ ids = picking_render.view(np.uint32)
68
+
69
+ assert ids[3 * 125 // 4, 125 // 4] == 1
70
+ assert ids[125 // 4, 3 * 125 // 4] == 2