vispy 0.12.2__cp39-cp39-macosx_10_9_x86_64.whl → 0.14.0__cp39-cp39-macosx_10_9_x86_64.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/app/backends/_qt.py +78 -53
- vispy/color/colormap.py +4 -24
- vispy/gloo/texture.py +9 -4
- vispy/scene/cameras/base_camera.py +4 -0
- vispy/scene/cameras/panzoom.py +4 -1
- vispy/scene/cameras/perspective.py +6 -0
- vispy/scene/cameras/tests/test_perspective.py +37 -0
- vispy/scene/canvas.py +9 -5
- vispy/scene/events.py +9 -0
- vispy/scene/node.py +18 -1
- vispy/scene/tests/test_visuals.py +113 -0
- vispy/scene/visuals.py +5 -1
- vispy/version.py +2 -2
- vispy/visuals/__init__.py +1 -0
- 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 +61 -6
- vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
- vispy/visuals/instanced_mesh.py +152 -0
- vispy/visuals/line/dash_atlas.py +46 -41
- vispy/visuals/markers.py +49 -24
- vispy/visuals/tests/test_instanced_mesh.py +50 -0
- vispy/visuals/text/_sdf_cpu.cpython-39-darwin.so +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +21 -23
- vispy/visuals/visual.py +142 -1
- vispy/visuals/volume.py +19 -10
- {vispy-0.12.2.dist-info → vispy-0.14.0.dist-info}/METADATA +7 -6
- {vispy-0.12.2.dist-info → vispy-0.14.0.dist-info}/RECORD +33 -29
- {vispy-0.12.2.dist-info → vispy-0.14.0.dist-info}/WHEEL +1 -1
- {vispy-0.12.2.dist-info → vispy-0.14.0.dist-info}/LICENSE.txt +0 -0
- {vispy-0.12.2.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/app/backends/_qt.py
CHANGED
|
@@ -22,6 +22,7 @@ known to cause unpredictable behavior and segfaults.
|
|
|
22
22
|
from __future__ import division
|
|
23
23
|
|
|
24
24
|
from time import sleep, time
|
|
25
|
+
import math
|
|
25
26
|
import os
|
|
26
27
|
import sys
|
|
27
28
|
import atexit
|
|
@@ -410,17 +411,10 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
410
411
|
# either not PyQt5 backend or no parent window available
|
|
411
412
|
pass
|
|
412
413
|
|
|
413
|
-
#
|
|
414
|
-
#
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if sys.platform == 'darwin':
|
|
418
|
-
if PYQT6_API:
|
|
419
|
-
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents)
|
|
420
|
-
self.grabGesture(QtCore.Qt.GestureType.PinchGesture)
|
|
421
|
-
else:
|
|
422
|
-
self.setAttribute(QtCore.Qt.WA_AcceptTouchEvents)
|
|
423
|
-
self.grabGesture(QtCore.Qt.PinchGesture)
|
|
414
|
+
# QNativeGestureEvent does not keep track of last or total
|
|
415
|
+
# values like QGestureEvent does
|
|
416
|
+
self._native_gesture_scale_values = []
|
|
417
|
+
self._native_gesture_rotation_values = []
|
|
424
418
|
|
|
425
419
|
def screen_changed(self, new_screen):
|
|
426
420
|
"""Window moved from one display to another, resize canvas.
|
|
@@ -563,50 +557,81 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
563
557
|
def keyReleaseEvent(self, ev):
|
|
564
558
|
self._keyEvent(self._vispy_canvas.events.key_release, ev)
|
|
565
559
|
|
|
560
|
+
def _handle_native_gesture_event(self, ev):
|
|
561
|
+
if self._vispy_canvas is None:
|
|
562
|
+
return
|
|
563
|
+
t = ev.gestureType()
|
|
564
|
+
# this is a workaround for what looks like a Qt bug where
|
|
565
|
+
# QNativeGestureEvent gives the wrong local position.
|
|
566
|
+
# See: https://bugreports.qt.io/browse/QTBUG-59595
|
|
567
|
+
try:
|
|
568
|
+
pos = self.mapFromGlobal(ev.globalPosition().toPoint())
|
|
569
|
+
except AttributeError:
|
|
570
|
+
# globalPos is deprecated in Qt6
|
|
571
|
+
pos = self.mapFromGlobal(ev.globalPos())
|
|
572
|
+
pos = pos.x(), pos.y()
|
|
573
|
+
|
|
574
|
+
if t == QtCore.Qt.NativeGestureType.BeginNativeGesture:
|
|
575
|
+
self._vispy_canvas.events.touch(
|
|
576
|
+
type='gesture_begin',
|
|
577
|
+
pos=_get_event_xy(ev),
|
|
578
|
+
)
|
|
579
|
+
elif t == QtCore.Qt.NativeGestureType.EndNativeGesture:
|
|
580
|
+
self._native_touch_total_rotation = []
|
|
581
|
+
self._native_touch_total_scale = []
|
|
582
|
+
self._vispy_canvas.events.touch(
|
|
583
|
+
type='gesture_end',
|
|
584
|
+
pos=_get_event_xy(ev),
|
|
585
|
+
)
|
|
586
|
+
elif t == QtCore.Qt.NativeGestureType.RotateNativeGesture:
|
|
587
|
+
angle = ev.value()
|
|
588
|
+
last_angle = (
|
|
589
|
+
self._native_gesture_rotation_values[-1]
|
|
590
|
+
if self._native_gesture_rotation_values
|
|
591
|
+
else None
|
|
592
|
+
)
|
|
593
|
+
self._native_gesture_rotation_values.append(angle)
|
|
594
|
+
total_rotation_angle = math.fsum(self._native_gesture_rotation_values)
|
|
595
|
+
self._vispy_canvas.events.touch(
|
|
596
|
+
type="gesture_rotate",
|
|
597
|
+
pos=pos,
|
|
598
|
+
rotation=angle,
|
|
599
|
+
last_rotation=last_angle,
|
|
600
|
+
total_rotation_angle=total_rotation_angle,
|
|
601
|
+
)
|
|
602
|
+
elif t == QtCore.Qt.NativeGestureType.ZoomNativeGesture:
|
|
603
|
+
scale = ev.value()
|
|
604
|
+
last_scale = (
|
|
605
|
+
self._native_gesture_scale_values[-1]
|
|
606
|
+
if self._native_gesture_scale_values
|
|
607
|
+
else None
|
|
608
|
+
)
|
|
609
|
+
self._native_gesture_scale_values.append(scale)
|
|
610
|
+
total_scale_factor = math.fsum(self._native_gesture_scale_values)
|
|
611
|
+
self._vispy_canvas.events.touch(
|
|
612
|
+
type="gesture_zoom",
|
|
613
|
+
pos=pos,
|
|
614
|
+
last_scale=last_scale,
|
|
615
|
+
scale=scale,
|
|
616
|
+
total_scale_factor=total_scale_factor,
|
|
617
|
+
)
|
|
618
|
+
# QtCore.Qt.NativeGestureType.PanNativeGesture
|
|
619
|
+
# Qt6 docs seem to imply this is only supported on Wayland but I have
|
|
620
|
+
# not been able to test it.
|
|
621
|
+
# Two finger pan events are anyway converted to scroll/wheel events.
|
|
622
|
+
# On macOS, more fingers are usually swallowed by the OS (by spaces,
|
|
623
|
+
# mission control, etc.).
|
|
624
|
+
|
|
566
625
|
def event(self, ev):
|
|
567
626
|
out = super(QtBaseCanvasBackend, self).event(ev)
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if t == qt_event_types.Gesture:
|
|
577
|
-
pinch_gesture = QtCore.Qt.GestureType.PinchGesture if PYQT6_API else QtCore.Qt.PinchGesture
|
|
578
|
-
gesture = ev.gesture(pinch_gesture)
|
|
579
|
-
if gesture:
|
|
580
|
-
(x, y) = _get_qpoint_pos(gesture.centerPoint())
|
|
581
|
-
scale = gesture.scaleFactor()
|
|
582
|
-
last_scale = gesture.lastScaleFactor()
|
|
583
|
-
rotation = gesture.rotationAngle()
|
|
584
|
-
self._vispy_canvas.events.touch(
|
|
585
|
-
type="pinch",
|
|
586
|
-
pos=(x, y),
|
|
587
|
-
last_pos=None,
|
|
588
|
-
scale=scale,
|
|
589
|
-
last_scale=last_scale,
|
|
590
|
-
rotation=rotation,
|
|
591
|
-
total_rotation_angle=gesture.totalRotationAngle(),
|
|
592
|
-
total_scale_factor=gesture.totalScaleFactor(),
|
|
593
|
-
)
|
|
594
|
-
# General touch event.
|
|
595
|
-
elif t == qt_event_types.TouchUpdate:
|
|
596
|
-
if qt_lib == 'pyqt6' or qt_lib == 'pyside6':
|
|
597
|
-
points = ev.points()
|
|
598
|
-
# These variables are lists of (x, y) coordinates.
|
|
599
|
-
pos = [_get_qpoint_pos(p.position()) for p in points]
|
|
600
|
-
lpos = [_get_qpoint_pos(p.lastPosition()) for p in points]
|
|
601
|
-
else:
|
|
602
|
-
points = ev.touchPoints()
|
|
603
|
-
# These variables are lists of (x, y) coordinates.
|
|
604
|
-
pos = [_get_qpoint_pos(p.pos()) for p in points]
|
|
605
|
-
lpos = [_get_qpoint_pos(p.lastPos()) for p in points]
|
|
606
|
-
self._vispy_canvas.events.touch(type='touch',
|
|
607
|
-
pos=pos,
|
|
608
|
-
last_pos=lpos,
|
|
609
|
-
)
|
|
627
|
+
|
|
628
|
+
# QNativeGestureEvent is Qt 5+
|
|
629
|
+
if (
|
|
630
|
+
(QT5_NEW_API or PYSIDE6_API or PYQT6_API)
|
|
631
|
+
and isinstance(ev, QtGui.QNativeGestureEvent)
|
|
632
|
+
):
|
|
633
|
+
self._handle_native_gesture_event(ev)
|
|
634
|
+
|
|
610
635
|
return out
|
|
611
636
|
|
|
612
637
|
def _keyEvent(self, func, ev):
|
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,17 +33,22 @@ 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
|
|
|
40
|
-
def downcast_to_32bit_if_needed(data, copy=False):
|
|
42
|
+
def downcast_to_32bit_if_needed(data, copy=False, dtype=None):
|
|
41
43
|
"""Downcast to 32bit dtype if necessary."""
|
|
42
|
-
dtype
|
|
44
|
+
if dtype is None:
|
|
45
|
+
dtype = data.dtype
|
|
46
|
+
dtype = np.dtype(dtype)
|
|
43
47
|
if dtype.itemsize > 4:
|
|
44
48
|
warnings.warn(
|
|
45
49
|
f"GPUs can't support dtypes bigger than 32-bit, but got '{dtype}'. "
|
|
46
|
-
"Precision will be lost due to downcasting to 32-bit."
|
|
50
|
+
"Precision will be lost due to downcasting to 32-bit.",
|
|
51
|
+
stacklevel=2,
|
|
47
52
|
)
|
|
48
53
|
|
|
49
54
|
size = min(dtype.itemsize, 4)
|
|
@@ -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
|
vispy/scene/cameras/panzoom.py
CHANGED
|
@@ -207,7 +207,10 @@ class PanZoomCamera(BaseCamera):
|
|
|
207
207
|
center = self._scene_transform.imap(event.pos)
|
|
208
208
|
self.zoom((1 + self.zoom_factor)**(-event.delta[1] * 30), center)
|
|
209
209
|
event.handled = True
|
|
210
|
-
|
|
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
|
|
211
214
|
elif event.type == 'mouse_move':
|
|
212
215
|
if event.press_event is None:
|
|
213
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):
|
|
@@ -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()
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
vispy/version.py
CHANGED
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 .
|
|
9
|
+
from .markers import MarkerPickingFilter # noqa
|
|
10
|
+
from .mesh import TextureFilter, ShadingFilter, InstancedShadingFilter, WireframeFilter, FacePickingFilter # noqa
|