vispy 0.13.0__cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.14.0__cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.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.
- vispy/app/backends/_glfw.py +1 -1
- vispy/color/colormap.py +4 -24
- vispy/gloo/texture.py +3 -1
- vispy/scene/canvas.py +1 -4
- vispy/scene/node.py +16 -0
- vispy/scene/tests/test_visuals.py +113 -0
- vispy/scene/visuals.py +4 -1
- vispy/version.py +2 -2
- vispy/visuals/filters/__init__.py +3 -2
- vispy/visuals/filters/base_filter.py +120 -0
- vispy/visuals/filters/markers.py +28 -0
- vispy/visuals/filters/mesh.py +32 -4
- vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
- vispy/visuals/line/dash_atlas.py +46 -41
- vispy/visuals/markers.py +25 -35
- vispy/visuals/text/_sdf_cpu.cpython-38-aarch64-linux-gnu.so +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +21 -23
- vispy/visuals/visual.py +142 -1
- {vispy-0.13.0.dist-info → vispy-0.14.0.dist-info}/METADATA +7 -6
- {vispy-0.13.0.dist-info → vispy-0.14.0.dist-info}/RECORD +433 -431
- {vispy-0.13.0.dist-info → vispy-0.14.0.dist-info}/WHEEL +1 -1
- {vispy-0.13.0.dist-info → vispy-0.14.0.dist-info}/LICENSE.txt +0 -0
- {vispy-0.13.0.dist-info → vispy-0.14.0.dist-info}/top_level.txt +0 -0
vispy/app/backends/_glfw.py
CHANGED
|
@@ -250,7 +250,7 @@ class CanvasBackend(BaseCanvasBackend):
|
|
|
250
250
|
raise ValueError('fullscreen must be <= %s'
|
|
251
251
|
% len(monitor))
|
|
252
252
|
monitor = monitor[p.fullscreen]
|
|
253
|
-
use_size = glfw.get_video_mode(monitor)[:2]
|
|
253
|
+
use_size = glfw.get_video_mode(monitor)[0][:2]
|
|
254
254
|
if use_size != tuple(p.size):
|
|
255
255
|
logger.debug('Requested size %s, will be ignored to '
|
|
256
256
|
'use fullscreen mode %s' % (p.size, use_size))
|
vispy/color/colormap.py
CHANGED
|
@@ -1092,17 +1092,13 @@ _colormaps = dict(
|
|
|
1092
1092
|
)
|
|
1093
1093
|
|
|
1094
1094
|
|
|
1095
|
-
def get_colormap(name
|
|
1096
|
-
"""Obtain a colormap.
|
|
1095
|
+
def get_colormap(name):
|
|
1096
|
+
"""Obtain a colormap by name.
|
|
1097
1097
|
|
|
1098
1098
|
Parameters
|
|
1099
1099
|
----------
|
|
1100
1100
|
name : str | Colormap
|
|
1101
1101
|
Colormap name. Can also be a Colormap for pass-through.
|
|
1102
|
-
*args:
|
|
1103
|
-
Deprecated.
|
|
1104
|
-
**kwargs
|
|
1105
|
-
Deprecated.
|
|
1106
1102
|
|
|
1107
1103
|
Examples
|
|
1108
1104
|
--------
|
|
@@ -1111,18 +1107,10 @@ def get_colormap(name, *args, **kwargs):
|
|
|
1111
1107
|
|
|
1112
1108
|
.. versionchanged: 0.7
|
|
1113
1109
|
|
|
1114
|
-
Additional args/kwargs are no longer accepted. Colormap
|
|
1115
|
-
no longer created on the fly.
|
|
1116
|
-
(``CubeHelixColormap``), ``single_hue`` (``SingleHue``), ``hsl``
|
|
1117
|
-
(``HSL``), ``husl`` (``HSLuv``), ``diverging`` (``Diverging``), or
|
|
1118
|
-
``RdYeBuCy`` (``RedYellowBlueCyan``) colormap you must import and
|
|
1119
|
-
instantiate it directly from the ``vispy.color.colormap`` module.
|
|
1110
|
+
Additional args/kwargs are no longer accepted. Colormap instances are
|
|
1111
|
+
no longer created on the fly.
|
|
1120
1112
|
|
|
1121
1113
|
"""
|
|
1122
|
-
if args or kwargs:
|
|
1123
|
-
warnings.warn("Creating a Colormap instance with 'get_colormap' is "
|
|
1124
|
-
"no longer supported. No additional arguments or "
|
|
1125
|
-
"keyword arguments should be passed.", DeprecationWarning)
|
|
1126
1114
|
if isinstance(name, BaseColormap):
|
|
1127
1115
|
return name
|
|
1128
1116
|
|
|
@@ -1130,14 +1118,6 @@ def get_colormap(name, *args, **kwargs):
|
|
|
1130
1118
|
raise TypeError('colormap must be a Colormap or string name')
|
|
1131
1119
|
if name in _colormaps: # vispy cmap
|
|
1132
1120
|
cmap = _colormaps[name]
|
|
1133
|
-
if name in ("cubehelix", "single_hue", "hsl", "husl", "diverging", "RdYeBuCy"):
|
|
1134
|
-
warnings.warn(
|
|
1135
|
-
f"Colormap '{name}' has been deprecated since vispy 0.7. "
|
|
1136
|
-
f"Please import and create 'vispy.color.colormap.{cmap.__class__.__name__}' "
|
|
1137
|
-
"directly instead.",
|
|
1138
|
-
DeprecationWarning,
|
|
1139
|
-
stacklevel=2,
|
|
1140
|
-
)
|
|
1141
1121
|
|
|
1142
1122
|
elif has_matplotlib(): # matplotlib cmap
|
|
1143
1123
|
try:
|
vispy/gloo/texture.py
CHANGED
|
@@ -33,7 +33,9 @@ def convert_dtype_and_clip(data, dtype, copy=False):
|
|
|
33
33
|
else:
|
|
34
34
|
# to reduce copying, we clip into a pre-generated array of the right dtype
|
|
35
35
|
new_data = np.empty_like(data, dtype=dtype)
|
|
36
|
-
|
|
36
|
+
# allow "unsafe" casting here as we're explicitly clipping to the
|
|
37
|
+
# range of the new dtype - this was a default before numpy 1.25
|
|
38
|
+
np.clip(data, new_min, new_max, out=new_data, casting="unsafe")
|
|
37
39
|
return new_data
|
|
38
40
|
|
|
39
41
|
|
vispy/scene/canvas.py
CHANGED
|
@@ -493,11 +493,8 @@ class SceneCanvas(app.Canvas, Frozen):
|
|
|
493
493
|
than triggering transform updates across the scene with every
|
|
494
494
|
click.
|
|
495
495
|
"""
|
|
496
|
-
|
|
497
|
-
self._scene.picking = True
|
|
496
|
+
with self._scene.set_picking():
|
|
498
497
|
img = self.render(bgcolor=(0, 0, 0, 0), crop=crop)
|
|
499
|
-
finally:
|
|
500
|
-
self._scene.picking = False
|
|
501
498
|
img = img.astype('int32') * [2**0, 2**8, 2**16, 2**24]
|
|
502
499
|
id_ = img.sum(axis=2).astype('int32')
|
|
503
500
|
return id_
|
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,
|
|
@@ -626,3 +627,18 @@ class Node(object):
|
|
|
626
627
|
for c in self.children:
|
|
627
628
|
c.picking = p
|
|
628
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
|
-
|
|
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
|
vispy/version.py
CHANGED
|
@@ -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 .
|
|
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)
|
vispy/visuals/filters/mesh.py
CHANGED
|
@@ -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.
|
|
@@ -756,13 +757,40 @@ class WireframeFilter(Filter):
|
|
|
756
757
|
bc = np.tile(bc[None, ...], (n_faces, 1, 1))
|
|
757
758
|
self._bc.set_data(bc, convert=True)
|
|
758
759
|
|
|
759
|
-
def
|
|
760
|
+
def on_data_updated(self, event):
|
|
760
761
|
self._update_data()
|
|
761
762
|
|
|
762
763
|
def _attach(self, visual):
|
|
763
764
|
super()._attach(visual)
|
|
764
|
-
visual.events.data_updated.connect(self.
|
|
765
|
+
visual.events.data_updated.connect(self.on_data_updated)
|
|
765
766
|
|
|
766
767
|
def _detach(self, visual):
|
|
767
|
-
visual.events.data_updated.disconnect(self.
|
|
768
|
+
visual.events.data_updated.disconnect(self.on_data_updated)
|
|
768
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
|
vispy/visuals/line/dash_atlas.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
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 functools import lru_cache
|
|
6
|
+
|
|
5
7
|
import numpy as np
|
|
6
8
|
|
|
7
9
|
|
|
@@ -40,46 +42,49 @@ class DashAtlas(object):
|
|
|
40
42
|
self._atlas[key] = [self._index / float(self._data.shape[0]), period]
|
|
41
43
|
self._index += 1
|
|
42
44
|
self._dirty = True
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def make_pattern(self, pattern, caps=[1, 1]):
|
|
46
|
-
""" """
|
|
47
|
-
|
|
48
|
-
# A pattern is defined as on/off sequence of segments
|
|
49
|
-
# It must be a multiple of 2
|
|
50
|
-
if len(pattern) > 1 and len(pattern) % 2:
|
|
51
|
-
pattern = [pattern[0] + pattern[-1]] + pattern[1:-1]
|
|
52
|
-
P = np.array(pattern)
|
|
53
|
-
|
|
54
|
-
# Period is the sum of all segment length
|
|
55
|
-
period = np.cumsum(P)[-1]
|
|
56
|
-
|
|
57
|
-
# Find all start and end of on-segment only
|
|
58
|
-
C, c = [], 0
|
|
59
|
-
for i in range(0, len(P) + 2, 2):
|
|
60
|
-
a = max(0.0001, P[i % len(P)])
|
|
61
|
-
b = max(0.0001, P[(i + 1) % len(P)])
|
|
62
|
-
C.extend([c, c + a])
|
|
63
|
-
c += a + b
|
|
64
|
-
C = np.array(C)
|
|
65
|
-
|
|
66
|
-
# Build pattern
|
|
45
|
+
|
|
46
|
+
def make_pattern(self, pattern, caps=(1, 1)):
|
|
67
47
|
length = self._data.shape[1]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
48
|
+
return _make_pattern(length, pattern, caps)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@lru_cache(maxsize=32)
|
|
52
|
+
def _make_pattern(length, pattern, caps):
|
|
53
|
+
"""Make a concrete dash pattern of a given length."""
|
|
54
|
+
# A pattern is defined as on/off sequence of segments
|
|
55
|
+
# It must be a multiple of 2
|
|
56
|
+
if len(pattern) > 1 and len(pattern) % 2:
|
|
57
|
+
pattern = [pattern[0] + pattern[-1]] + pattern[1:-1]
|
|
58
|
+
P = np.array(pattern)
|
|
59
|
+
|
|
60
|
+
# Period is the sum of all segment length
|
|
61
|
+
period = np.cumsum(P)[-1]
|
|
62
|
+
|
|
63
|
+
# Find all start and end of on-segment only
|
|
64
|
+
C, c = [], 0
|
|
65
|
+
for i in range(0, len(P) + 2, 2):
|
|
66
|
+
a = max(0.0001, P[i % len(P)])
|
|
67
|
+
b = max(0.0001, P[(i + 1) % len(P)])
|
|
68
|
+
C.extend([c, c + a])
|
|
69
|
+
c += a + b
|
|
70
|
+
C = np.array(C)
|
|
71
|
+
|
|
72
|
+
# Build pattern
|
|
73
|
+
Z = np.zeros((length, 4), dtype=np.float32)
|
|
74
|
+
for i in np.arange(0, len(Z)):
|
|
75
|
+
x = period * (i) / float(len(Z) - 1)
|
|
76
|
+
index = np.argmin(abs(C - (x)))
|
|
77
|
+
if index % 2 == 0:
|
|
78
|
+
if x <= C[index]:
|
|
79
|
+
dash_type = +1
|
|
80
|
+
else:
|
|
81
|
+
dash_type = 0
|
|
82
|
+
dash_start, dash_end = C[index], C[index + 1]
|
|
83
|
+
else:
|
|
84
|
+
if x > C[index]:
|
|
85
|
+
dash_type = -1
|
|
78
86
|
else:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
dash_start, dash_end = C[index - 1], C[index]
|
|
84
|
-
Z[i] = C[index], dash_type, dash_start, dash_end
|
|
85
|
-
return Z, period
|
|
87
|
+
dash_type = 0
|
|
88
|
+
dash_start, dash_end = C[index - 1], C[index]
|
|
89
|
+
Z[i] = C[index], dash_type, dash_start, dash_end
|
|
90
|
+
return Z, period
|