Skip to content

Commit e51aaaf

Browse files
committed
Add PyQt6 and PySide{2,6} support.
1 parent dd8d947 commit e51aaaf

27 files changed

+107
-416
lines changed

docs/source/Figures.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This section outlines:
88
* General figure management.
99
* Functions for controlling the camera position.
1010
* Screenshotting the figure contents to a frozen image (file).
11-
* Embedding a figure into PyQt5_.
11+
* Embedding a figure into PyQt6_.
1212

1313
Overview
1414
--------

docs/source/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def add_url(name, url):
159159
'python': ('http://docs.python.org/3', None),
160160
'numpy': ('https://numpy.org/doc/stable/', None),
161161
'scipy': ('https://docs.scipy.org/doc/scipy/', None),
162-
'PyQt5': ('https://www.riverbankcomputing.com/static/Docs/PyQt5/', None),
162+
'PyQt6': ('https://www.riverbankcomputing.com/static/Docs/PyQt6/', None),
163163
'wxPython': ('https://docs.wxpython.org/', None),
164164
'matplotib': ('https://matplotlib.org', None),
165165
'pillow': ('https://pillow.readthedocs.io/en/stable/', None),
@@ -169,7 +169,7 @@ def add_url(name, url):
169169
def setup(app):
170170
# type: (Sphinx) -> None
171171

172-
# PyQt5 has all its cross reference targets in a special :sip: domain which
172+
# PyQt6 has all its cross reference targets in a special :sip: domain which
173173
# needs to be registered to be used. Don't ask me how this works. It's
174174
# mostly guess work and copy/paste from sphinx.domains.c.CDomain.
175175

docs/source/lookup_example.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,4 @@ Or, for time-critical applications, using a KDTree from either
6767
Note that having a ``MouseMoveEvent`` which modifies the contents of a figure and
6868
therefore requires ``fig.update()`` to be called for every such event requires a
6969
lot of processing power. A better option is to use an output outside of the
70-
renderer such as a `PyQt5.QtWidgets.QLabel`.
70+
renderer such as a `PyQt6.QtWidgets.QLabel`.

docs/source/quickstart.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Key features
3030
* Clean and easy to install.
3131
* Takes advantage of VTK's lesser known numpy support so that data can be efficiently copied by reference between numpy and VTK making it much faster than most of the VTK examples you'll find online.
3232
* Has direct support for STL plotting.
33-
* Can be embedded seamlessly into `PyQt5`_ applications.
33+
* Can be embedded seamlessly into `PyQt6`_ (or PyQt5, PySide2 or PySide6) applications.
3434
* Is freezable with `PyInstaller`_.
3535

3636

@@ -63,14 +63,14 @@ Optional requirements:
6363
Some features require you to install the following:
6464

6565
- `numpy-stl`_ or any other STL library if you want to plot STL files. `numpy-stl`_ is my STL library of choice.
66-
- `PyQt5`_ if you want to include your plots in a larger Qt GUI.
66+
- `PyQt6`_ if you want to include your plots in a larger Qt GUI.
6767
- `namegenerator`_ for fun.
6868

6969

7070
.. _numpy: http://numpy.org/
7171
.. _matplotlib: http://matplotlib.org/
7272
.. _vtk: https://pypi.org/project/vtk/
73-
.. _PyQt5: https://pypi.org/project/PyQt5/
73+
.. _PyQt6: https://pypi.org/project/PyQt6/
7474
.. _numpy-stl: https://pypi.org/project/numpy-stl/
7575
.. _PyInstaller: https://www.pyinstaller.org/
7676
.. _namegenerator: https://pypi.org/project/namegenerator/

