Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-vispy for openSUSE:Factory checked in at 2023-07-19 19:10:45 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-vispy (Old) and /work/SRC/openSUSE:Factory/.python-vispy.new.5570 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-vispy" Wed Jul 19 19:10:45 2023 rev:11 rq:1099362 version:0.13.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-vispy/python-vispy.changes 2023-03-24 15:20:11.298815116 +0100 +++ /work/SRC/openSUSE:Factory/.python-vispy.new.5570/python-vispy.changes 2023-07-19 19:10:47.352620607 +0200 @@ -1,0 +2,14 @@ +Wed Jul 19 04:42:21 UTC 2023 - Steve Kowalik <steven.kowa...@suse.com> + +- Update to 0.13.0: + * Switch MarkersVisual scaling option to string "fixed", "scene", or + "visual" + * Add early-termination optimization to attenuated mip + * Add `InstancedMeshVisual` for faster and easier rendering of repeated + meshes + * Instanced mesh example + * Use QNativeEventGesture for touchpad gesture input + * Fix TypeError with pinch-to-zoom +- Replace setuptools_scm_git_archive BuildRequires with setuptools_scm. + +------------------------------------------------------------------- Old: ---- vispy-0.12.2.tar.gz New: ---- vispy-0.13.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-vispy.spec ++++++ --- /var/tmp/diff_new_pack.GOLwY1/_old 2023-07-19 19:10:48.132625169 +0200 +++ /var/tmp/diff_new_pack.GOLwY1/_new 2023-07-19 19:10:48.136625192 +0200 @@ -18,7 +18,7 @@ %bcond_without ext_deps Name: python-vispy -Version: 0.12.2 +Version: 0.13.0 Release: 0 Summary: Interactive visualization in Python License: BSD-3-Clause @@ -28,7 +28,7 @@ BuildRequires: %{python_module devel} BuildRequires: %{python_module numpy-devel} BuildRequires: %{python_module pip} -BuildRequires: %{python_module setuptools_scm_git_archive} +BuildRequires: %{python_module setuptools_scm} BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: jupyter-notebook-filesystem @@ -118,6 +118,6 @@ %doc *.rst *.md %license LICENSE.txt %{python_sitearch}/vispy -%{python_sitearch}/vispy-%{version}*-info +%{python_sitearch}/vispy-%{version}.dist-info %changelog ++++++ vispy-0.12.2.tar.gz -> vispy-0.13.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/.github/workflows/wheels.yml new/vispy-0.13.0/.github/workflows/wheels.yml --- old/vispy-0.12.2/.github/workflows/wheels.yml 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/.github/workflows/wheels.yml 2023-05-12 20:06:40.000000000 +0200 @@ -27,7 +27,7 @@ if: ${{ matrix.arch == 'aarch64' }} uses: docker/setup-qemu-action@v2 - name: Build wheels - uses: pypa/cibuildwheel@v2.12.1 + uses: pypa/cibuildwheel@v2.12.3 env: CIBW_SKIP: "cp36-* pp* *-win32 *-manylinux_i686 *-musllinux*" CIBW_ARCHS_LINUX: ${{ matrix.arch }} @@ -42,6 +42,8 @@ CIBW_ENVIRONMENT: "GITHUB_ACTIONS=true" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_MANYLINUX_I686_IMAGE: manylinux2014 + CIBW_ARCHS_MACOS: 'x86_64 arm64' + CIBW_TEST_SKIP: '*-macosx_arm64' - uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/CHANGELOG.md new/vispy-0.13.0/CHANGELOG.md --- old/vispy-0.12.2/CHANGELOG.md 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/CHANGELOG.md 2023-05-12 20:06:40.000000000 +0200 @@ -1,5 +1,24 @@ # Release Notes +## [v0.13.0](https://github.com/vispy/vispy/tree/v0.13.0) (2023-05-12) + +**Enhancements:** + +- Switch MarkersVisual scaling option to string "fixed", "scene", or "visual" [\#2470](https://github.com/vispy/vispy/pull/2470) ([djhoese](https://github.com/djhoese)) +- Add early-termination optimization to attenuated mip [\#2465](https://github.com/vispy/vispy/pull/2465) ([aganders3](https://github.com/aganders3)) +- Add `InstancedMeshVisual` for faster and easier rendering of repeated meshes [\#2461](https://github.com/vispy/vispy/pull/2461) ([brisvag](https://github.com/brisvag)) +- Instanced mesh example [\#2460](https://github.com/vispy/vispy/pull/2460) ([brisvag](https://github.com/brisvag)) +- Use QNativeEventGesture for touchpad gesture input [\#2456](https://github.com/vispy/vispy/pull/2456) ([aganders3](https://github.com/aganders3)) + +**Fixed bugs:** + +- Fix TypeError with pinch-to-zoom [\#2483](https://github.com/vispy/vispy/pull/2483) ([aganders3](https://github.com/aganders3)) + +**Merged pull requests:** + +- Bump pypa/cibuildwheel from 2.12.1 to 2.12.3 [\#2472](https://github.com/vispy/vispy/pull/2472) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Cleanup site navbar with pydata-sphinx-theme 0.10+ [\#2371](https://github.com/vispy/vispy/pull/2371) ([djhoese](https://github.com/djhoese)) + ## [v0.12.2](https://github.com/vispy/vispy/tree/v0.12.2) (2023-03-20) **Enhancements:** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/PKG-INFO new/vispy-0.13.0/PKG-INFO --- old/vispy-0.12.2/PKG-INFO 2023-03-20 16:16:37.000000000 +0100 +++ new/vispy-0.13.0/PKG-INFO 2023-05-12 20:07:07.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: vispy -Version: 0.12.2 +Version: 0.13.0 Summary: Interactive visualization in Python Home-page: http://vispy.org Download-URL: https://pypi.python.org/pypi/vispy diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/doc/conf.py new/vispy-0.13.0/doc/conf.py --- old/vispy-0.12.2/doc/conf.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/doc/conf.py 2023-05-12 20:06:40.000000000 +0200 @@ -186,6 +186,7 @@ "use_edit_page_button": True, "github_url": "https://github.com/vispy/vispy", "twitter_url": "https://twitter.com/vispyproject", + "header_links_before_dropdown": 7, } # Add any paths that contain custom themes here, relative to this directory. @@ -346,11 +347,11 @@ # ----------------------------------------------------------------------------- # intersphinx # ----------------------------------------------------------------------------- -_python_doc_base = 'https://docs.python.org/3.9' +_python_doc_base = "https://docs.python.org/3" intersphinx_mapping = { - _python_doc_base: None, - 'https://numpy.org/doc/stable/': None, - 'https://scipy.github.io/devdocs/': None, + "python": (_python_doc_base, None), + "numpy": ("https://numpy.org/doc/stable/", None), + "scipy": ("https://scipy.github.io/devdocs/", None), } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/examples/basics/visuals/markers.py new/vispy-0.13.0/examples/basics/visuals/markers.py --- old/vispy-0.12.2/examples/basics/visuals/markers.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/examples/basics/visuals/markers.py 2023-05-12 20:06:40.000000000 +0200 @@ -4,7 +4,13 @@ # Copyright (c) Vispy Development Team. All Rights Reserved. # Distributed under the (new) BSD License. See LICENSE.txt for more info. # ----------------------------------------------------------------------------- -""" Display markers at different sizes and line thicknessess. +"""Display markers at different sizes and line thicknesses. + +Keyboard options: +* spacebar: Cycle through possible marker symbols. +* "s": Switch between "fixed" marker scaling (initial setting) and "scene" + scaling. + """ import numpy as np @@ -60,10 +66,11 @@ self.markers.symbol = self.markers.symbols[self.index] self.update() elif event.text == 's': - self.markers.scaling = not self.markers.scaling + self.markers.scaling = "fixed" if self.markers.scaling != "fixed" else "scene" self.update() if __name__ == '__main__': + print(__doc__) canvas = Canvas() app.run() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/examples/scene/instanced_mesh.py new/vispy-0.13.0/examples/scene/instanced_mesh.py --- old/vispy-0.12.2/examples/scene/instanced_mesh.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vispy-0.13.0/examples/scene/instanced_mesh.py 2023-05-12 20:06:40.000000000 +0200 @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# vispy: gallery 5 +# ----------------------------------------------------------------------------- +# Copyright (c) Vispy Development Team. All Rights Reserved. +# Distributed under the (new) BSD License. See LICENSE.txt for more info. +# ----------------------------------------------------------------------------- +""" +Instanced rendering of arbitrarily transformed meshes +===================================================== +""" + +from vispy import app, gloo, visuals, scene, use +import numpy as np +from scipy.spatial.transform import Rotation +from vispy.io import read_mesh, load_data_file + +# full gl+ context is required for instanced rendering +use(gl='gl+') + + +vertex_shader = """ +// these attributes will be defined on an instance basis +attribute vec3 shift; +attribute vec4 color; +attribute vec3 transform_x; +attribute vec3 transform_y; +attribute vec3 transform_z; + +varying vec4 v_color; + +void main() { + v_color = color; + // transform is generated from column vectors (new basis vectors) + // https://en.wikibooks.org/wiki/GLSL_Programming/Vector_and_Matrix_Operations#Constructors + mat3 instance_transform = mat3(transform_x, transform_y, transform_z); + vec3 pos_rotated = instance_transform * $position; + vec4 pos_shifted = vec4(pos_rotated + shift, 1); + gl_Position = $transform(pos_shifted); +} +""" + +fragment_shader = """ +varying vec4 v_color; + +void main() { + gl_FragColor = v_color; +} +""" + + +class InstancedMeshVisual(visuals.Visual): + def __init__(self, vertices, faces, positions, colors, transforms, subdivisions=5): + visuals.Visual.__init__(self, vertex_shader, fragment_shader) + + self.set_gl_state('translucent', depth_test=True, cull_face=True) + self._draw_mode = 'triangles' + + # set up vertex and index buffer + self.vbo = gloo.VertexBuffer(vertices.astype(np.float32)) + self.shared_program.vert['position'] = self.vbo + self._index_buffer = gloo.IndexBuffer(data=faces.astype(np.uint32)) + + # create a vertex buffer with a divisor argument of 1. This means that the + # attribute value is set to the next element of the array every 1 instance. + # The length of the array multiplied by the divisor determines the number + # of instances + self.shifts = gloo.VertexBuffer(positions.astype(np.float32), divisor=1) + self.shared_program['shift'] = self.shifts + + # vispy does not handle matrix attributes (likely requires some big changes in GLIR) + # so we decompose it into three vec3; (column vectors of the matrix) + transforms = transforms.astype(np.float32) + self.transforms_x = gloo.VertexBuffer(transforms[..., 0].copy(), divisor=1) + self.transforms_y = gloo.VertexBuffer(transforms[..., 1].copy(), divisor=1) + self.transforms_z = gloo.VertexBuffer(transforms[..., 2].copy(), divisor=1) + self.shared_program['transform_x'] = self.transforms_x + self.shared_program['transform_y'] = self.transforms_y + self.shared_program['transform_z'] = self.transforms_z + + # we can provide additional buffers with different divisors, as long as the + # amount of instances (length * divisor) is the same. In this case, we will change + # color every 5 instances + self.color = gloo.VertexBuffer(colors.astype(np.float32), divisor=1) + self.shared_program['color'] = self.color + + def _prepare_transforms(self, view): + view.view_program.vert['transform'] = view.get_transform() + + +# create a visual node class to add it to the canvas +InstancedMesh = scene.visuals.create_visual_node(InstancedMeshVisual) + +# set up vanvas +canvas = scene.SceneCanvas(keys='interactive', show=True) +view = canvas.central_widget.add_view() +view.camera = 'arcball' +view.camera.scale_factor = 1000 + +N = 1000 + +mesh_file = load_data_file('orig/triceratops.obj.gz') +vertices, faces, _, _ = read_mesh(mesh_file) + +np.random.seed(0) +pos = (np.random.rand(N, 3) - 0.5) * 1000 +colors = np.random.rand(N, 4) +transforms = Rotation.random(N).as_matrix() + +multimesh = InstancedMesh(vertices * 10, faces, pos, colors, transforms, parent=view.scene) +# global transforms are applied correctly after the individual instance transforms! +multimesh.transform = visuals.transforms.STTransform(scale=(3, 2, 1)) + + +if __name__ == '__main__': + import sys + if sys.flags.interactive != 1: + app.run() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/examples/scene/instanced_mesh_visual.py new/vispy-0.13.0/examples/scene/instanced_mesh_visual.py --- old/vispy-0.12.2/examples/scene/instanced_mesh_visual.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vispy-0.13.0/examples/scene/instanced_mesh_visual.py 2023-05-12 20:06:40.000000000 +0200 @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# vispy: gallery 30 +# ----------------------------------------------------------------------------- +# Copyright (c) Vispy Development Team. All Rights Reserved. +# Distributed under the (new) BSD License. See LICENSE.txt for more info. +# ----------------------------------------------------------------------------- +""" +Instanced Mesh Visual +===================== + +Show usage of the InstancedMesh visual and its filters. +""" + +from itertools import cycle + +import numpy as np +from scipy.spatial.transform import Rotation +from vispy import app, scene, use +from vispy.io import imread, load_data_file, read_mesh +from vispy.scene.visuals import InstancedMesh +from vispy.visuals.filters import InstancedShadingFilter, WireframeFilter, TextureFilter + +# needed for instanced rendering to work +use(gl='gl+') + + +mesh_path = load_data_file('spot/spot.obj.gz') +texture_path = load_data_file('spot/spot.png') +vertices, faces, normals, texcoords = read_mesh(mesh_path) +texture = np.flipud(imread(texture_path)) + +canvas = scene.SceneCanvas(keys='interactive', bgcolor='white', show=True) +view = canvas.central_widget.add_view() + +view.camera = 'arcball' +view.camera.depth_value = 10 * (vertices.max() - vertices.min()) + +n_instances = 100 + +instance_colors = np.random.rand(n_instances, 3).astype(np.float32) +instance_positions = ((np.random.rand(n_instances, 3) - 0.5) * 10).astype(np.float32) +face_colors = np.random.rand(len(faces), 3) +instance_transforms = Rotation.random(n_instances).as_matrix().astype(np.float32) + +# Create a colored `MeshVisual`. +mesh = InstancedMesh( + vertices, + faces, + instance_colors=instance_colors, + face_colors=face_colors, + instance_positions=instance_positions, + instance_transforms=instance_transforms, + parent=view.scene, +) + + +wireframe_filter = WireframeFilter(width=1) +shading_filter = InstancedShadingFilter('smooth', shininess=1) +texture_filter = TextureFilter(texture, texcoords) +mesh.attach(wireframe_filter) +mesh.attach(shading_filter) +mesh.attach(texture_filter) + + +def attach_headlight(view): + light_dir = (0, 1, 0, 0) + shading_filter.light_dir = light_dir[:3] + initial_light_dir = view.camera.transform.imap(light_dir) + + @view.scene.transform.changed.connect + def on_transform_change(event): + transform = view.camera.transform + shading_filter.light_dir = transform.map(initial_light_dir)[:3] + + +attach_headlight(view) + + +shading_cycle = cycle(['flat', None, 'smooth']) +color_cycle = cycle([None, instance_colors]) +face_color_cycle = cycle([None, face_colors]) + + +@canvas.events.key_press.connect +def on_key_press(event): + if event.key == "t": + texture_filter.enabled = not texture_filter.enabled + canvas.update() + if event.key == 's': + shading_filter.shading = next(shading_cycle) + canvas.update() + if event.key == 'c': + mesh.instance_colors = next(color_cycle) + canvas.update() + if event.key == 'f': + mesh.set_data( + vertices=vertices, + faces=faces, + face_colors=next(face_color_cycle), + ) + canvas.update() + if event.key == 'w': + wireframe_filter.enabled = not wireframe_filter.enabled + canvas.update() + + +if __name__ == "__main__": + app.run() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/app/backends/_qt.py new/vispy-0.13.0/vispy/app/backends/_qt.py --- old/vispy-0.12.2/vispy/app/backends/_qt.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/app/backends/_qt.py 2023-05-12 20:06:40.000000000 +0200 @@ -22,6 +22,7 @@ from __future__ import division from time import sleep, time +import math import os import sys import atexit @@ -410,17 +411,10 @@ # either not PyQt5 backend or no parent window available pass - # Activate touch and gesture. - # NOTE: we only activate touch on OS X because there seems to be - # problems on Ubuntu computers with touchscreen. - # See https://github.com/vispy/vispy/pull/1143 - if sys.platform == 'darwin': - if PYQT6_API: - self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents) - self.grabGesture(QtCore.Qt.GestureType.PinchGesture) - else: - self.setAttribute(QtCore.Qt.WA_AcceptTouchEvents) - self.grabGesture(QtCore.Qt.PinchGesture) + # QNativeGestureEvent does not keep track of last or total + # values like QGestureEvent does + self._native_gesture_scale_values = [] + self._native_gesture_rotation_values = [] def screen_changed(self, new_screen): """Window moved from one display to another, resize canvas. @@ -563,50 +557,81 @@ def keyReleaseEvent(self, ev): self._keyEvent(self._vispy_canvas.events.key_release, ev) + def _handle_native_gesture_event(self, ev): + if self._vispy_canvas is None: + return + t = ev.gestureType() + # this is a workaround for what looks like a Qt bug where + # QNativeGestureEvent gives the wrong local position. + # See: https://bugreports.qt.io/browse/QTBUG-59595 + try: + pos = self.mapFromGlobal(ev.globalPosition().toPoint()) + except AttributeError: + # globalPos is deprecated in Qt6 + pos = self.mapFromGlobal(ev.globalPos()) + pos = pos.x(), pos.y() + + if t == QtCore.Qt.NativeGestureType.BeginNativeGesture: + self._vispy_canvas.events.touch( + type='gesture_begin', + pos=_get_event_xy(ev), + ) + elif t == QtCore.Qt.NativeGestureType.EndNativeGesture: + self._native_touch_total_rotation = [] + self._native_touch_total_scale = [] + self._vispy_canvas.events.touch( + type='gesture_end', + pos=_get_event_xy(ev), + ) + elif t == QtCore.Qt.NativeGestureType.RotateNativeGesture: + angle = ev.value() + last_angle = ( + self._native_gesture_rotation_values[-1] + if self._native_gesture_rotation_values + else None + ) + self._native_gesture_rotation_values.append(angle) + total_rotation_angle = math.fsum(self._native_gesture_rotation_values) + self._vispy_canvas.events.touch( + type="gesture_rotate", + pos=pos, + rotation=angle, + last_rotation=last_angle, + total_rotation_angle=total_rotation_angle, + ) + elif t == QtCore.Qt.NativeGestureType.ZoomNativeGesture: + scale = ev.value() + last_scale = ( + self._native_gesture_scale_values[-1] + if self._native_gesture_scale_values + else None + ) + self._native_gesture_scale_values.append(scale) + total_scale_factor = math.fsum(self._native_gesture_scale_values) + self._vispy_canvas.events.touch( + type="gesture_zoom", + pos=pos, + last_scale=last_scale, + scale=scale, + total_scale_factor=total_scale_factor, + ) + # QtCore.Qt.NativeGestureType.PanNativeGesture + # Qt6 docs seem to imply this is only supported on Wayland but I have + # not been able to test it. + # Two finger pan events are anyway converted to scroll/wheel events. + # On macOS, more fingers are usually swallowed by the OS (by spaces, + # mission control, etc.). + def event(self, ev): out = super(QtBaseCanvasBackend, self).event(ev) - t = ev.type() - qt_event_types = QtCore.QEvent.Type if PYQT6_API else QtCore.QEvent - # Two-finger pinch. - if t == qt_event_types.TouchBegin: - self._vispy_canvas.events.touch(type='begin') - if t == qt_event_types.TouchEnd: - self._vispy_canvas.events.touch(type='end') - if t == qt_event_types.Gesture: - pinch_gesture = QtCore.Qt.GestureType.PinchGesture if PYQT6_API else QtCore.Qt.PinchGesture - gesture = ev.gesture(pinch_gesture) - if gesture: - (x, y) = _get_qpoint_pos(gesture.centerPoint()) - scale = gesture.scaleFactor() - last_scale = gesture.lastScaleFactor() - rotation = gesture.rotationAngle() - self._vispy_canvas.events.touch( - type="pinch", - pos=(x, y), - last_pos=None, - scale=scale, - last_scale=last_scale, - rotation=rotation, - total_rotation_angle=gesture.totalRotationAngle(), - total_scale_factor=gesture.totalScaleFactor(), - ) - # General touch event. - elif t == qt_event_types.TouchUpdate: - if qt_lib == 'pyqt6' or qt_lib == 'pyside6': - points = ev.points() - # These variables are lists of (x, y) coordinates. - pos = [_get_qpoint_pos(p.position()) for p in points] - lpos = [_get_qpoint_pos(p.lastPosition()) for p in points] - else: - points = ev.touchPoints() - # These variables are lists of (x, y) coordinates. - pos = [_get_qpoint_pos(p.pos()) for p in points] - lpos = [_get_qpoint_pos(p.lastPos()) for p in points] - self._vispy_canvas.events.touch(type='touch', - pos=pos, - last_pos=lpos, - ) + # QNativeGestureEvent is Qt 5+ + if ( + (QT5_NEW_API or PYSIDE6_API or PYQT6_API) + and isinstance(ev, QtGui.QNativeGestureEvent) + ): + self._handle_native_gesture_event(ev) + return out def _keyEvent(self, func, ev): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/gloo/texture.py new/vispy-0.13.0/vispy/gloo/texture.py --- old/vispy-0.12.2/vispy/gloo/texture.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/gloo/texture.py 2023-05-12 20:06:40.000000000 +0200 @@ -37,13 +37,16 @@ return new_data -def downcast_to_32bit_if_needed(data, copy=False): +def downcast_to_32bit_if_needed(data, copy=False, dtype=None): """Downcast to 32bit dtype if necessary.""" - dtype = np.dtype(data.dtype) + if dtype is None: + dtype = data.dtype + dtype = np.dtype(dtype) if dtype.itemsize > 4: warnings.warn( f"GPUs can't support dtypes bigger than 32-bit, but got '{dtype}'. " - "Precision will be lost due to downcasting to 32-bit." + "Precision will be lost due to downcasting to 32-bit.", + stacklevel=2, ) size = min(dtype.itemsize, 4) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/cameras/base_camera.py new/vispy-0.13.0/vispy/scene/cameras/base_camera.py --- old/vispy-0.12.2/vispy/scene/cameras/base_camera.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/cameras/base_camera.py 2023-05-12 20:06:40.000000000 +0200 @@ -133,6 +133,8 @@ viewbox.events.mouse_release.connect(self.viewbox_mouse_event) viewbox.events.mouse_move.connect(self.viewbox_mouse_event) viewbox.events.mouse_wheel.connect(self.viewbox_mouse_event) + viewbox.events.gesture_zoom.connect(self.viewbox_mouse_event) + viewbox.events.gesture_rotate.connect(self.viewbox_mouse_event) viewbox.events.resize.connect(self.viewbox_resize_event) # todo: also add key events! (and also on viewbox (they're missing) @@ -144,6 +146,8 @@ viewbox.events.mouse_release.disconnect(self.viewbox_mouse_event) viewbox.events.mouse_move.disconnect(self.viewbox_mouse_event) viewbox.events.mouse_wheel.disconnect(self.viewbox_mouse_event) + viewbox.events.gesture_zoom.disconnect(self.viewbox_mouse_event) + viewbox.events.gesture_rotate.disconnect(self.viewbox_mouse_event) viewbox.events.resize.disconnect(self.viewbox_resize_event) @property diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/cameras/panzoom.py new/vispy-0.13.0/vispy/scene/cameras/panzoom.py --- old/vispy-0.12.2/vispy/scene/cameras/panzoom.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/cameras/panzoom.py 2023-05-12 20:06:40.000000000 +0200 @@ -207,7 +207,10 @@ center = self._scene_transform.imap(event.pos) self.zoom((1 + self.zoom_factor)**(-event.delta[1] * 30), center) event.handled = True - + elif event.type == 'gesture_zoom': + center = self._scene_transform.imap(event.pos) + self.zoom(1 - event.scale, center) + event.handled = True elif event.type == 'mouse_move': if event.press_event is None: return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/cameras/perspective.py new/vispy-0.13.0/vispy/scene/cameras/perspective.py --- old/vispy-0.12.2/vispy/scene/cameras/perspective.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/cameras/perspective.py 2023-05-12 20:06:40.000000000 +0200 @@ -62,6 +62,12 @@ if self._distance is not None: self._distance *= s self.view_changed() + elif event.type == 'gesture_zoom': + s = 1 - event.scale + self._scale_factor *= s + if self._distance is not None: + self._distance *= s + self.view_changed() @property def scale_factor(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/cameras/tests/test_perspective.py new/vispy-0.13.0/vispy/scene/cameras/tests/test_perspective.py --- old/vispy-0.12.2/vispy/scene/cameras/tests/test_perspective.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/cameras/tests/test_perspective.py 2023-05-12 20:06:40.000000000 +0200 @@ -82,4 +82,41 @@ assert v.camera.center == (-12.8, -12.8, 0) +@requires_application() +def test_panzoom_gesture_zoom(): + with TestingCanvas(size=(120, 200)) as canvas: + view = canvas.central_widget.add_view() + imdata = io.load_crate().astype('float32') / 255 + scene.visuals.Image(imdata, parent=view.scene) + view.camera = scene.PanZoomCamera(aspect=1) + + assert view.camera.rect.size == (1, 1) + + canvas.events.touch( + type="gesture_zoom", + pos=(60, 100), + scale=-1.0, + ) + + assert view.camera.rect.size == (2, 2) + + +@requires_application() +def test_turntable_gesture_zoom(): + with TestingCanvas(size=(120, 200)) as canvas: + view = canvas.central_widget.add_view() + imdata = io.load_crate().astype('float32') / 255 + scene.visuals.Image(imdata, parent=view.scene) + view.camera = scene.TurntableCamera() + + initial_scale_factor = view.camera.scale_factor + canvas.events.touch( + type="gesture_zoom", + pos=(60, 100), + scale=-1.0, + ) + + assert view.camera.scale_factor == 2 * initial_scale_factor + + run_tests_if_main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/canvas.py new/vispy-0.13.0/vispy/scene/canvas.py --- old/vispy-0.12.2/vispy/scene/canvas.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/canvas.py 2023-05-12 20:06:40.000000000 +0200 @@ -140,6 +140,7 @@ self.events.mouse_move.connect(self._process_mouse_event) self.events.mouse_release.connect(self._process_mouse_event) self.events.mouse_wheel.connect(self._process_mouse_event) + self.events.touch.connect(self._process_mouse_event) self.scene = SubScene() self.freeze() @@ -344,7 +345,12 @@ def _process_mouse_event(self, event): prof = Profiler() # noqa - deliver_types = ['mouse_press', 'mouse_wheel'] + deliver_types = [ + 'mouse_press', + 'mouse_wheel', + 'gesture_zoom', + 'gesture_rotate', + ] if self._send_hover_events: deliver_types += ['mouse_move'] @@ -524,6 +530,7 @@ self.events.mouse_move.disconnect(self._process_mouse_event) self.events.mouse_release.disconnect(self._process_mouse_event) self.events.mouse_wheel.disconnect(self._process_mouse_event) + self.events.touch.disconnect(self._process_mouse_event) # -------------------------------------------------- transform handling --- def push_viewport(self, viewport): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/events.py new/vispy-0.13.0/vispy/scene/events.py --- old/vispy-0.12.2/vispy/scene/events.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/events.py 2023-05-12 20:06:40.000000000 +0200 @@ -71,6 +71,15 @@ """The increment by which the mouse wheel has moved.""" return self.mouse_event.delta + @property + def scale(self): + """The scale of a gesture_zoom event""" + try: + return self.mouse_event.scale + except AttributeError: + errmsg = f"SceneMouseEvent type '{self.type}' has no scale" + raise TypeError(errmsg) + def copy(self): ev = self.__class__(self.mouse_event, self.visual) return ev diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/node.py new/vispy-0.13.0/vispy/scene/node.py --- old/vispy-0.12.2/vispy/scene/node.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/node.py 2023-05-12 20:06:40.000000000 +0200 @@ -63,7 +63,8 @@ # Add some events to the emitter groups: events = ['canvas_change', 'parent_change', 'children_change', 'transform_change', 'mouse_press', 'mouse_move', - 'mouse_release', 'mouse_wheel', 'key_press', 'key_release'] + 'mouse_release', 'mouse_wheel', 'key_press', 'key_release', + 'gesture_zoom', 'gesture_rotate'] # Create event emitter if needed (in subclasses that inherit from # Visual, we already have an emitter to share) if not hasattr(self, 'events'): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/scene/visuals.py new/vispy-0.13.0/vispy/scene/visuals.py --- old/vispy-0.12.2/vispy/scene/visuals.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/scene/visuals.py 2023-05-12 20:06:40.000000000 +0200 @@ -244,6 +244,7 @@ Image = create_visual_node(visuals.ImageVisual) ComplexImage = create_visual_node(visuals.ComplexImageVisual) InfiniteLine = create_visual_node(visuals.InfiniteLineVisual) +InstancedMesh = create_visual_node(visuals.InstancedMeshVisual) Isocurve = create_visual_node(visuals.IsocurveVisual) Isoline = create_visual_node(visuals.IsolineVisual) Isosurface = create_visual_node(visuals.IsosurfaceVisual) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/version.py new/vispy-0.13.0/vispy/version.py --- old/vispy-0.12.2/vispy/version.py 2023-03-20 16:16:37.000000000 +0100 +++ new/vispy-0.13.0/vispy/version.py 2023-05-12 20:07:06.000000000 +0200 @@ -1,4 +1,4 @@ # file generated by setuptools_scm # don't change, don't track in version control -__version__ = version = '0.12.2' -__version_tuple__ = version_tuple = (0, 12, 2) +__version__ = version = '0.13.0' +__version_tuple__ = version_tuple = (0, 13, 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/visuals/__init__.py new/vispy-0.13.0/vispy/visuals/__init__.py --- old/vispy-0.12.2/vispy/visuals/__init__.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/visuals/__init__.py 2023-05-12 20:06:40.000000000 +0200 @@ -21,6 +21,7 @@ from .gridmesh import GridMeshVisual # noqa from .histogram import HistogramVisual # noqa from .infinite_line import InfiniteLineVisual # noqa +from .instanced_mesh import InstancedMeshVisual # noqa from .isocurve import IsocurveVisual # noqa from .isoline import IsolineVisual # noqa from .isosurface import IsosurfaceVisual # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/visuals/filters/__init__.py new/vispy-0.13.0/vispy/visuals/filters/__init__.py --- old/vispy-0.12.2/vispy/visuals/filters/__init__.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/visuals/filters/__init__.py 2023-05-12 20:06:40.000000000 +0200 @@ -6,4 +6,4 @@ from .clipper import Clipper # noqa from .color import Alpha, ColorFilter, IsolineFilter, ZColormapFilter # noqa from .picking import PickingFilter # noqa -from .mesh import TextureFilter, ShadingFilter, WireframeFilter # noqa +from .mesh import TextureFilter, ShadingFilter, InstancedShadingFilter, WireframeFilter # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/visuals/filters/mesh.py new/vispy-0.13.0/vispy/visuals/filters/mesh.py --- old/vispy-0.12.2/vispy/visuals/filters/mesh.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/visuals/filters/mesh.py 2023-05-12 20:06:40.000000000 +0200 @@ -387,6 +387,10 @@ <https://github.com/vispy/vispy/blob/main/examples/basics/scene/mesh_shading.py>`_ example script. """ + _shaders = { + 'vertex': shading_vertex_template, + 'fragment': shading_fragment_template, + } def __init__(self, shading='flat', ambient_coefficient=(1, 1, 1, 1), @@ -412,8 +416,8 @@ self._enabled = enabled - vfunc = Function(shading_vertex_template) - ffunc = Function(shading_fragment_template) + vfunc = Function(self._shaders['vertex']) + ffunc = Function(self._shaders['fragment']) self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32)) vfunc['normal'] = self._normals @@ -572,6 +576,29 @@ super()._detach(visual) +instanced_shading_vertex_template = shading_vertex_template.replace( + "$normal", + "mat3($instance_transform_x, $instance_transform_y, $instance_transform_z) * $normal" +) + + +class InstancedShadingFilter(ShadingFilter): + """Shading filter modified for use with :class:`~vispy.visuals.InstancedMeshVisual`. + + See :class:`ShadingFilter` for details and usage. + """ + _shaders = { + 'vertex': instanced_shading_vertex_template, + 'fragment': ShadingFilter._shaders['fragment'], + } + + def _attach(self, visual): + super()._attach(visual) + self.vshader['instance_transform_x'] = visual._instance_transforms_vbos[0] + self.vshader['instance_transform_y'] = visual._instance_transforms_vbos[1] + self.vshader['instance_transform_z'] = visual._instance_transforms_vbos[2] + + wireframe_vertex_template = """ varying vec3 v_bc; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/visuals/instanced_mesh.py new/vispy-0.13.0/vispy/visuals/instanced_mesh.py --- old/vispy-0.12.2/vispy/visuals/instanced_mesh.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vispy-0.13.0/vispy/visuals/instanced_mesh.py 2023-05-12 20:06:40.000000000 +0200 @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright (c) Vispy Development Team. All Rights Reserved. +# Distributed under the (new) BSD License. See LICENSE.txt for more info. +# ----------------------------------------------------------------------------- + +"""An instanced version of MeshVisual with arbitrary shifts, transforms, and colors.""" + +from __future__ import division + +import numpy as np + +from ..gloo import VertexBuffer +from ..gloo.texture import downcast_to_32bit_if_needed +from ..color import ColorArray +from .filters import InstancedShadingFilter +from .shaders import Variable + +from .mesh import MeshVisual + + +_VERTEX_SHADER = """ +uniform bool use_instance_colors; + +// these attributes will be defined on an instance basis +attribute vec3 shift; +attribute vec3 transform_x; +attribute vec3 transform_y; +attribute vec3 transform_z; + +varying vec4 v_base_color; +void main() { + + v_base_color = $color_transform($base_color); + + // transform is generated from column vectors (new basis vectors) + // https://en.wikibooks.org/wiki/GLSL_Programming/Vector_and_Matrix_Operations#Constructors + mat3 instance_transform = mat3(transform_x, transform_y, transform_z); + vec3 pos_rotated = instance_transform * $to_vec4($position).xyz; + vec4 pos_shifted = $to_vec4(pos_rotated + shift); + gl_Position = $transform(pos_shifted); +} +""" + + +class InstancedMeshVisual(MeshVisual): + """Instanced Mesh visual. + + Mostly identical to MeshVisual, but additionally takes arrays of + of positions and transforms (optionally colors) to create multiple + instances of the mesh. + + Instancing is a rendering technique that re-uses the same mesh data + by applying transformations to vertices and vertex data or textures, + wich can drastically improve performance compared to having many + simple MeshVisuals. + + Parameters + ---------- + instance_positions : (I, 3) array + Coordinates for each instance of the mesh. + instance_transforms : (I, 3, 3) array + Matrices for the transforms to apply to each instance. + instance_colors : ColorArray + Matrices of colors for each instance. Colors + *args : list + Positional arguments to pass to :class:`~vispy.visuals.mesh.MeshVisual`. + **kwargs : dict + Keyword arguments to pass to :class:`~vispy.visuals.mesh.MeshVisual`. + + Examples + -------- + See example `scene/instanced_mesh_visual.py` in the gallery. + """ + + _shaders = { + 'vertex': _VERTEX_SHADER, + 'fragment': MeshVisual._shaders['fragment'], + } + + _shading_filter_class = InstancedShadingFilter + + def __init__(self, *args, instance_positions, instance_transforms, instance_colors=None, **kwargs): + self._instance_positions = None + self._instance_positions_vbo = None + self._instance_transforms = None + self._instance_transforms_vbos = None + self._instance_colors = None + self._instance_colors_vbo = None + super().__init__(*args, **kwargs) + self.instance_positions = instance_positions + self.instance_transforms = instance_transforms + self.instance_colors = instance_colors + + @property + def instance_positions(self): + return self._instance_positions + + @instance_positions.setter + def instance_positions(self, pos): + pos = np.reshape(pos, (-1, 3)) + if pos.ndim != 2 or pos.shape[-1] != 3: + raise ValueError(f'positions must be 3D coordinates, but provided data has shape {pos.shape}') + self._instance_positions = downcast_to_32bit_if_needed(pos, dtype=np.float32) + self._instance_positions_vbo = VertexBuffer(self._instance_positions, divisor=1) + self.mesh_data_changed() + + @property + def instance_transforms(self): + return self._instance_transforms + + @instance_transforms.setter + def instance_transforms(self, matrix): + matrix = np.reshape(matrix, (-1, 3, 3)) + if matrix.ndim != 3 or matrix.shape[1:] != (3, 3): + raise ValueError(f'transforms must be an array of 3x3 matrices, but provided data has shape {matrix.shape}') + self._instance_transforms = downcast_to_32bit_if_needed(matrix, dtype=np.float32) + # copy if not c contiguous + self._instance_transforms_vbos = ( + VertexBuffer(np.ascontiguousarray(self._instance_transforms[..., 0]), divisor=1), + VertexBuffer(np.ascontiguousarray(self._instance_transforms[..., 1]), divisor=1), + VertexBuffer(np.ascontiguousarray(self._instance_transforms[..., 2]), divisor=1), + ) + self.mesh_data_changed() + + @property + def instance_colors(self): + return self._instance_colors + + @instance_colors.setter + def instance_colors(self, colors): + if colors is not None: + colors = ColorArray(colors) + self._instance_colors_vbo = VertexBuffer(colors.rgba, divisor=1) + else: + self._instance_colors_vbo = Variable('base_color', self._color.rgba) + + self._instance_colors = colors + self.mesh_data_changed() + + def _update_data(self): + with self.events.data_updated.blocker(): + super()._update_data() + + # set instance buffers + self.shared_program.vert['base_color'] = self._instance_colors_vbo + self.shared_program['transform_x'] = self._instance_transforms_vbos[0] + self.shared_program['transform_y'] = self._instance_transforms_vbos[1] + self.shared_program['transform_z'] = self._instance_transforms_vbos[2] + self.shared_program['shift'] = self._instance_positions_vbo + + self.events.data_updated() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/visuals/markers.py new/vispy-0.13.0/vispy/visuals/markers.py --- old/vispy-0.12.2/vispy/visuals/markers.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/visuals/markers.py 2023-05-12 20:06:40.000000000 +0200 @@ -16,7 +16,7 @@ _VERTEX_SHADER = """ uniform float u_antialias; uniform float u_px_scale; -uniform bool u_scaling; +uniform int u_scaling; uniform bool u_spherical; attribute vec3 a_position; @@ -43,28 +43,38 @@ vec4 pos = vec4(a_position, 1); vec4 fb_pos = $visual_to_framebuffer(pos); + vec4 x; + vec4 size_vec; gl_Position = $framebuffer_to_render(fb_pos); // NOTE: gl_stuff uses framebuffer coords! - - if (u_scaling == true) { - // calculate point size from visual to framebuffer coords to determine size + if (u_scaling == 1) { + // scaling == "scene": scale marker using entire visual -> framebuffer set of transforms + x = $framebuffer_to_visual(fb_pos + vec4(big_float, 0, 0, 0)); + x = (x - pos); + size_vec = $visual_to_framebuffer(pos + normalize(x) * a_size); + $v_size = size_vec.x / size_vec.w - fb_pos.x / fb_pos.w; + v_edgewidth = ($v_size / a_size) * a_edgewidth; + } + else if (u_scaling == 2) { + // scaling == "visual": scale marker using only the Visual's transform // move horizontally in framebuffer space // then go to scene coordinates (not visual, so scaling is accounted for) - vec4 x = $framebuffer_to_scene(fb_pos + vec4(big_float, 0, 0, 0)); + x = $framebuffer_to_scene(fb_pos + vec4(big_float, 0, 0, 0)); // subtract position, so we get the scene-coordinate vector describing // an "horizontal direction parallel to the screen" vec4 scene_pos = $framebuffer_to_scene(fb_pos); x = (x - scene_pos); // multiply that direction by the size (in scene space) and add it to the position // this gives us the position of the edge of the point, which we convert in screen space - vec4 size_vec = $scene_to_framebuffer(scene_pos + normalize(x) * a_size); + size_vec = $scene_to_framebuffer(scene_pos + normalize(x) * a_size); // divide by `w` for perspective, and subtract pos // this gives us the actual screen-space size of the point $v_size = size_vec.x / size_vec.w - fb_pos.x / fb_pos.w; v_edgewidth = ($v_size / a_size) * a_edgewidth; } else { + // scaling == "fixed": marker is always the same number of pixels $v_size = a_size * u_px_scale; v_edgewidth = a_edgewidth * u_px_scale; } @@ -505,8 +515,20 @@ The color used to draw each symbol interior. symbol : str or array The style of symbol used to draw each marker (see Notes). - scaling : bool - If set to True, marker scales when rezooming. + scaling : str | bool + Scaling method of individual markers. If set to "fixed" (default) then + no scaling is done and markers will always be the same number of + pixels on the screen. If set to "scene" then the chain of transforms + from the Visual's transform to the transform mapping to the OpenGL + framebuffer are used to scaling the marker. This has the effect of the + marker staying the same size in the "scene" coordinate space and + changing size as the visualization is zoomed in and out. If set to + "visual" the marker is scaled only using the transform of the Visual + and not the rest of the scene/camera. This means that something like + a camera changing the view will not affect the size of the marker, but + the user can still scale it using the Visual's transform. For + backwards compatibility this can be set to the boolean ``False`` for + "fixed" or ``True`` for "scene". alpha : float The opacity level of the visual. antialias : float @@ -534,7 +556,7 @@ _symbol_shader_values = symbol_shader_values _symbol_shader = symbol_func - def __init__(self, scaling=False, alpha=1, antialias=1, spherical=False, + def __init__(self, scaling="fixed", alpha=1, antialias=1, spherical=False, light_color='white', light_position=(1, -1, 1), light_ambient=0.3, **kwargs): self._vbo = VertexBuffer() self._data = None @@ -554,6 +576,8 @@ if len(kwargs) > 0: self.set_data(**kwargs) + self._scaling = "fixed" + self._scaling_int = 0 self.scaling = scaling self.antialias = antialias self.light_color = light_color @@ -677,9 +701,20 @@ @scaling.setter def scaling(self, value): - value = bool(value) - self.shared_program['u_scaling'] = value + scaling_modes = { + False: 0, + True: 1, + "fixed": 0, + "scene": 1, + "visual": 2, + } + if value not in scaling_modes: + possible_options = ", ".join(repr(opt) for opt in scaling_modes) + raise ValueError(f"Unknown scaling option {value!r}, expected one of: {possible_options}") + scaling_int = scaling_modes[value] + self.shared_program['u_scaling'] = scaling_int self._scaling = value + self._scaling_int = scaling_int self.update() @property @@ -773,7 +808,7 @@ if self._data is None: return False view.view_program['u_px_scale'] = view.transforms.pixel_scale - view.view_program['u_scaling'] = self.scaling + view.view_program['u_scaling'] = self._scaling_int def _compute_bounds(self, axis, view): pos = self._data['a_position'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/visuals/tests/test_instanced_mesh.py new/vispy-0.13.0/vispy/visuals/tests/test_instanced_mesh.py --- old/vispy-0.12.2/vispy/visuals/tests/test_instanced_mesh.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vispy-0.13.0/vispy/visuals/tests/test_instanced_mesh.py 2023-05-12 20:06:40.000000000 +0200 @@ -0,0 +1,50 @@ +import numpy as np +from vispy import scene, use + +from vispy.testing import (TestingCanvas, requires_application, + run_tests_if_main, requires_pyopengl) + + +def setup_module(module): + use(gl='gl+') + + +def teardown_module(module): + use(gl='gl2') + + +@requires_pyopengl() +@requires_application() +def test_mesh_with_vertex_values(): + size = (80, 60) + with TestingCanvas(size=size) as c: + use(gl='gl+') + vert = np.array([[0, 0, 0], [0, 30, 0], [40, 0, 0]]) + faces = np.array([0, 1, 2]) + pos = np.array([[0, 0, 0], [80, 60, 0]]) + # identity and rotate 180 + trans = np.array([ + [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + [ + [-1, 0, 0], + [0, -1, 0], + [0, 0, 1], + ], + ]) + colors = ['red', 'blue'] + mesh = scene.visuals.InstancedMesh( + vertices=vert, faces=faces, instance_positions=pos, instance_transforms=trans, instance_colors=colors + ) + v = c.central_widget.add_view(border_width=0) + v.add(mesh) + render = c.render() + assert np.allclose(render[10, 10], (255, 0, 0, 255)) + assert np.allclose(render[-10, -10], (0, 0, 255, 255)) + assert np.allclose(render[30, 40], (0, 0, 0, 255)) + + +run_tests_if_main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy/visuals/volume.py new/vispy-0.13.0/vispy/visuals/volume.py --- old/vispy-0.12.2/vispy/visuals/volume.py 2023-03-20 16:16:17.000000000 +0100 +++ new/vispy-0.13.0/vispy/visuals/volume.py 2023-05-12 20:06:40.000000000 +0200 @@ -369,9 +369,13 @@ int maxi = -1; // Where the maximum value was encountered """, in_loop=""" - if( val > maxval ) { + if ( val > maxval ) { maxval = val; maxi = iter; + if ( maxval >= clim.y ) { + // stop if no chance of finding a higher maxval + iter = nsteps; + } } """, after_loop=""" @@ -395,8 +399,7 @@ } frag_depth_point = max_loc_tex * u_shape; gl_FragColor = applyColormap(maxval); - } - else { + } else { discard; } """, @@ -406,7 +409,7 @@ before_loop=""" float maxval = u_mip_cutoff; // The maximum encountered value float sumval = 0.0; // The sum of the encountered values - float scaled = 0.0; // The scaled value + float scale = 0.0; // The cumulative attenuation int maxi = -1; // Where the maximum value was encountered vec3 max_loc_tex = vec3(0.0); // Location where the maximum value was encountered """, @@ -415,9 +418,12 @@ // * attenuation value does not depend on data values // * negative values do not amplify instead of attenuate sumval = sumval + clamp((val - clim.x) / (clim.y - clim.x), 0.0, 1.0); - scaled = val * exp(-u_attenuation * (sumval - 1) / u_relative_step_size); - if( scaled > maxval ) { - maxval = scaled; + scale = exp(-u_attenuation * (sumval - 1) / u_relative_step_size); + if( maxval > scale * clim.y ) { + // stop if no chance of finding a higher maxval + iter = nsteps; + } else if( val * scale > maxval ) { + maxval = val * scale; maxi = iter; max_loc_tex = loc; } @@ -439,9 +445,13 @@ int mini = -1; // Where the minimum value was encountered """, in_loop=""" - if( val < minval ) { + if ( val < minval ) { minval = val; mini = iter; + if ( minval <= clim.x ) { + // stop if no chance of finding a lower minval + iter = nsteps; + } } """, after_loop=""" @@ -465,8 +475,7 @@ } frag_depth_point = min_loc_tex * u_shape; gl_FragColor = applyColormap(minval); - } - else { + } else { discard; } """, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy.egg-info/PKG-INFO new/vispy-0.13.0/vispy.egg-info/PKG-INFO --- old/vispy-0.12.2/vispy.egg-info/PKG-INFO 2023-03-20 16:16:37.000000000 +0100 +++ new/vispy-0.13.0/vispy.egg-info/PKG-INFO 2023-05-12 20:07:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: vispy -Version: 0.12.2 +Version: 0.13.0 Summary: Interactive visualization in Python Home-page: http://vispy.org Download-URL: https://pypi.python.org/pypi/vispy diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vispy-0.12.2/vispy.egg-info/SOURCES.txt new/vispy-0.13.0/vispy.egg-info/SOURCES.txt --- old/vispy-0.12.2/vispy.egg-info/SOURCES.txt 2023-03-20 16:16:37.000000000 +0100 +++ new/vispy-0.13.0/vispy.egg-info/SOURCES.txt 2023-05-12 20:07:07.000000000 +0200 @@ -245,6 +245,8 @@ examples/scene/image.py examples/scene/image_custom_kernel.py examples/scene/infinite_line.py +examples/scene/instanced_mesh.py +examples/scene/instanced_mesh_visual.py examples/scene/instanced_quad_visual.py examples/scene/isocurve.py examples/scene/isocurve_for_trisurface.py @@ -693,6 +695,7 @@ vispy/visuals/image.py vispy/visuals/image_complex.py vispy/visuals/infinite_line.py +vispy/visuals/instanced_mesh.py vispy/visuals/isocurve.py vispy/visuals/isoline.py vispy/visuals/isosurface.py @@ -784,6 +787,7 @@ vispy/visuals/tests/test_image.py vispy/visuals/tests/test_image_complex.py vispy/visuals/tests/test_infinite_line.py +vispy/visuals/tests/test_instanced_mesh.py vispy/visuals/tests/test_isosurface.py vispy/visuals/tests/test_linear_region.py vispy/visuals/tests/test_markers.py