vispy 0.14.2__cp312-cp312-win_amd64.whl → 0.15.0__cp312-cp312-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.
- vispy/app/backends/_qt.py +46 -11
- vispy/color/colormap.py +106 -27
- vispy/color/tests/test_color.py +27 -1
- vispy/ext/cocoapy.py +1 -21
- vispy/geometry/__init__.py +1 -1
- vispy/geometry/calculations.py +30 -2
- vispy/geometry/meshdata.py +3 -1
- vispy/geometry/tests/test_triangulation.py +88 -0
- vispy/geometry/triangulation.py +14 -14
- vispy/gloo/buffer.py +4 -4
- vispy/gloo/gl/tests/test_functionality.py +4 -2
- vispy/gloo/glir.py +7 -2
- vispy/gloo/program.py +2 -2
- vispy/gloo/texture.py +4 -3
- vispy/scene/cameras/arcball.py +1 -2
- vispy/scene/cameras/base_camera.py +63 -50
- vispy/scene/cameras/panzoom.py +4 -1
- vispy/scene/cameras/perspective.py +6 -1
- vispy/scene/cameras/turntable.py +11 -1
- vispy/testing/_runners.py +3 -1
- vispy/util/__init__.py +15 -0
- vispy/version.py +9 -4
- vispy/visuals/_scalable_textures.py +7 -5
- vispy/visuals/collections/array_list.py +4 -4
- vispy/visuals/filters/mesh.py +6 -1
- vispy/visuals/gridlines.py +61 -5
- vispy/visuals/image.py +21 -8
- vispy/visuals/surface_plot.py +1 -1
- vispy/visuals/tests/test_gridlines.py +30 -0
- vispy/visuals/tests/test_image.py +17 -15
- vispy/visuals/tests/test_scalable_textures.py +16 -0
- vispy/visuals/tests/test_surface_plot.py +8 -3
- vispy/visuals/text/_sdf_cpu.cp312-win_amd64.pyd +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +2 -0
- vispy/visuals/volume.py +35 -4
- {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/METADATA +53 -28
- {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/RECORD +40 -39
- {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/WHEEL +1 -1
- {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info/licenses}/LICENSE.txt +1 -1
- {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/top_level.txt +0 -0
vispy/geometry/triangulation.py
CHANGED
|
@@ -7,6 +7,8 @@ import numpy as np
|
|
|
7
7
|
|
|
8
8
|
from collections import OrderedDict
|
|
9
9
|
|
|
10
|
+
from .calculations import _cross_2d
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class Triangulation(object):
|
|
12
14
|
"""Constrained delaunay triangulation
|
|
@@ -97,7 +99,7 @@ class Triangulation(object):
|
|
|
97
99
|
self._tops = self.edges.max(axis=1)
|
|
98
100
|
self._bottoms = self.edges.min(axis=1)
|
|
99
101
|
|
|
100
|
-
#
|
|
102
|
+
# initialize sweep front
|
|
101
103
|
# values in this list are indexes into self.pts
|
|
102
104
|
self._front = [0, 2, 1]
|
|
103
105
|
|
|
@@ -183,7 +185,7 @@ class Triangulation(object):
|
|
|
183
185
|
for j in self._bottoms[self._tops == i]:
|
|
184
186
|
# Make sure edge (j, i) is present in mesh
|
|
185
187
|
# because edge event may have created a new front list
|
|
186
|
-
self._edge_event(i, j)
|
|
188
|
+
self._edge_event(i, int(j))
|
|
187
189
|
front = self._front
|
|
188
190
|
|
|
189
191
|
self._finalize()
|
|
@@ -201,7 +203,7 @@ class Triangulation(object):
|
|
|
201
203
|
while k < idx-1:
|
|
202
204
|
# if edges lie in counterclockwise direction, then signed area
|
|
203
205
|
# is positive
|
|
204
|
-
if self.
|
|
206
|
+
if self._orientation((front[k], front[k+1]), front[k+2]) < 0:
|
|
205
207
|
self._add_tri(front[k], front[k+1], front[k+2])
|
|
206
208
|
front.pop(k+1)
|
|
207
209
|
idx -= 1
|
|
@@ -396,7 +398,7 @@ class Triangulation(object):
|
|
|
396
398
|
|
|
397
399
|
upper_polygon.append(front[front_index+front_dir])
|
|
398
400
|
|
|
399
|
-
# (iii)
|
|
401
|
+
# (iii) triangulate empty areas
|
|
400
402
|
|
|
401
403
|
for polygon in [lower_polygon, upper_polygon]:
|
|
402
404
|
dist = self._distances_from_line((i, j), polygon)
|
|
@@ -671,13 +673,6 @@ class Triangulation(object):
|
|
|
671
673
|
d = (a + c - b) / ((4 * a * c)**0.5)
|
|
672
674
|
return d
|
|
673
675
|
|
|
674
|
-
def _iscounterclockwise(self, a, b, c):
|
|
675
|
-
# Check if the points lie in counter-clockwise order or not
|
|
676
|
-
A = self.pts[a]
|
|
677
|
-
B = self.pts[b]
|
|
678
|
-
C = self.pts[c]
|
|
679
|
-
return np.cross(B-A, C-B) > 0
|
|
680
|
-
|
|
681
676
|
def _edges_intersect(self, edge1, edge2):
|
|
682
677
|
"""Return 1 if edges intersect completely (endpoints excluded)"""
|
|
683
678
|
h12 = self._intersect_edge_arrays(self.pts[np.array(edge1)],
|
|
@@ -739,14 +734,14 @@ class Triangulation(object):
|
|
|
739
734
|
"""
|
|
740
735
|
v1 = self.pts[point] - self.pts[edge[0]]
|
|
741
736
|
v2 = self.pts[edge[1]] - self.pts[edge[0]]
|
|
742
|
-
c =
|
|
737
|
+
c = _cross_2d(v1, v2) # positive if v1 is CW from v2
|
|
743
738
|
return 1 if c > 0 else (-1 if c < 0 else 0)
|
|
744
739
|
|
|
745
740
|
def _add_tri(self, a, b, c):
|
|
746
741
|
# sanity check
|
|
747
742
|
assert a != b and b != c and c != a
|
|
748
743
|
|
|
749
|
-
# ignore
|
|
744
|
+
# ignore tris with duplicate points
|
|
750
745
|
pa = self.pts[a]
|
|
751
746
|
pb = self.pts[b]
|
|
752
747
|
pc = self.pts[c]
|
|
@@ -759,8 +754,13 @@ class Triangulation(object):
|
|
|
759
754
|
raise Exception("Cannot add %s; already have %s" %
|
|
760
755
|
((a, b, c), t))
|
|
761
756
|
|
|
757
|
+
# ignore lines
|
|
758
|
+
orientation = self._orientation((a, b), c)
|
|
759
|
+
if orientation == 0:
|
|
760
|
+
return
|
|
761
|
+
|
|
762
762
|
# TODO: should add to edges_lookup after legalization??
|
|
763
|
-
if
|
|
763
|
+
if orientation < 0:
|
|
764
764
|
assert (a, b) not in self._edges_lookup
|
|
765
765
|
assert (b, c) not in self._edges_lookup
|
|
766
766
|
assert (c, a) not in self._edges_lookup
|
vispy/gloo/buffer.py
CHANGED
|
@@ -10,7 +10,7 @@ from traceback import extract_stack, format_list
|
|
|
10
10
|
import weakref
|
|
11
11
|
|
|
12
12
|
from . globject import GLObject
|
|
13
|
-
from ..util import logger
|
|
13
|
+
from ..util import logger, np_copy_if_needed
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# ------------------------------------------------------------ Buffer class ---
|
|
@@ -70,7 +70,7 @@ class Buffer(GLObject):
|
|
|
70
70
|
data is actually uploaded to GPU memory.
|
|
71
71
|
Asking explicitly for a copy will prevent this behavior.
|
|
72
72
|
"""
|
|
73
|
-
data = np.array(data, copy=copy)
|
|
73
|
+
data = np.array(data, copy=copy or np_copy_if_needed)
|
|
74
74
|
nbytes = data.nbytes
|
|
75
75
|
|
|
76
76
|
if offset < 0:
|
|
@@ -98,7 +98,7 @@ class Buffer(GLObject):
|
|
|
98
98
|
data is actually uploaded to GPU memory.
|
|
99
99
|
Asking explicitly for a copy will prevent this behavior.
|
|
100
100
|
"""
|
|
101
|
-
data = np.array(data, copy=copy)
|
|
101
|
+
data = np.array(data, copy=copy or np_copy_if_needed)
|
|
102
102
|
nbytes = data.nbytes
|
|
103
103
|
|
|
104
104
|
if nbytes != self._nbytes:
|
|
@@ -283,7 +283,7 @@ class DataBuffer(Buffer):
|
|
|
283
283
|
|
|
284
284
|
# Make sure data is an array
|
|
285
285
|
if not isinstance(data, np.ndarray):
|
|
286
|
-
data = np.array(data, dtype=self.dtype
|
|
286
|
+
data = np.array(data, dtype=self.dtype)
|
|
287
287
|
|
|
288
288
|
# Make sure data is big enough
|
|
289
289
|
if data.size < stop - start:
|
|
@@ -192,8 +192,10 @@ quad = np.array([[0, 0, 0], [-1, 0, 0], [-1, -1, 0],
|
|
|
192
192
|
N = quad.shape[0] * 4
|
|
193
193
|
|
|
194
194
|
# buf3 contains coordinates in device coordinates for four quadrants
|
|
195
|
-
buf3 = np.
|
|
196
|
-
|
|
195
|
+
buf3 = np.vstack([
|
|
196
|
+
quad + (0, 0, 0), quad + (0, 1, 0),
|
|
197
|
+
quad + (1, 1, 0), quad + (1, 0, 0),
|
|
198
|
+
]).astype(np.float32)
|
|
197
199
|
|
|
198
200
|
# buf2 is texture coords. Note that this is a view on buf2
|
|
199
201
|
buf2 = ((buf3+1.0)*0.5)[:, :2] # not C-contiguous
|
vispy/gloo/glir.py
CHANGED
|
@@ -1283,8 +1283,13 @@ class GlirProgram(GlirObject):
|
|
|
1283
1283
|
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo_handle)
|
|
1284
1284
|
gl.glEnableVertexAttribArray(attr_handle)
|
|
1285
1285
|
func(attr_handle, *args)
|
|
1286
|
-
if
|
|
1287
|
-
gl.glVertexAttribDivisor(attr_handle, divisor)
|
|
1286
|
+
if hasattr(gl, "glVertexAttribDivisor"):
|
|
1287
|
+
gl.glVertexAttribDivisor(attr_handle, divisor or 0)
|
|
1288
|
+
elif divisor is not None:
|
|
1289
|
+
logger.warning(
|
|
1290
|
+
'Instanced rendering is not supported by the current'
|
|
1291
|
+
f'backend ("{gl.current_backend.__name__}")'
|
|
1292
|
+
)
|
|
1288
1293
|
else:
|
|
1289
1294
|
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
|
|
1290
1295
|
gl.glDisableVertexAttribArray(attr_handle)
|
vispy/gloo/program.py
CHANGED
|
@@ -362,7 +362,7 @@ class Program(GLObject):
|
|
|
362
362
|
data = TextureCube(data)
|
|
363
363
|
else:
|
|
364
364
|
# This should not happen
|
|
365
|
-
raise RuntimeError('Unknown type %s' % type_)
|
|
365
|
+
raise RuntimeError('Unknown type %s for %s' % (type_, name))
|
|
366
366
|
# Store and send GLIR command
|
|
367
367
|
self._user_variables[name] = data
|
|
368
368
|
self.glir.associate(data.glir)
|
|
@@ -442,7 +442,7 @@ class Program(GLObject):
|
|
|
442
442
|
self._glir.command('ATTRIBUTE', self._id,
|
|
443
443
|
name, type_, value, divisor)
|
|
444
444
|
else:
|
|
445
|
-
raise KeyError('Cannot set data for a %s.' % kind)
|
|
445
|
+
raise KeyError('Cannot set data for a %s (%s).' % (kind, name))
|
|
446
446
|
else:
|
|
447
447
|
# This variable is not defined in the current source code,
|
|
448
448
|
# so we cannot establish whether this is a uniform or
|
vispy/gloo/texture.py
CHANGED
|
@@ -11,6 +11,7 @@ import warnings
|
|
|
11
11
|
|
|
12
12
|
from .globject import GLObject
|
|
13
13
|
from .util import check_enum
|
|
14
|
+
from ..util import np_copy_if_needed
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def get_dtype_limits(dtype):
|
|
@@ -29,7 +30,7 @@ def convert_dtype_and_clip(data, dtype, copy=False):
|
|
|
29
30
|
new_min, new_max = get_dtype_limits(dtype)
|
|
30
31
|
if new_max >= old_max and new_min <= old_min:
|
|
31
32
|
# no need to clip
|
|
32
|
-
return np.array(data, dtype=dtype, copy=copy)
|
|
33
|
+
return np.array(data, dtype=dtype, copy=copy or np_copy_if_needed)
|
|
33
34
|
else:
|
|
34
35
|
# to reduce copying, we clip into a pre-generated array of the right dtype
|
|
35
36
|
new_data = np.empty_like(data, dtype=dtype)
|
|
@@ -158,7 +159,7 @@ class BaseTexture(GLObject):
|
|
|
158
159
|
if data is not None:
|
|
159
160
|
if shape is not None:
|
|
160
161
|
raise ValueError('Texture needs data or shape, not both.')
|
|
161
|
-
data = np.array(data
|
|
162
|
+
data = np.array(data)
|
|
162
163
|
# So we can test the combination
|
|
163
164
|
self._resize(data.shape, format, internalformat)
|
|
164
165
|
self._set_data(data)
|
|
@@ -427,7 +428,7 @@ class BaseTexture(GLObject):
|
|
|
427
428
|
|
|
428
429
|
# Make sure data is an array
|
|
429
430
|
if not isinstance(data, np.ndarray):
|
|
430
|
-
data = np.array(data
|
|
431
|
+
data = np.array(data)
|
|
431
432
|
# Make sure data is big enough
|
|
432
433
|
if data.shape != shape:
|
|
433
434
|
data = np.resize(data, shape)
|
vispy/scene/cameras/arcball.py
CHANGED
|
@@ -6,7 +6,6 @@ from __future__ import division
|
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
from ...util import transforms
|
|
11
10
|
from ...util.quaternion import Quaternion
|
|
12
11
|
from ...visuals.transforms import MatrixTransform
|
|
@@ -57,7 +56,7 @@ class ArcballCamera(Base3DRotationCamera):
|
|
|
57
56
|
|
|
58
57
|
def _update_rotation(self, event):
|
|
59
58
|
"""Update rotation parmeters based on mouse movement"""
|
|
60
|
-
p2 = event.
|
|
59
|
+
p2 = event.pos[:2]
|
|
61
60
|
if self._event_value is None:
|
|
62
61
|
self._event_value = p2
|
|
63
62
|
wh = self._viewbox.size
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
4
4
|
|
|
5
5
|
from __future__ import division
|
|
6
|
+
from contextlib import contextmanager
|
|
6
7
|
|
|
7
8
|
from ...util import keys
|
|
8
9
|
from ..node import Node
|
|
@@ -211,11 +212,14 @@ class BaseCamera(Node):
|
|
|
211
212
|
@center.setter
|
|
212
213
|
def center(self, val):
|
|
213
214
|
if len(val) == 2:
|
|
214
|
-
|
|
215
|
+
center = float(val[0]), float(val[1]), 0.0
|
|
215
216
|
elif len(val) == 3:
|
|
216
|
-
|
|
217
|
+
center = float(val[0]), float(val[1]), float(val[2])
|
|
217
218
|
else:
|
|
218
219
|
raise ValueError('Center must be a 2 or 3 element tuple')
|
|
220
|
+
if center == self._center:
|
|
221
|
+
return
|
|
222
|
+
self._center = center
|
|
219
223
|
self.view_changed()
|
|
220
224
|
|
|
221
225
|
@property
|
|
@@ -233,6 +237,12 @@ class BaseCamera(Node):
|
|
|
233
237
|
self._fov = fov
|
|
234
238
|
self.view_changed()
|
|
235
239
|
|
|
240
|
+
@contextmanager
|
|
241
|
+
def _block_updates(self):
|
|
242
|
+
prev, self._resetting = self._resetting, True
|
|
243
|
+
yield
|
|
244
|
+
self._resetting = prev
|
|
245
|
+
|
|
236
246
|
# Camera methods
|
|
237
247
|
|
|
238
248
|
def set_range(self, x=None, y=None, z=None, margin=0.05):
|
|
@@ -276,31 +286,30 @@ class BaseCamera(Node):
|
|
|
276
286
|
return
|
|
277
287
|
|
|
278
288
|
# There is a viewbox, we're going to set the range for real
|
|
279
|
-
self.
|
|
289
|
+
with self._block_updates():
|
|
280
290
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
291
|
+
# Get bounds from viewbox if not given
|
|
292
|
+
if all([(b is None) for b in bounds]):
|
|
293
|
+
bounds = self._viewbox.get_scene_bounds()
|
|
294
|
+
else:
|
|
295
|
+
for i in range(3):
|
|
296
|
+
if bounds[i] is None:
|
|
297
|
+
bounds[i] = self._viewbox.get_scene_bounds(i)
|
|
298
|
+
|
|
299
|
+
# Calculate ranges and margins
|
|
300
|
+
ranges = [b[1] - b[0] for b in bounds]
|
|
301
|
+
margins = [(r*margin or 0.1) for r in ranges]
|
|
302
|
+
# Assign limits for this camera
|
|
303
|
+
bounds_margins = [(b[0]-m, b[1]+m) for b, m in zip(bounds, margins)]
|
|
304
|
+
self._xlim, self._ylim, self._zlim = bounds_margins
|
|
305
|
+
# Store center location
|
|
306
|
+
if (not init) or (self._center is None):
|
|
307
|
+
self._center = [(b[0] + r / 2) for b, r in zip(bounds, ranges)]
|
|
308
|
+
|
|
309
|
+
# Let specific camera handle it
|
|
310
|
+
self._set_range(init)
|
|
301
311
|
|
|
302
312
|
# Finish
|
|
303
|
-
self._resetting = False
|
|
304
313
|
self.view_changed()
|
|
305
314
|
|
|
306
315
|
def _set_range(self, init):
|
|
@@ -357,32 +366,34 @@ class BaseCamera(Node):
|
|
|
357
366
|
state = state or {}
|
|
358
367
|
state.update(kwargs)
|
|
359
368
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
369
|
+
with self._block_updates():
|
|
370
|
+
# In first pass, process tuple keys which select subproperties. This
|
|
371
|
+
# is an undocumented feature used for selective linking of camera state.
|
|
372
|
+
#
|
|
373
|
+
# Subproperties are handled by first copying old value of the root
|
|
374
|
+
# property, then setting the subproperty on this copy, and finally
|
|
375
|
+
# assigning the copied object back to the camera property. There needs
|
|
376
|
+
# to be an assignment of the root property so setters are called and
|
|
377
|
+
# update is triggered.
|
|
378
|
+
for key in list(state.keys()):
|
|
379
|
+
if isinstance(key, tuple):
|
|
380
|
+
key1 = key[0]
|
|
381
|
+
if key1 not in state:
|
|
382
|
+
root_prop = getattr(self, key1)
|
|
383
|
+
# We make copies by passing the old object to the type's
|
|
384
|
+
# constructor. This needs to be supported as is the case in
|
|
385
|
+
# e.g. the geometry.Rect class.
|
|
386
|
+
state[key1] = root_prop.__class__(root_prop)
|
|
387
|
+
nested_setattr(state[key1], key[1:], state[key])
|
|
388
|
+
|
|
389
|
+
# In second pass, assign the new root properties.
|
|
390
|
+
for key, val in state.items():
|
|
391
|
+
if isinstance(key, tuple):
|
|
392
|
+
continue
|
|
393
|
+
if key not in self._state_props:
|
|
394
|
+
raise KeyError('Not a valid camera state property %r' % key)
|
|
395
|
+
setattr(self, key, val)
|
|
396
|
+
self.view_changed()
|
|
386
397
|
|
|
387
398
|
def link(self, camera, props=None, axis=None):
|
|
388
399
|
"""Link this camera with another camera of the same type
|
|
@@ -508,6 +519,8 @@ class BaseCamera(Node):
|
|
|
508
519
|
|
|
509
520
|
def _set_scene_transform(self, tr):
|
|
510
521
|
"""Called by subclasses to configure the viewbox scene transform."""
|
|
522
|
+
if self._resetting:
|
|
523
|
+
return
|
|
511
524
|
# todo: check whether transform has changed, connect to
|
|
512
525
|
# transform.changed event
|
|
513
526
|
pre_tr = self.pre_transform
|
vispy/scene/cameras/panzoom.py
CHANGED
|
@@ -164,7 +164,7 @@ class PanZoomCamera(BaseCamera):
|
|
|
164
164
|
def center(self, center):
|
|
165
165
|
if not (isinstance(center, (tuple, list)) and len(center) in (2, 3)):
|
|
166
166
|
raise ValueError('center must be a 2 or 3 element tuple')
|
|
167
|
-
rect = Rect(self.rect) or Rect(*DEFAULT_RECT_TUPLE)
|
|
167
|
+
rect = Rect(self.rect) or Rect(*DEFAULT_RECT_TUPLE) # make a copy of self.rect
|
|
168
168
|
rect.center = center[:2]
|
|
169
169
|
self.rect = rect
|
|
170
170
|
|
|
@@ -246,6 +246,9 @@ class PanZoomCamera(BaseCamera):
|
|
|
246
246
|
event.handled = False
|
|
247
247
|
|
|
248
248
|
def _update_transform(self):
|
|
249
|
+
if self._resetting: # base camera linking operation
|
|
250
|
+
return
|
|
251
|
+
|
|
249
252
|
rect = self.rect
|
|
250
253
|
self._real_rect = Rect(rect)
|
|
251
254
|
vbr = self._viewbox.rect.flipped(x=self.flip[0], y=(not self.flip[1]))
|
|
@@ -80,7 +80,10 @@ class PerspectiveCamera(BaseCamera):
|
|
|
80
80
|
|
|
81
81
|
@scale_factor.setter
|
|
82
82
|
def scale_factor(self, value):
|
|
83
|
-
|
|
83
|
+
value = abs(float(value))
|
|
84
|
+
if value == self._scale_factor:
|
|
85
|
+
return
|
|
86
|
+
self._scale_factor = value
|
|
84
87
|
self.view_changed()
|
|
85
88
|
|
|
86
89
|
@property
|
|
@@ -137,6 +140,8 @@ class PerspectiveCamera(BaseCamera):
|
|
|
137
140
|
# Do we have a viewbox
|
|
138
141
|
if self._viewbox is None:
|
|
139
142
|
return
|
|
143
|
+
if self._resetting: # base camera linking operation
|
|
144
|
+
return
|
|
140
145
|
|
|
141
146
|
# Calculate viewing range for x and y
|
|
142
147
|
fx = fy = self._scale_factor
|
vispy/scene/cameras/turntable.py
CHANGED
|
@@ -73,6 +73,9 @@ class TurntableCamera(Base3DRotationCamera):
|
|
|
73
73
|
super(TurntableCamera, self).__init__(fov=fov, **kwargs)
|
|
74
74
|
|
|
75
75
|
# Set camera attributes
|
|
76
|
+
self._azimuth = azimuth
|
|
77
|
+
self._elevation = elevation
|
|
78
|
+
self._roll = roll
|
|
76
79
|
self.azimuth = azimuth
|
|
77
80
|
self.elevation = elevation
|
|
78
81
|
self.roll = roll
|
|
@@ -90,7 +93,10 @@ class TurntableCamera(Base3DRotationCamera):
|
|
|
90
93
|
@elevation.setter
|
|
91
94
|
def elevation(self, elev):
|
|
92
95
|
elev = float(elev)
|
|
93
|
-
|
|
96
|
+
elev = min(90, max(-90, elev))
|
|
97
|
+
if elev == self._elevation:
|
|
98
|
+
return
|
|
99
|
+
self._elevation = elev
|
|
94
100
|
self.view_changed()
|
|
95
101
|
|
|
96
102
|
@property
|
|
@@ -108,6 +114,8 @@ class TurntableCamera(Base3DRotationCamera):
|
|
|
108
114
|
azim += 360
|
|
109
115
|
while azim > 180:
|
|
110
116
|
azim -= 360
|
|
117
|
+
if azim == self._azimuth:
|
|
118
|
+
return
|
|
111
119
|
self._azimuth = azim
|
|
112
120
|
self.view_changed()
|
|
113
121
|
|
|
@@ -123,6 +131,8 @@ class TurntableCamera(Base3DRotationCamera):
|
|
|
123
131
|
roll += 360
|
|
124
132
|
while roll > 180:
|
|
125
133
|
roll -= 360
|
|
134
|
+
if roll == self._roll:
|
|
135
|
+
return
|
|
126
136
|
self._roll = roll
|
|
127
137
|
self.view_changed()
|
|
128
138
|
|
vispy/testing/_runners.py
CHANGED
|
@@ -164,7 +164,9 @@ def _flake():
|
|
|
164
164
|
print('Running flake8... ') # if end='', first error gets ugly
|
|
165
165
|
sys.stdout.flush()
|
|
166
166
|
try:
|
|
167
|
-
main
|
|
167
|
+
# flake8 used to exit on failure, but instead `main` now returns an exit code
|
|
168
|
+
# see https://github.com/PyCQA/flake8/pull/1461
|
|
169
|
+
raise SystemExit(main())
|
|
168
170
|
except SystemExit as ex:
|
|
169
171
|
if ex.code in (None, 0):
|
|
170
172
|
pass # do not exit yet, we want to print a success msg
|
vispy/util/__init__.py
CHANGED
|
@@ -15,3 +15,18 @@ from . import fonts # noqa
|
|
|
15
15
|
from . import transforms # noqa
|
|
16
16
|
from .wrappers import use, run_subprocess # noqa
|
|
17
17
|
from .bunch import SimpleBunch # noqa
|
|
18
|
+
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
# `copy` keyword semantics changed in NumPy 2.0
|
|
24
|
+
# this maintains compatibility with older versions
|
|
25
|
+
# if/when NumPy 2.0 becomes the minimum version, we can remove this
|
|
26
|
+
# we don't worry about early dev versions of NumPy 2.0 (that may or may not have the kwarg) here
|
|
27
|
+
# see:
|
|
28
|
+
# https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword
|
|
29
|
+
# https://github.com/scipy/scipy/pull/20172
|
|
30
|
+
np_copy_if_needed: Optional[bool] = None
|
|
31
|
+
if np.lib.NumpyVersion(np.__version__) < "1.28.0":
|
|
32
|
+
np_copy_if_needed = False
|
vispy/version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.15.0'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 15, 0)
|
|
@@ -6,6 +6,7 @@ import warnings
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
from vispy.gloo.texture import Texture2D, Texture3D, convert_dtype_and_clip
|
|
9
|
+
from vispy.util import np_copy_if_needed
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def get_default_clim_from_dtype(dtype):
|
|
@@ -290,15 +291,16 @@ class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
290
291
|
|
|
291
292
|
range_min, range_max = self._data_limits
|
|
292
293
|
clim_min, clim_max = self.clim
|
|
293
|
-
|
|
294
|
+
full_range = range_max - range_min
|
|
295
|
+
if clim_min == clim_max or full_range == 0:
|
|
294
296
|
return 0, np.inf
|
|
295
|
-
clim_min = (clim_min - range_min) /
|
|
296
|
-
clim_max = (clim_max - range_min) /
|
|
297
|
+
clim_min = (clim_min - range_min) / full_range
|
|
298
|
+
clim_max = (clim_max - range_min) / full_range
|
|
297
299
|
return clim_min, clim_max
|
|
298
300
|
|
|
299
301
|
@staticmethod
|
|
300
302
|
def _scale_data_on_cpu(data, clim, copy=True):
|
|
301
|
-
data = np.array(data, dtype=np.float32, copy=copy)
|
|
303
|
+
data = np.array(data, dtype=np.float32, copy=copy or np_copy_if_needed)
|
|
302
304
|
if clim[0] != clim[1]:
|
|
303
305
|
# we always must copy the data if we change it here, otherwise it might change
|
|
304
306
|
# unexpectedly the data held outside of here
|
|
@@ -311,7 +313,7 @@ class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
311
313
|
def scale_and_set_data(self, data, offset=None, copy=True):
|
|
312
314
|
"""Upload new data to the GPU, scaling if necessary."""
|
|
313
315
|
if self._data_dtype is None:
|
|
314
|
-
|
|
316
|
+
self._data_dtype = data.dtype
|
|
315
317
|
|
|
316
318
|
# ensure dtype is the same as it was before, or funny things happen
|
|
317
319
|
# no copy is performed unless asked for or necessary
|
|
@@ -73,7 +73,7 @@ class ArrayList(object):
|
|
|
73
73
|
if isinstance(data[0], (list, tuple)):
|
|
74
74
|
itemsize = [len(sublist) for sublist in data]
|
|
75
75
|
data = [item for sublist in data for item in sublist]
|
|
76
|
-
self._data = np.array(data
|
|
76
|
+
self._data = np.array(data)
|
|
77
77
|
self._size = self._data.size
|
|
78
78
|
|
|
79
79
|
# Default is one group with all data inside
|
|
@@ -88,7 +88,7 @@ class ArrayList(object):
|
|
|
88
88
|
_itemsize = np.ones(
|
|
89
89
|
self._count, dtype=int) * (self._size // self._count)
|
|
90
90
|
else:
|
|
91
|
-
_itemsize = np.array(itemsize
|
|
91
|
+
_itemsize = np.array(itemsize)
|
|
92
92
|
self._count = len(itemsize)
|
|
93
93
|
if _itemsize.sum() != self._size:
|
|
94
94
|
raise ValueError("Cannot partition data as requested")
|
|
@@ -302,7 +302,7 @@ class ArrayList(object):
|
|
|
302
302
|
itemsize = [len(sublist) for sublist in data]
|
|
303
303
|
data = [item for sublist in data for item in sublist]
|
|
304
304
|
|
|
305
|
-
data = np.array(data
|
|
305
|
+
data = np.array(data).ravel()
|
|
306
306
|
size = data.size
|
|
307
307
|
|
|
308
308
|
# Check item size and get item number
|
|
@@ -313,7 +313,7 @@ class ArrayList(object):
|
|
|
313
313
|
_count = size // itemsize
|
|
314
314
|
_itemsize = np.ones(_count, dtype=int) * (size // _count)
|
|
315
315
|
else:
|
|
316
|
-
_itemsize = np.array(itemsize
|
|
316
|
+
_itemsize = np.array(itemsize)
|
|
317
317
|
_count = len(itemsize)
|
|
318
318
|
if _itemsize.sum() != size:
|
|
319
319
|
raise ValueError("Cannot partition data as requested")
|
vispy/visuals/filters/mesh.py
CHANGED
|
@@ -421,6 +421,7 @@ class ShadingFilter(Filter):
|
|
|
421
421
|
ffunc = Function(self._shaders['fragment'])
|
|
422
422
|
|
|
423
423
|
self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32))
|
|
424
|
+
self._normals_cache = None
|
|
424
425
|
vfunc['normal'] = self._normals
|
|
425
426
|
|
|
426
427
|
super().__init__(vcode=vfunc, fcode=ffunc)
|
|
@@ -550,7 +551,11 @@ class ShadingFilter(Filter):
|
|
|
550
551
|
)
|
|
551
552
|
|
|
552
553
|
normals = self._visual.mesh_data.get_vertex_normals(indexed='faces')
|
|
553
|
-
self.
|
|
554
|
+
if normals is not self._normals_cache:
|
|
555
|
+
# limit how often we upload new normal arrays
|
|
556
|
+
# gotcha: if normals are changed in place then this won't invalidate this cache
|
|
557
|
+
self._normals_cache = normals
|
|
558
|
+
self._normals.set_data(self._normals_cache, convert=True)
|
|
554
559
|
|
|
555
560
|
def on_mesh_data_updated(self, event):
|
|
556
561
|
self._update_data()
|