docs/source/rst_prolog.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
.. _numpy: http://numpy.org/
1414
.. _matplotlib: http://matplotlib.org/
1515
.. _vtk: https://pypi.org/project/vtk/
16-
.. _PyQt5: https://pypi.org/project/PyQt5/
16+
.. _PyQt6: https://pypi.org/project/PyQt6/
1717
.. _numpy-stl: https://pypi.org/project/numpy-stl/
1818
.. _PyInstaller: https://www.pyinstaller.org/
1919
.. _namegenerator: https://pypi.org/project/namegenerator/

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"test_full": [
3535
"pytest",
3636
"pytest-order",
37-
"PyQt5",
37+
"PyQt6",
3838
"numpy-stl",
3939
"namegenerator",
4040
"pillow",

tests/_common.py

Lines changed: 0 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -3,203 +3,13 @@
33
Shared variables/markers for testing.
44
"""
55

6-
import os
7-
import numpy as np
86
from pathlib import Path
97

108
import pytest
119

12-
try:
13-
import inspect
14-
15-
except ImportError:
16-
VTKPLOTLIB_WINDOWLESS_TEST = False
17-
DEFAULT_MODE = ""
18-
else:
19-
VTKPLOTLIB_WINDOWLESS_TEST = bool(
20-
int(os.environ.get("VTKPLOTLIB_WINDOWLESS_TEST", "0")))
21-
22-
if VTKPLOTLIB_WINDOWLESS_TEST:
23-
DEFAULT_MODE = "r"
24-
else:
25-
DEFAULT_MODE = "w"
26-
2710
TEST_DIR = Path(__file__).parent.absolute().resolve() / "temp"
2811
TEST_DIR.mkdir(exist_ok=True)
2912

3013

31-
class AutoChecker(object):
32-
"""Attempts to assess reproducibility of visualisations between runs so I
33-
don't have to manually click through each test to verify that it looks OK.
34-
35-
Wraps around any test function which plots something but doesn't call
36-
``vpl.show()`` at the end. The wrapped function, if called in **write** mode
37-
(determined by the **mode** argument to that newly wrapped function) it will
38-
call the original function, screenshot and store the image, then call
39-
``vpl.show()`` so you can verify it. If called in **read** mode, then it
40-
will call the original function, screenshot the result and verify it matches
41-
the stored one, then closes so that you don't have to interact with it."""
42-
43-
def __init__(self):
44-
self.path = TEST_DIR / "test-data.json"
45-
self.load()
46-
47-
def auto_check_contents(self, test_func):
48-
name = self.name_function(test_func)
49-
50-
def wrapped(testcase=None, mode=DEFAULT_MODE):
51-
from vtkplotlib.figures.figure_manager import gcf, close, show
52-
53-
close()
54-
55-
np.random.seed(0)
56-
if testcase is None:
57-
out = test_func()
58-
else:
59-
out = test_func(testcase)
60-
from vtkplotlib.plots.BasePlot import BasePlot
61-
from vtkplotlib.figures.BaseFigure import BaseFigure
62-
if isinstance(out, BasePlot):
63-
out = None
64-
if out is None:
65-
out = gcf()
66-
67-
if isinstance(out, BaseFigure):
68-
fig = out
69-
else:
70-
fig = "gcf"
71-
72-
if mode == "r":
73-
correct_value = self.data[name]
74-
self.validate(self.reduce(out), correct_value)
75-
close(fig=fig)
76-
elif mode == "w":
77-
self.data[name] = self.reduce(out)
78-
if not isinstance(out, np.ndarray):
79-
show(fig=fig)
80-
self.save()
81-
else:
82-
show(fig=fig)
83-
84-
return wrapped
85-
86-
__call__ = auto_check_contents
87-
88-
@staticmethod
89-
def reduce_image_array(arr):
90-
"""Serialise an image array into something that can go into a json file
91-
i.e plain text."""
92-
# Ideally I'd just use 'shape' and 'crc32_checksum' but there are
93-
# subtle, visually invisible differences between snapshots from
94-
# different OSs and VTK versions. So image matching must be fuzzy
95-
# meaning we have to store the whole image.
96-
# Interestingly bz2 gives better compression than PNG.
97-
import zlib, bz2, base64
98-
return {
99-
"shape": list(arr.shape),
100-
"crc32_checksum": zlib.crc32(arr.tobytes("C")),
101-
"image": base64.b64encode(bz2.compress(arr.tobytes("C"))).decode()
102-
}
103-
104-
@classmethod
105-
def reduce_fig(cls, fig):
106-
from vtkplotlib.figures.figure_manager import screenshot_fig
107-
return cls.reduce_image_array(screenshot_fig(fig=fig))
108-
109-
@classmethod
110-
def reduce(cls, obj):
111-
if isinstance(obj, dict):
112-
return obj
113-
if isinstance(obj, np.ndarray):
114-
return cls.reduce_image_array(obj)
115-
return cls.reduce_fig(obj)
116-
117-
def save(self):
118-
import json
119-
text = json.dumps(self.data, indent=2)
120-
self.path.write_text(text)
121-
122-
def load(self):
123-
import json
124-
if self.path.exists():
125-
text = self.path.read_text()
126-
self.data = json.loads(text)
127-
else:
128-
self.data = {}
129-
130-
@staticmethod
131-
def name_function(func):
132-
module = inspect.getmodulename(func.__code__.co_filename)
133-
name = str(module) + "." + func.__name__
134-
assert "__main__" not in name
135-
return name
136-
137-
def validate(self, old_dic, new_dic):
138-
assert old_dic["shape"] == new_dic["shape"]
139-
if old_dic["crc32_checksum"] == new_dic["crc32_checksum"]:
140-
return True
141-
old_im = self.extract_dic_image(old_dic)
142-
new_im = self.extract_dic_image(new_dic)
143-
self.assertLess(np.abs(old_im - new_im.astype(float)).mean(), 5)
144-
145-
@staticmethod
146-
def extract_dic_image(dic):
147-
import bz2, base64
148-
shape = dic["shape"]
149-
bin = bz2.decompress(base64.b64decode(dic["image"].encode()))
150-
arr = np.frombuffer(bin, np.uint8).reshape(shape)
151-
return arr
152-
153-
def show_saved(self, func_name):
154-
from matplotlib import pylab
155-
pylab.imshow(self.extract_dic_image(self.data[func_name]))
156-
pylab.show()
157-
158-
159-
_checker = None
160-
161-
162-
def checker(*no_arguments):
163-
if len(no_arguments):
164-
raise TypeError("Checker takes no arguments. You probably forgot the "
165-
"parenthesis. Should be `@checker()\\n"
166-
"def function...`.")
167-
global _checker
168-
if _checker is None:
169-
_checker = AutoChecker()
170-
return _checker
171-
172-
173-
def reset():
174-
checker().data.clear()
175-
checker().save()
176-
global _checker
177-
_checker = None
178-
179-
180-
@checker()
181-
def test_quick_test_plot():
182-
import vtkplotlib as vpl
183-
vpl.quick_test_plot()
184-
185-
186-
def test_checker():
187-
188-
test_quick_test_plot(mode=DEFAULT_MODE and "w")
189-
190-
checker().save()
191-
checker().load()
192-
193-
test_quick_test_plot(mode=DEFAULT_MODE and "r")
194-
195-
196-
requires_interaction = pytest.mark.skipif(VTKPLOTLIB_WINDOWLESS_TEST,
197-
reason="Requires manual interaction.")
198-
199-
20014
def numpy_stl():
20115
return pytest.importorskip("stl.mesh", reason="Requires numpy-stl")
202-
203-
204-
if __name__ == "__main__":
205-
test_checker()

tests/test_colors.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
from matplotlib import cm
1313

14-
from tests._common import checker
15-
1614
pytestmark = pytest.mark.order(0)
1715

1816
# Target output. Everything below should normalise to this.
@@ -31,15 +29,6 @@
3129
(u"bright green", A, None),
3230
]
3331

34-
try:
35-
from PyQt5.QtGui import QColor
36-
parameters += [
37-
(QColor(*(int(i * 255) for i in RGB)), A, None),
38-
(QColor("#01FF06"), A, None),
39-
]
40-
except ImportError:
41-
pass
42-
4332

4433
@pytest.mark.parametrize(("rgb", "a", "warns"), parameters)
4534
def test_as_rgb_a(rgb, a, warns):

0 commit comments

Comments
 (0)