vispy 0.11.0__cp310-cp310-macosx_10_9_x86_64.whl → 0.14.0__cp310-cp310-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 +2 -2
- vispy/app/backends/_pyglet.py +2 -2
- vispy/app/backends/_qt.py +86 -61
- vispy/app/backends/_wx.py +1 -1
- vispy/app/canvas.py +4 -2
- vispy/app/tests/test_canvas.py +15 -1
- vispy/app/tests/test_context.py +5 -3
- vispy/color/color_array.py +8 -1
- vispy/color/colormap.py +5 -25
- vispy/geometry/meshdata.py +76 -38
- vispy/geometry/tests/test_meshdata.py +72 -0
- vispy/gloo/buffer.py +12 -0
- vispy/gloo/gl/_constants.py +3 -3
- vispy/gloo/glir.py +23 -13
- vispy/gloo/program.py +27 -10
- vispy/gloo/tests/test_program.py +9 -2
- vispy/gloo/tests/test_texture.py +15 -0
- vispy/gloo/texture.py +42 -14
- vispy/glsl/build_spatial_filters.py +4 -4
- vispy/glsl/misc/spatial-filters.frag +2 -2
- vispy/io/datasets.py +1 -1
- vispy/io/image.py +1 -1
- vispy/scene/cameras/base_camera.py +6 -2
- vispy/scene/cameras/panzoom.py +4 -1
- vispy/scene/cameras/perspective.py +6 -0
- vispy/scene/cameras/tests/test_cameras.py +27 -0
- vispy/scene/cameras/tests/test_perspective.py +37 -0
- vispy/scene/cameras/turntable.py +39 -23
- 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/scene/widgets/viewbox.py +3 -2
- vispy/testing/_runners.py +6 -12
- vispy/testing/_testing.py +3 -4
- vispy/util/check_environment.py +4 -4
- vispy/util/gallery_scraper.py +50 -32
- vispy/util/tests/test_gallery_scraper.py +2 -0
- vispy/util/wrappers.py +1 -1
- vispy/version.py +2 -3
- vispy/visuals/__init__.py +1 -0
- vispy/visuals/_scalable_textures.py +20 -16
- vispy/visuals/collections/array_list.py +3 -3
- vispy/visuals/collections/base_collection.py +1 -1
- vispy/visuals/ellipse.py +1 -1
- vispy/visuals/filters/__init__.py +3 -2
- vispy/visuals/filters/base_filter.py +120 -0
- vispy/visuals/filters/clipping_planes.py +14 -6
- 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/graphs/graph.py +1 -1
- vispy/visuals/image.py +27 -18
- vispy/visuals/instanced_mesh.py +152 -0
- vispy/visuals/isocurve.py +1 -1
- vispy/visuals/line/dash_atlas.py +46 -41
- vispy/visuals/markers.py +310 -384
- vispy/visuals/mesh.py +2 -2
- vispy/visuals/tests/test_axis.py +2 -2
- vispy/visuals/tests/test_image.py +64 -20
- vispy/visuals/tests/test_instanced_mesh.py +50 -0
- vispy/visuals/tests/test_markers.py +6 -0
- vispy/visuals/tests/test_mesh.py +17 -0
- vispy/visuals/tests/test_volume.py +77 -5
- vispy/visuals/text/_sdf_cpu.cpython-310-darwin.so +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +21 -23
- vispy/visuals/tube.py +2 -2
- vispy/visuals/visual.py +144 -3
- vispy/visuals/volume.py +36 -16
- {vispy-0.11.0.dist-info → vispy-0.14.0.dist-info}/LICENSE.txt +1 -1
- {vispy-0.11.0.dist-info → vispy-0.14.0.dist-info}/METADATA +28 -6
- {vispy-0.11.0.dist-info → vispy-0.14.0.dist-info}/RECORD +519 -514
- {vispy-0.11.0.dist-info → vispy-0.14.0.dist-info}/WHEEL +1 -1
- {vispy-0.11.0.dist-info → vispy-0.14.0.dist-info}/top_level.txt +0 -0
vispy/app/backends/_glfw.py
CHANGED
|
@@ -27,7 +27,7 @@ glfw = None
|
|
|
27
27
|
try:
|
|
28
28
|
import glfw
|
|
29
29
|
except ImportError:
|
|
30
|
-
why_not = "Could not import
|
|
30
|
+
why_not = "Could not import glfw, you may need to `pip install glfw` first."
|
|
31
31
|
available, testable, why_not, which = False, False, why_not, None
|
|
32
32
|
except Exception as err:
|
|
33
33
|
why_not = "Error importing glfw: " + str(err)
|
|
@@ -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/_pyglet.py
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
from __future__ import division
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from packaging.version import Version
|
|
10
10
|
from time import sleep
|
|
11
11
|
|
|
12
12
|
from ..base import (BaseApplicationBackend, BaseCanvasBackend,
|
|
@@ -23,7 +23,7 @@ USE_EGL = config['gl_backend'].lower().startswith('es')
|
|
|
23
23
|
try:
|
|
24
24
|
import pyglet
|
|
25
25
|
version = pyglet.version
|
|
26
|
-
if
|
|
26
|
+
if Version(version) < Version('1.2'):
|
|
27
27
|
help_ = ('You can install the latest pyglet using:\n '
|
|
28
28
|
'pip install http://pyglet.googlecode.com/archive/tip.zip')
|
|
29
29
|
raise ImportError('Pyglet version too old (%s), need >= 1.2\n%s'
|
vispy/app/backends/_qt.py
CHANGED
|
@@ -22,11 +22,12 @@ 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
|
|
28
29
|
import ctypes
|
|
29
|
-
from
|
|
30
|
+
from packaging.version import Version
|
|
30
31
|
|
|
31
32
|
from ...util import logger
|
|
32
33
|
from ..base import (BaseApplicationBackend, BaseCanvasBackend,
|
|
@@ -90,7 +91,7 @@ elif qt_lib == 'pyqt5':
|
|
|
90
91
|
_check_imports('PyQt5')
|
|
91
92
|
if not USE_EGL:
|
|
92
93
|
from PyQt5.QtCore import QT_VERSION_STR
|
|
93
|
-
if
|
|
94
|
+
if Version(QT_VERSION_STR) >= Version('5.4.0'):
|
|
94
95
|
from PyQt5.QtWidgets import QOpenGLWidget as QGLWidget
|
|
95
96
|
from PyQt5.QtGui import QSurfaceFormat as QGLFormat
|
|
96
97
|
QT5_NEW_API = True
|
|
@@ -102,7 +103,7 @@ elif qt_lib == 'pyqt6':
|
|
|
102
103
|
_check_imports('PyQt6')
|
|
103
104
|
if not USE_EGL:
|
|
104
105
|
from PyQt6.QtCore import QT_VERSION_STR
|
|
105
|
-
if
|
|
106
|
+
if Version(QT_VERSION_STR) >= Version('6.0.0'):
|
|
106
107
|
from PyQt6.QtOpenGLWidgets import QOpenGLWidget as QGLWidget
|
|
107
108
|
from PyQt6.QtGui import QSurfaceFormat as QGLFormat
|
|
108
109
|
PYQT6_API = True
|
|
@@ -114,7 +115,7 @@ elif qt_lib == 'pyside6':
|
|
|
114
115
|
_check_imports('PySide6')
|
|
115
116
|
if not USE_EGL:
|
|
116
117
|
from PySide6.QtCore import __version__ as QT_VERSION_STR
|
|
117
|
-
if
|
|
118
|
+
if Version(QT_VERSION_STR) >= Version('6.0.0'):
|
|
118
119
|
from PySide6.QtOpenGLWidgets import QOpenGLWidget as QGLWidget
|
|
119
120
|
from PySide6.QtGui import QSurfaceFormat as QGLFormat
|
|
120
121
|
PYSIDE6_API = True
|
|
@@ -126,7 +127,7 @@ elif qt_lib == 'pyside2':
|
|
|
126
127
|
_check_imports('PySide2')
|
|
127
128
|
if not USE_EGL:
|
|
128
129
|
from PySide2.QtCore import __version__ as QT_VERSION_STR
|
|
129
|
-
if
|
|
130
|
+
if Version(QT_VERSION_STR) >= Version('5.4.0'):
|
|
130
131
|
from PySide2.QtWidgets import QOpenGLWidget as QGLWidget
|
|
131
132
|
from PySide2.QtGui import QSurfaceFormat as QGLFormat
|
|
132
133
|
QT5_NEW_API = True
|
|
@@ -191,7 +192,7 @@ KEYMAP = {
|
|
|
191
192
|
qt_keys.Key_Return: keys.ENTER,
|
|
192
193
|
qt_keys.Key_Tab: keys.TAB,
|
|
193
194
|
}
|
|
194
|
-
if PYQT6_API:
|
|
195
|
+
if PYQT6_API or PYSIDE6_API:
|
|
195
196
|
BUTTONMAP = {
|
|
196
197
|
QtCore.Qt.MouseButton.NoButton: 0,
|
|
197
198
|
QtCore.Qt.MouseButton.LeftButton: 1,
|
|
@@ -278,11 +279,11 @@ def _set_config(c):
|
|
|
278
279
|
glformat.setGreenBufferSize(c['green_size'])
|
|
279
280
|
glformat.setBlueBufferSize(c['blue_size'])
|
|
280
281
|
glformat.setAlphaBufferSize(c['alpha_size'])
|
|
281
|
-
if QT5_NEW_API
|
|
282
|
+
if QT5_NEW_API:
|
|
282
283
|
# Qt5 >= 5.4.0 - below options automatically enabled if nonzero.
|
|
283
284
|
glformat.setSwapBehavior(glformat.DoubleBuffer if c['double_buffer']
|
|
284
285
|
else glformat.SingleBuffer)
|
|
285
|
-
elif PYQT6_API:
|
|
286
|
+
elif PYQT6_API or PYSIDE6_API:
|
|
286
287
|
glformat.setSwapBehavior(glformat.SwapBehavior.DoubleBuffer if c['double_buffer']
|
|
287
288
|
else glformat.SwapBehavior.SingleBuffer)
|
|
288
289
|
else:
|
|
@@ -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/app/backends/_wx.py
CHANGED
|
@@ -466,7 +466,7 @@ class TimerBackend(BaseTimerBackend):
|
|
|
466
466
|
parent.Bind(wx.EVT_TIMER, self._vispy_timeout, self._timer)
|
|
467
467
|
|
|
468
468
|
def _vispy_start(self, interval):
|
|
469
|
-
self._timer.Start(interval * 1000
|
|
469
|
+
self._timer.Start(int(interval * 1000.), False)
|
|
470
470
|
|
|
471
471
|
def _vispy_stop(self):
|
|
472
472
|
self._timer.Stop()
|
vispy/app/canvas.py
CHANGED
|
@@ -606,7 +606,7 @@ class MouseEvent(Event):
|
|
|
606
606
|
will return the button that started the drag (same thing as
|
|
607
607
|
``event.press_event.button``).
|
|
608
608
|
buttons : [int, ...]
|
|
609
|
-
The list of buttons
|
|
609
|
+
The list of buttons pressed during this event.
|
|
610
610
|
modifiers : tuple of Key instances
|
|
611
611
|
Tuple that specifies which modifier keys were pressed down at the
|
|
612
612
|
time of the event (shift, control, alt, meta).
|
|
@@ -632,7 +632,9 @@ class MouseEvent(Event):
|
|
|
632
632
|
Event.__init__(self, type, **kwargs)
|
|
633
633
|
self._pos = np.array([0, 0]) if (pos is None) else np.array(pos)
|
|
634
634
|
self._button = int(button) if (button is not None) else None
|
|
635
|
-
|
|
635
|
+
# Explicitly add button to buttons if newly pressed, check #2344 for more reference
|
|
636
|
+
newly_pressed_buttons = [button] if button is not None and type == 'mouse_press' else []
|
|
637
|
+
self._buttons = [] if (buttons is None) else buttons + newly_pressed_buttons
|
|
636
638
|
self._modifiers = tuple(modifiers or ())
|
|
637
639
|
self._delta = np.zeros(2) if (delta is None) else np.array(delta)
|
|
638
640
|
self._last_event = last_event
|
vispy/app/tests/test_canvas.py
CHANGED
|
@@ -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.app import Canvas
|
|
5
|
+
from vispy.app import Canvas, MouseEvent
|
|
6
6
|
from vispy.visuals import ImageVisual
|
|
7
7
|
from vispy.testing import requires_application
|
|
8
8
|
from vispy.visuals.transforms import STTransform
|
|
@@ -106,3 +106,17 @@ def test_blend_presets(preset):
|
|
|
106
106
|
|
|
107
107
|
rgba_result = c.render()
|
|
108
108
|
assert not np.allclose(rgba_result[..., :3], 0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@requires_application()
|
|
112
|
+
@pytest.mark.parametrize("mouse_event_type, button, buttons, expected_button, expected_buttons", [
|
|
113
|
+
('mouse_press', 1, [], 1, [1]),
|
|
114
|
+
('mouse_release', 1, [1], 1, [1]),
|
|
115
|
+
# left click pressed and held, followed by a right click
|
|
116
|
+
('mouse_press', 2, [1], 2, [1, 2]),
|
|
117
|
+
('mouse_release', 2, [1, 2], 2, [1, 2]),
|
|
118
|
+
])
|
|
119
|
+
def test_mouse_event(mouse_event_type, button, buttons, expected_button, expected_buttons):
|
|
120
|
+
mev = MouseEvent(type=mouse_event_type, button=button, buttons=buttons)
|
|
121
|
+
assert mev.buttons == expected_buttons
|
|
122
|
+
assert mev.button == expected_button
|
vispy/app/tests/test_context.py
CHANGED
|
@@ -17,6 +17,8 @@ def test_context_properties():
|
|
|
17
17
|
return # cannot set more than once on Pyglet
|
|
18
18
|
if a.backend_name.lower() == 'osmesa':
|
|
19
19
|
return # cannot set config on OSMesa
|
|
20
|
+
if 'pyqt5' in a.backend_name.lower() or 'pyqt6' in a.backend_name.lower() or 'pyside2' in a.backend_name.lower() or 'pyside6' in a.backend_name.lower():
|
|
21
|
+
pytest.xfail("Context sharing is not supported in PyQt5, PyQt6, PySide2, or PySide6 at this time.")
|
|
20
22
|
|
|
21
23
|
# stereo, double buffer won't work on every sys
|
|
22
24
|
configs = [dict(samples=4), dict(stencil_size=8),
|
|
@@ -68,9 +70,9 @@ def test_context_sharing():
|
|
|
68
70
|
# Check while c1 is active
|
|
69
71
|
check()
|
|
70
72
|
|
|
71
|
-
# pyqt5 does not currently support context sharing
|
|
72
|
-
if 'pyqt5' in c1.app.backend_name.lower() or 'pyqt6' in c1.app.backend_name.lower():
|
|
73
|
-
pytest.xfail("Context sharing is not supported in PyQt5 at this time.")
|
|
73
|
+
# pyqt5 does not currently support context sharing, pyside6 seg faults on app tests
|
|
74
|
+
if 'pyqt5' in c1.app.backend_name.lower() or 'pyqt6' in c1.app.backend_name.lower() or 'pyside2' in c1.app.backend_name.lower() or 'pyside6' in c1.app.backend_name.lower():
|
|
75
|
+
pytest.xfail("Context sharing is not supported in PyQt5, PyQt6, PySide2, or PySide6 at this time.")
|
|
74
76
|
|
|
75
77
|
# Tkinter does not currently support context sharing
|
|
76
78
|
if 'tk' in c1.app.backend_name.lower():
|
vispy/color/color_array.py
CHANGED
|
@@ -21,7 +21,7 @@ def _string_to_rgb(color):
|
|
|
21
21
|
if not color.startswith('#'):
|
|
22
22
|
if color.lower() not in _color_dict:
|
|
23
23
|
raise ValueError('Color "%s" unknown' % color)
|
|
24
|
-
color = _color_dict[color]
|
|
24
|
+
color = _color_dict[color.lower()]
|
|
25
25
|
assert color[0] == '#'
|
|
26
26
|
# hex color
|
|
27
27
|
color = color[1:]
|
|
@@ -163,6 +163,13 @@ class ColorArray(object):
|
|
|
163
163
|
"""Helper to get the class name once it's been created"""
|
|
164
164
|
return cls.__name__
|
|
165
165
|
|
|
166
|
+
def __array__(self, dtype=None):
|
|
167
|
+
"""Get a standard numpy array representing RGBA."""
|
|
168
|
+
rgba = self.rgba
|
|
169
|
+
if dtype is not None:
|
|
170
|
+
rgba = rgba.astype(dtype)
|
|
171
|
+
return rgba
|
|
172
|
+
|
|
166
173
|
def __len__(self):
|
|
167
174
|
return self._rgba.shape[0]
|
|
168
175
|
|
vispy/color/colormap.py
CHANGED
|
@@ -435,7 +435,7 @@ class Colormap(BaseColormap):
|
|
|
435
435
|
if self.texture_map_data is None:
|
|
436
436
|
return None
|
|
437
437
|
interp = 'linear' if self.interpolation == 'linear' else 'nearest'
|
|
438
|
-
texture_LUT = vispy.gloo.Texture2D(np.zeros(self.texture_map_data.shape),
|
|
438
|
+
texture_LUT = vispy.gloo.Texture2D(np.zeros(self.texture_map_data.shape, dtype=np.float32),
|
|
439
439
|
interpolation=interp)
|
|
440
440
|
texture_LUT.set_data(self.texture_map_data, offset=None, copy=True)
|
|
441
441
|
return texture_LUT
|
|
@@ -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/geometry/meshdata.py
CHANGED
|
@@ -19,6 +19,56 @@ def _fix_colors(colors):
|
|
|
19
19
|
return colors
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _compute_face_normals(vertices):
|
|
23
|
+
if vertices.shape[1:] != (3, 3):
|
|
24
|
+
raise ValueError("Expected (N, 3, 3) array of vertices repeated on"
|
|
25
|
+
f" the triangle corners, got {vertices.shape}.")
|
|
26
|
+
edges1 = vertices[:, 1] - vertices[:, 0]
|
|
27
|
+
edges2 = vertices[:, 2] - vertices[:, 0]
|
|
28
|
+
return np.cross(edges1, edges2)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _repeat_face_normals_on_corners(normals):
|
|
32
|
+
if normals.shape[1:] != (3,):
|
|
33
|
+
raise ValueError("Expected (F, 3) array of face normals, got"
|
|
34
|
+
f" {normals.shape}.")
|
|
35
|
+
n_corners_in_face = 3
|
|
36
|
+
new_shape = (normals.shape[0], n_corners_in_face, normals.shape[1])
|
|
37
|
+
return np.repeat(normals, n_corners_in_face, axis=0).reshape(new_shape)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _compute_vertex_normals(face_normals, faces, vertices):
|
|
41
|
+
if face_normals.shape[1:] != (3,):
|
|
42
|
+
raise ValueError("Expected (F, 3) array of face normals, got"
|
|
43
|
+
f" {face_normals.shape}.")
|
|
44
|
+
if faces.shape[1:] != (3,):
|
|
45
|
+
raise ValueError("Expected (F, 3) array of face vertex indices, got"
|
|
46
|
+
f" {faces.shape}.")
|
|
47
|
+
if vertices.shape[1:] != (3,):
|
|
48
|
+
raise ValueError("Expected (N, 3) array of vertices, got"
|
|
49
|
+
f" {vertices.shape}.")
|
|
50
|
+
|
|
51
|
+
vertex_normals = np.zeros_like(vertices)
|
|
52
|
+
n_corners_in_triangle = 3
|
|
53
|
+
face_normals_repeated_on_corners = np.repeat(face_normals,
|
|
54
|
+
n_corners_in_triangle,
|
|
55
|
+
axis=0)
|
|
56
|
+
# NOTE: The next line is equivalent to
|
|
57
|
+
#
|
|
58
|
+
# vertex_normals[self._faces.ravel()] += face_normals_repeated_on_corners
|
|
59
|
+
#
|
|
60
|
+
# except that it accumulates the values from the right hand side at
|
|
61
|
+
# repeated indices on the left hand side, instead of overwritting them,
|
|
62
|
+
# like in the above.
|
|
63
|
+
np.add.at(vertex_normals, faces.ravel(), face_normals_repeated_on_corners)
|
|
64
|
+
|
|
65
|
+
norms = np.sqrt((vertex_normals**2).sum(axis=1))
|
|
66
|
+
nonzero_norms = norms > 0
|
|
67
|
+
vertex_normals[nonzero_norms] /= norms[nonzero_norms][:, None]
|
|
68
|
+
|
|
69
|
+
return vertex_normals
|
|
70
|
+
|
|
71
|
+
|
|
22
72
|
class MeshData(object):
|
|
23
73
|
"""
|
|
24
74
|
Class for storing and operating on 3D mesh data.
|
|
@@ -141,7 +191,7 @@ class MeshData(object):
|
|
|
141
191
|
self._compute_edges(indexed='faces')
|
|
142
192
|
return self._edges_indexed_by_faces
|
|
143
193
|
else:
|
|
144
|
-
raise
|
|
194
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
145
195
|
|
|
146
196
|
def set_faces(self, faces):
|
|
147
197
|
"""Set the faces
|
|
@@ -191,7 +241,7 @@ class MeshData(object):
|
|
|
191
241
|
self._vertices[self.get_faces()]
|
|
192
242
|
return self._vertices_indexed_by_faces
|
|
193
243
|
else:
|
|
194
|
-
raise
|
|
244
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
195
245
|
|
|
196
246
|
def get_bounds(self):
|
|
197
247
|
"""Get the mesh bounds
|
|
@@ -234,7 +284,7 @@ class MeshData(object):
|
|
|
234
284
|
if verts is not None:
|
|
235
285
|
self._vertices_indexed_by_faces = verts
|
|
236
286
|
else:
|
|
237
|
-
raise
|
|
287
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
238
288
|
|
|
239
289
|
if reset_normals:
|
|
240
290
|
self.reset_normals()
|
|
@@ -293,22 +343,19 @@ class MeshData(object):
|
|
|
293
343
|
normals : ndarray
|
|
294
344
|
The normals.
|
|
295
345
|
"""
|
|
346
|
+
if indexed not in (None, 'faces'):
|
|
347
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
348
|
+
|
|
296
349
|
if self._face_normals is None:
|
|
297
|
-
|
|
298
|
-
self._face_normals =
|
|
299
|
-
v[:, 2] - v[:, 0])
|
|
350
|
+
vertices = self.get_vertices(indexed='faces')
|
|
351
|
+
self._face_normals = _compute_face_normals(vertices)
|
|
300
352
|
|
|
301
|
-
if indexed is None:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
norms[:] = self._face_normals[:, np.newaxis, :]
|
|
308
|
-
self._face_normals_indexed_by_faces = norms
|
|
309
|
-
return self._face_normals_indexed_by_faces
|
|
310
|
-
else:
|
|
311
|
-
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
|
|
353
|
+
if indexed == 'faces' and self._face_normals_indexed_by_faces is None:
|
|
354
|
+
self._face_normals_indexed_by_faces = \
|
|
355
|
+
_repeat_face_normals_on_corners(self._face_normals)
|
|
356
|
+
|
|
357
|
+
return (self._face_normals if indexed is None
|
|
358
|
+
else self._face_normals_indexed_by_faces)
|
|
312
359
|
|
|
313
360
|
def get_vertex_normals(self, indexed=None):
|
|
314
361
|
"""Get vertex normals
|
|
@@ -326,29 +373,20 @@ class MeshData(object):
|
|
|
326
373
|
normals : ndarray
|
|
327
374
|
The normals.
|
|
328
375
|
"""
|
|
376
|
+
if indexed not in (None, 'faces'):
|
|
377
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
378
|
+
|
|
329
379
|
if self._vertex_normals is None:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
faces = vertFaces[vindex]
|
|
336
|
-
if len(faces) == 0:
|
|
337
|
-
self._vertex_normals[vindex] = (0, 0, 0)
|
|
338
|
-
continue
|
|
339
|
-
norms = faceNorms[faces] # get all face normals
|
|
340
|
-
norm = norms.sum(axis=0) # sum normals
|
|
341
|
-
renorm = (norm**2).sum()**0.5
|
|
342
|
-
if renorm > 0:
|
|
343
|
-
norm /= renorm
|
|
344
|
-
self._vertex_normals[vindex] = norm
|
|
380
|
+
face_normals = self.get_face_normals()
|
|
381
|
+
faces = self.get_faces()
|
|
382
|
+
vertices = self.get_vertices()
|
|
383
|
+
self._vertex_normals = _compute_vertex_normals(face_normals, faces,
|
|
384
|
+
vertices)
|
|
345
385
|
|
|
346
386
|
if indexed is None:
|
|
347
387
|
return self._vertex_normals
|
|
348
388
|
elif indexed == 'faces':
|
|
349
389
|
return self._vertex_normals[self.get_faces()]
|
|
350
|
-
else:
|
|
351
|
-
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
|
|
352
390
|
|
|
353
391
|
def get_vertex_colors(self, indexed=None):
|
|
354
392
|
"""Get vertex colors
|
|
@@ -373,7 +411,7 @@ class MeshData(object):
|
|
|
373
411
|
self._vertex_colors[self.get_faces()]
|
|
374
412
|
return self._vertex_colors_indexed_by_faces
|
|
375
413
|
else:
|
|
376
|
-
raise
|
|
414
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
377
415
|
|
|
378
416
|
def get_vertex_values(self, indexed=None):
|
|
379
417
|
"""Get vertex colors
|
|
@@ -398,7 +436,7 @@ class MeshData(object):
|
|
|
398
436
|
self._vertex_values[self.get_faces()]
|
|
399
437
|
return self._vertex_values_indexed_by_faces
|
|
400
438
|
else:
|
|
401
|
-
raise
|
|
439
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
402
440
|
|
|
403
441
|
def set_vertex_colors(self, colors, indexed=None):
|
|
404
442
|
"""Set the vertex color array
|
|
@@ -488,7 +526,7 @@ class MeshData(object):
|
|
|
488
526
|
self._face_colors.reshape(Nf, 1, 4)
|
|
489
527
|
return self._face_colors_indexed_by_faces
|
|
490
528
|
else:
|
|
491
|
-
raise
|
|
529
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
492
530
|
|
|
493
531
|
def set_face_colors(self, colors, indexed=None):
|
|
494
532
|
"""Set the face color array
|
|
@@ -611,7 +649,7 @@ class MeshData(object):
|
|
|
611
649
|
raise Exception("MeshData cannot generate edges--no faces in "
|
|
612
650
|
"this data.")
|
|
613
651
|
else:
|
|
614
|
-
raise
|
|
652
|
+
raise ValueError("Invalid indexing mode. Accepts: None, 'faces'")
|
|
615
653
|
|
|
616
654
|
def save(self):
|
|
617
655
|
"""Serialize this mesh to a string appropriate for disk storage
|