vispy 0.14.2__cp310-cp310-macosx_11_0_arm64.whl → 0.15.0__cp310-cp310-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vispy might be problematic. Click here for more details.

Files changed (40) hide show
  1. vispy/app/backends/_qt.py +46 -11
  2. vispy/color/colormap.py +106 -27
  3. vispy/color/tests/test_color.py +27 -1
  4. vispy/ext/cocoapy.py +1 -21
  5. vispy/geometry/__init__.py +1 -1
  6. vispy/geometry/calculations.py +30 -2
  7. vispy/geometry/meshdata.py +3 -1
  8. vispy/geometry/tests/test_triangulation.py +88 -0
  9. vispy/geometry/triangulation.py +14 -14
  10. vispy/gloo/buffer.py +4 -4
  11. vispy/gloo/gl/tests/test_functionality.py +4 -2
  12. vispy/gloo/glir.py +7 -2
  13. vispy/gloo/program.py +2 -2
  14. vispy/gloo/texture.py +4 -3
  15. vispy/scene/cameras/arcball.py +1 -2
  16. vispy/scene/cameras/base_camera.py +63 -50
  17. vispy/scene/cameras/panzoom.py +4 -1
  18. vispy/scene/cameras/perspective.py +6 -1
  19. vispy/scene/cameras/turntable.py +11 -1
  20. vispy/testing/_runners.py +3 -1
  21. vispy/util/__init__.py +15 -0
  22. vispy/version.py +9 -4
  23. vispy/visuals/_scalable_textures.py +7 -5
  24. vispy/visuals/collections/array_list.py +4 -4
  25. vispy/visuals/filters/mesh.py +6 -1
  26. vispy/visuals/gridlines.py +61 -5
  27. vispy/visuals/image.py +21 -8
  28. vispy/visuals/surface_plot.py +1 -1
  29. vispy/visuals/tests/test_gridlines.py +30 -0
  30. vispy/visuals/tests/test_image.py +17 -15
  31. vispy/visuals/tests/test_scalable_textures.py +16 -0
  32. vispy/visuals/tests/test_surface_plot.py +8 -3
  33. vispy/visuals/text/_sdf_cpu.cpython-310-darwin.so +0 -0
  34. vispy/visuals/text/_sdf_cpu.pyx +2 -0
  35. vispy/visuals/volume.py +35 -4
  36. {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/METADATA +53 -28
  37. {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/RECORD +40 -39
  38. {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/WHEEL +2 -1
  39. {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info/licenses}/LICENSE.txt +1 -1
  40. {vispy-0.14.2.dist-info → vispy-0.15.0.dist-info}/top_level.txt +0 -0
@@ -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
- # inintialize sweep front
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._iscounterclockwise(front[k], front[k+1], front[k+2]):
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) triangluate empty areas
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 = np.cross(v1, v2) # positive if v1 is CW from v2
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 flat tris
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 self._iscounterclockwise(a, b, c):
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, copy=False)
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.row_stack([quad + (0, 0, 0), quad + (0, 1, 0),
196
- quad + (1, 1, 0), quad + (1, 0, 0)]).astype(np.float32)
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 divisor is not None:
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, copy=False)
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, copy=False)
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)
@@ -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.mouse_event.pos
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
- self._center = float(val[0]), float(val[1]), 0.0
215
+ center = float(val[0]), float(val[1]), 0.0
215
216
  elif len(val) == 3:
216
- self._center = float(val[0]), float(val[1]), float(val[2])
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._resetting = True
289
+ with self._block_updates():
280
290
 
281
- # Get bounds from viewbox if not given
282
- if all([(b is None) for b in bounds]):
283
- bounds = self._viewbox.get_scene_bounds()
284
- else:
285
- for i in range(3):
286
- if bounds[i] is None:
287
- bounds[i] = self._viewbox.get_scene_bounds(i)
288
-
289
- # Calculate ranges and margins
290
- ranges = [b[1] - b[0] for b in bounds]
291
- margins = [(r*margin or 0.1) for r in ranges]
292
- # Assign limits for this camera
293
- bounds_margins = [(b[0]-m, b[1]+m) for b, m in zip(bounds, margins)]
294
- self._xlim, self._ylim, self._zlim = bounds_margins
295
- # Store center location
296
- if (not init) or (self._center is None):
297
- self._center = [(b[0] + r / 2) for b, r in zip(bounds, ranges)]
298
-
299
- # Let specific camera handle it
300
- self._set_range(init)
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
- # In first pass, process tuple keys which select subproperties. This
361
- # is an undocumented feature used for selective linking of camera state.
362
- #
363
- # Subproperties are handled by first copying old value of the root
364
- # property, then setting the subproperty on this copy, and finally
365
- # assigning the copied object back to the camera property. There needs
366
- # to be an assignment of the root property so setters are called and
367
- # update is triggered.
368
- for key in list(state.keys()):
369
- if isinstance(key, tuple):
370
- key1 = key[0]
371
- if key1 not in state:
372
- root_prop = getattr(self, key1)
373
- # We make copies by passing the old object to the type's
374
- # constructor. This needs to be supported as is the case in
375
- # e.g. the geometry.Rect class.
376
- state[key1] = root_prop.__class__(root_prop)
377
- nested_setattr(state[key1], key[1:], state[key])
378
-
379
- # In second pass, assign the new root properties.
380
- for key, val in state.items():
381
- if isinstance(key, tuple):
382
- continue
383
- if key not in self._state_props:
384
- raise KeyError('Not a valid camera state property %r' % key)
385
- setattr(self, key, val)
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
@@ -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
- self._scale_factor = abs(float(value))
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
@@ -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
- self._elevation = min(90, max(-90, elev))
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 setuptools_scm
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, Union
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.14.2'
16
- __version_tuple__ = version_tuple = (0, 14, 2)
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
- if clim_min == clim_max:
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) / (range_max - range_min)
296
- clim_max = (clim_max - range_min) / (range_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
- data.dtype == self._data_dtype
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, copy=False)
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, copy=False)
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, copy=False).ravel()
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, copy=False)
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")
@@ -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._normals.set_data(normals, convert=True)
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()