diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 00000000..11082d25
--- /dev/null
+++ b/.codespellrc
@@ -0,0 +1,4 @@
+[codespell]
+skip = *.pyc,*.txt,*.gif,*.png,*.jpg,*.ply,*.vtk,*.vti,*.vtu,*.js,*.html,*.doctree,*.ttf,*.woff,*.woff2,*.eot,*.mp4,*.inv,*.pickle,*.ipynb,flycheck*,./.git/*,./.hypothesis/*,*.yml,doc/_build/*,./doc/images/*,./dist/*,*~,.hypothesis*,./doc/examples/*,*.mypy_cache/*,*cover,./tests/tinypages/_build/*,*/_autosummary/*
+ignore-words-list = lod,byteorder,flem,parm,doubleclick,revered,PullRequest
+quiet-level = 3
\ No newline at end of file
diff --git a/.coveragerc b/.coveragerc
index 5e6ab884..ef06fc87 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -4,6 +4,7 @@ source = pyvistaqt
omit =
*/setup.py
*/pyvistaqt/rwi.py
+ */py.typed
[report]
exclude_lines =
diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..3b5a96aa
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,36 @@
+[flake8]
+max-line-length = 100
+exclude =
+ __pycache__,
+ .venv,
+ .cache,
+ .eggs
+ .git,
+ .tox,
+ *.egg-info,
+ *.pyc,
+ *.pyi,
+ build,
+ dist,
+ # This is adopted from VTK
+ pyvistaqt/rwi.py,
+max-complexity = 10
+doctests = true
+extend-ignore =
+ # whitespace before ':'
+ E203,
+ # line break before binary operator
+ W503,
+ # line length too long
+ E501,
+ # do not assign a lambda expression, use a def
+ E731,
+ # missing class docstring; use ``__init__`` docstring instead
+ D101,
+ # missing docstring in magic method
+ D105,
+ # Qt uses camelCase
+ N802
+per-file-ignores =
+ # "Imported but unused: happens with packages
+ __init__.py:F401
\ No newline at end of file
diff --git a/.isort.cfg b/.isort.cfg
deleted file mode 100644
index ba2778dc..00000000
--- a/.isort.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[settings]
-multi_line_output=3
-include_trailing_comma=True
-force_grid_wrap=0
-use_parentheses=True
-line_length=88
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..153d0aea
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,64 @@
+repos:
+- repo: https://github.com/psf/black
+ rev: 22.3.0
+ hooks:
+ - id: black
+
+- repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+
+- repo: https://gitlab.com/PyCQA/flake8
+ rev: 3.9.2
+ hooks:
+ - id: flake8
+ additional_dependencies: [
+ "flake8-black==0.3.2",
+ "flake8-isort==4.1.1",
+ "flake8-quotes==3.3.1",
+ ]
+
+- repo: https://github.com/codespell-project/codespell
+ rev: v2.1.0
+ hooks:
+ - id: codespell
+ args: [
+ "doc examples examples_flask pyvista tests",
+ "*.py *.rst *.md",
+ ]
+
+- repo: https://github.com/pycqa/pydocstyle
+ rev: 6.1.1
+ hooks:
+ - id: pydocstyle
+ additional_dependencies: [toml==0.10.2]
+ files: ^(pyvista/|other/)
+ exclude: ^pyvista/ext/
+
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.3.0
+ hooks:
+ - id: check-merge-conflict
+ - id: debug-statements
+ - id: no-commit-to-branch
+ args: [--branch, main]
+
+# this validates our github workflow files
+- repo: https://github.com/python-jsonschema/check-jsonschema
+ rev: 0.16.2
+ hooks:
+ - id: check-github-workflows
+
+- repo: local
+ hooks:
+ - id: pylint
+ name: pylint
+ entry: pylint
+ language: system
+ types: [python]
+ args:
+ [
+ "-rn", # Only display messages
+ "-sn", # Don't display the score
+ ]
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
index a0629152..876e6f32 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -3,18 +3,24 @@
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
-extension-pkg-whitelist=vtk
+extension-pkg-whitelist=vtk,
+ PyQt5,
+ PyQt6,
+ PySide2,
+ PySide6
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10
# Add files or directories to the blacklist. They should be base names, not
# paths.
-ignore=CVS
+ignore=rwi.py,
+ conf.py,
+ conftest.py
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
-ignore-patterns=
+ignore-patterns=test_.*[.]py
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
@@ -60,17 +66,7 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
-disable=print-statement,
- parameter-unpacking,
- unpacking-in-except,
- old-raise-syntax,
- backtick,
- long-suffix,
- old-ne-operator,
- old-octal-literal,
- import-star-module-level,
- non-ascii-bytes-literal,
- raw-checker-failed,
+disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
@@ -78,71 +74,13 @@ disable=print-statement,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
- apply-builtin,
- basestring-builtin,
- buffer-builtin,
- cmp-builtin,
- coerce-builtin,
- execfile-builtin,
- file-builtin,
- long-builtin,
- raw_input-builtin,
- reduce-builtin,
- standarderror-builtin,
- unicode-builtin,
- xrange-builtin,
- coerce-method,
- delslice-method,
- getslice-method,
- setslice-method,
- no-absolute-import,
- old-division,
- dict-iter-method,
- dict-view-method,
- next-method-called,
- metaclass-assignment,
- indexing-exception,
- raising-string,
- reload-builtin,
- oct-method,
- hex-method,
- nonzero-method,
- cmp-method,
- input-builtin,
- round-builtin,
- intern-builtin,
- unichr-builtin,
- map-builtin-not-iterating,
- zip-builtin-not-iterating,
- range-builtin-not-iterating,
- filter-builtin-not-iterating,
- using-cmp-argument,
- eq-without-hash,
- div-method,
- idiv-method,
- rdiv-method,
- exception-message-attribute,
- invalid-str-codec,
- sys-max-int,
- bad-python3-import,
- deprecated-string-function,
- deprecated-str-translate-call,
- deprecated-itertools-function,
- deprecated-types-field,
- next-method-defined,
- dict-items-not-iterating,
- dict-keys-not-iterating,
- dict-values-not-iterating,
- deprecated-operator-function,
- deprecated-urllib-function,
- xreadlines-attribute,
- deprecated-sys-function,
- exception-escape,
- comprehension-escape,
- bad-continuation,
arguments-differ,
no-name-in-module,
- no-member
+ no-member,
+ # Redundant alias imports required for type hinting by PEP 484
+ useless-import-alias,
+ # Qt uses PascalCase
+ invalid-name,
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
@@ -446,8 +384,11 @@ max-module-lines=1000
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,
- dict-separator
+#
+# Removed in version 2.15
+# https://pylint.pycqa.org/en/latest/whatsnew/2/2.6/summary.html#summary-release-highlights
+; no-space-check=trailing-comma,
+; dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
diff --git a/docs/conf.py b/docs/conf.py
index 443edd55..64b9850f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,38 +1,47 @@
+"""Configuration file for sphinx documentation."""
+from __future__ import annotations
+
import datetime
import os
import sys
+import warnings
+from typing import Optional
+
+import numpy as np
+import pyvista
+import sphinx_rtd_theme
+from docutils.parsers.rst import directives
+from sphinx.ext.autosummary import Autosummary, get_documenter
+from sphinx.util.inspect import safe_getattr
+
+import pyvistaqt
if sys.version_info >= (3, 0):
import faulthandler
+
faulthandler.enable()
sys.path.insert(0, os.path.abspath('.'))
-
-import numpy as np
-# -- pyvista configuration ---------------------------------------------------
-import pyvista
-
-import pyvistaqt
-
# Manage errors
pyvista.set_error_output_file('errors.txt')
+
# Ensure that offscreen rendering is used for docs generation
-pyvista.OFF_SCREEN = True # Not necessary - simply an insurance policy
+pyvista.OFF_SCREEN = True # Not necessary - simply an insurance policy
pyvista.BUILDING_GALLERY = True
+
# Preferred plotting style for documentation
pyvista.set_plot_theme('document')
pyvista.rcParams['window_size'] = np.array([1024, 768]) * 2
+
# Save figures in specified directory
pyvista.FIGURE_PATH = os.path.join(os.path.abspath('./images/'), 'auto-generated/')
if not os.path.exists(pyvista.FIGURE_PATH):
os.makedirs(pyvista.FIGURE_PATH)
-# SG warnings
-import warnings
warnings.filterwarnings(
- "ignore",
+ 'ignore',
category=UserWarning,
message='Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure.',
)
@@ -45,16 +54,17 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
-extensions = ['sphinx.ext.autodoc',
- 'sphinx.ext.napoleon',
- 'sphinx.ext.doctest',
- 'sphinx.ext.autosummary',
- 'notfound.extension',
- 'sphinx_copybutton',
- 'sphinx.ext.extlinks',
- 'sphinx.ext.coverage',
- 'sphinx.ext.intersphinx'
- ]
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.doctest',
+ 'sphinx.ext.autosummary',
+ 'notfound.extension',
+ 'sphinx_copybutton',
+ 'sphinx.ext.extlinks',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.intersphinx',
+]
linkcheck_retries = 3
@@ -70,10 +80,14 @@
master_doc = 'index'
# General information about the project.
-project = u'PyVistaQt'
+project = 'PyVistaQt'
year = datetime.date.today().year
-copyright = u'2017-{}, The PyVista Developers'.format(year)
-author = u'Alex Kaszynski and Bane Sullivan'
+copyright = (
+ '2017-{}, The PyVista Developers'.format( # pylint: disable=redefined-builtin,line-too-long
+ year
+ )
+)
+author = 'Alex Kaszynski and Bane Sullivan'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -89,13 +103,18 @@
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
+# Usually you set 'language' from the command line for these cases.
+language: Optional[str] = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints']
+exclude_patterns = [
+ '_build',
+ 'Thumbs.db',
+ '.DS_Store',
+ '**.ipynb_checkpoints',
+]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'friendly'
@@ -112,24 +131,35 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_context = {
- # Enable the "Edit in GitHub link within the header of each page.
+ # Enable the 'Edit in GitHub link within the header of each page.
'display_github': False,
# Set the following variables to generate the resulting github URL for each page.
- # Format Template: https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}
+ # Format Template: https://{{ github_host|default('github.com') }}/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}
'github_user': 'pyvista',
'github_repo': 'pyvistaqt',
'github_version': 'main/docs/',
'menu_links_name': 'Getting Connected',
'menu_links': [
- (' Slack Community', 'http://slack.pyvista.org'),
- (' Support', 'https://github.com/pyvista/pyvista-support'),
- (' Source Code', 'https://github.com/pyvista/pyvistaqt'),
- (' Contributing', 'https://github.com/pyvista/pyvistaqt/blob/main/CONTRIBUTING.md'),
+ (
+ ' Slack Community',
+ 'http://slack.pyvista.org',
+ ),
+ (
+ ' Support',
+ 'https://github.com/pyvista/pyvista-support',
+ ),
+ (
+ ' Source Code',
+ 'https://github.com/pyvista/pyvistaqt',
+ ),
+ (
+ ' Contributing',
+ 'https://github.com/pyvista/pyvistaqt/blob/main/CONTRIBUTING.md',
+ ),
],
}
@@ -143,7 +173,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
+# so a file named 'default.css' will overwrite the builtin 'default.css'.
html_static_path = ['_static']
@@ -154,27 +184,21 @@
# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'https://docs.python.org/': None,
- 'https://docs.pyvista.org/': None,
+intersphinx_mapping = {
+ 'https://docs.python.org/': None,
+ 'https://docs.pyvista.org/': None,
}
# -- Custom 404 page
notfound_context = {
- 'body': '
Page not found.
\n\nPerhaps try the examples page.',
+ 'body': 'Page not found.
\n\nPerhaps try the examples page.',
}
notfound_no_urls_prefix = True
-from docutils.parsers.rst import directives
-# -- Autosummary options
-from sphinx.ext.autosummary import Autosummary, get_documenter
-from sphinx.util.inspect import safe_getattr
-
-
class AutoAutoSummary(Autosummary):
-
option_spec = {
'methods': directives.unchanged,
'attributes': directives.unchanged,
@@ -188,7 +212,7 @@ def get_members(obj, typ, include_public=None):
if not include_public:
include_public = []
items = []
- for name in sorted(obj.__dict__.keys()):#dir(obj):
+ for name in sorted(obj.__dict__.keys()): # dir(obj):
try:
documenter = get_documenter(AutoAutoSummary.app, safe_getattr(obj, name), obj)
except AttributeError:
@@ -206,17 +230,24 @@ def run(self):
c = getattr(m, class_name)
if 'methods' in self.options:
_, methods = self.get_members(c, ['method'], ['__init__'])
- self.content = ["~%s.%s" % (clazz, method) for method in methods if not method.startswith('_')]
+ self.content = [
+ '~%s.%s' % (clazz, method) for method in methods if not method.startswith('_')
+ ]
if 'attributes' in self.options:
_, attribs = self.get_members(c, ['attribute', 'property'])
- self.content = ["~%s.%s" % (clazz, attrib) for attrib in attribs if not attrib.startswith('_')]
- except:
+ self.content = [
+ '~%s.%s' % (clazz, attrib) for attrib in attribs if not attrib.startswith('_')
+ ]
+ except Exception: # pylint: disable=broad-except
print('Something went wrong when autodocumenting {}'.format(clazz))
finally:
- return super(AutoAutoSummary, self).run()
+ return super(
+ AutoAutoSummary, self # pylint: disable=lost-exception
+ ).run() # pylint: disable=lost-exception
+
def setup(app):
AutoAutoSummary.app = app
app.add_directive('autoautosummary', AutoAutoSummary)
- app.add_css_file("style.css")
- app.add_css_file("copybutton.css")
+ app.add_css_file('style.css')
+ app.add_css_file('copybutton.css')
diff --git a/environment.yml b/environment.yml
index c0999b85..2e7d2407 100644
--- a/environment.yml
+++ b/environment.yml
@@ -5,6 +5,7 @@ dependencies:
- python<3.9
- codecov
- ipython
+ - mypy
- numpy
- pytest
- pytest-cov
diff --git a/ignore_words.txt b/ignore_words.txt
index 08703815..35430c91 100644
--- a/ignore_words.txt
+++ b/ignore_words.txt
@@ -1,3 +1,4 @@
lod
byteorder
flem
+PullRequest
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..6173c3c5
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,17 @@
+[tool.isort]
+profile = "black"
+line_length = 100
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+ensure_newline_before_comments = true
+
+[tool.black]
+line-length = 100
+skip-string-normalization = true
+target-version = ["py38"]
+exclude='\.eggs|\.git|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist|node_modules'
+
+[tool.pydocstyle]
+match = '(?!coverage).*.py'
\ No newline at end of file
diff --git a/pytest.ini b/pytest.ini
index 50997d7b..533b2d34 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -12,4 +12,4 @@ filterwarnings =
ignore:.*`np.float` is a deprecated alias.*:DeprecationWarning
ignore:.*`np.object` is a deprecated alias.*:DeprecationWarning
ignore:.*`np.long` is a deprecated alias:DeprecationWarning
- ignore:.*Converting `np\.character` to a dtype is deprecated.*:DeprecationWarning
+ ignore:.*Converting `np\.character` to a dtype is deprecated.*:DeprecationWarning
\ No newline at end of file
diff --git a/pyvistaqt/__init__.py b/pyvistaqt/__init__.py
index 003c6288..cb70dd9b 100755
--- a/pyvistaqt/__init__.py
+++ b/pyvistaqt/__init__.py
@@ -6,35 +6,19 @@
except Exception as exc: # pragma: no cover # pylint: disable=broad-except
_exc_msg = exc
- # pylint: disable=too-few-public-methods
- class _QtBindingError:
- def __init__(self, *args, **kwargs):
- raise RuntimeError(f"No Qt binding was found, got: {_exc_msg}")
-
- # pylint: disable=too-few-public-methods
- class BackgroundPlotter(_QtBindingError):
- """Handle Qt binding error for BackgroundPlotter."""
-
- # pylint: disable=too-few-public-methods
- class MainWindow(_QtBindingError):
- """Handle Qt binding error for MainWindow."""
-
- # pylint: disable=too-few-public-methods
- class MultiPlotter(_QtBindingError):
- """Handle Qt binding error for MultiPlotter."""
-
- # pylint: disable=too-few-public-methods
- class QtInteractor(_QtBindingError):
- """Handle Qt binding error for QtInteractor."""
+ raise RuntimeError(f'No Qt binding was found, got: {_exc_msg}') from exc
else:
- from .plotting import BackgroundPlotter, MainWindow, MultiPlotter, QtInteractor
+ from .plotting import BackgroundPlotter as BackgroundPlotter
+ from .plotting import MultiPlotter as MultiPlotter
+ from .plotting import QtInteractor as QtInteractor
+ from .window import MainWindow as MainWindow
__all__ = [
- "__version__",
- "BackgroundPlotter",
- "MainWindow",
- "MultiPlotter",
- "QtInteractor",
+ '__version__',
+ 'BackgroundPlotter',
+ 'MainWindow',
+ 'MultiPlotter',
+ 'QtInteractor',
]
diff --git a/pyvistaqt/_version.py b/pyvistaqt/_version.py
index 41aab3cf..5098b481 100644
--- a/pyvistaqt/_version.py
+++ b/pyvistaqt/_version.py
@@ -1,6 +1,6 @@
"""Version info for pyvistaqt."""
# major, minor, patch
-VERSION_INFO = 0, 10, "dev0"
+VERSION_INFO = 0, 10, 'dev0'
# Nice string for the version
-__version__ = ".".join(map(str, VERSION_INFO))
+__version__ = '.'.join(map(str, VERSION_INFO))
diff --git a/pyvistaqt/counter.py b/pyvistaqt/counter.py
index e31a7a64..1fecd817 100644
--- a/pyvistaqt/counter.py
+++ b/pyvistaqt/counter.py
@@ -16,11 +16,9 @@ def __init__(self, count: int) -> None:
if isinstance(count, int) and count > 0:
self.count = count
elif count > 0:
- raise TypeError(
- f"Expected type of `count` to be `int` but got: {type(count)}"
- )
+ raise TypeError(f'Expected type of `count` to be `int` but got: {type(count)}')
else:
- raise ValueError("count is not strictly positive.")
+ raise ValueError('count is not strictly positive.')
@Slot()
def decrease(self) -> None:
diff --git a/pyvistaqt/dialog.py b/pyvistaqt/dialog.py
index c531da14..10d62b59 100644
--- a/pyvistaqt/dialog.py
+++ b/pyvistaqt/dialog.py
@@ -6,14 +6,7 @@
import pyvista as pv
from qtpy import QtCore
from qtpy.QtCore import Signal
-from qtpy.QtWidgets import (
- QDialog,
- QDoubleSpinBox,
- QFileDialog,
- QFormLayout,
- QHBoxLayout,
- QSlider,
-)
+from qtpy.QtWidgets import QDialog, QDoubleSpinBox, QFileDialog, QFormLayout, QHBoxLayout, QSlider
from .window import MainWindow
@@ -101,20 +94,16 @@ def _value_range(self) -> float:
def value(self) -> float:
"""Return the value of the slider."""
- return (
- float(super().value()) / self._max_int * self._value_range + self._min_value
- )
+ return float(super().value()) / self._max_int * self._value_range + self._min_value
def setValue(self, value: float) -> None: # pylint: disable=invalid-name
"""Set the value of the slider."""
- super().setValue(
- int((value - self._min_value) / self._value_range * self._max_int)
- )
+ super().setValue(int((value - self._min_value) / self._value_range * self._max_int))
def setMinimum(self, value: float) -> None: # pylint: disable=invalid-name
"""Set the minimum value of the slider."""
if value > self._max_value: # pragma: no cover
- raise ValueError("Minimum limit cannot be higher than maximum")
+ raise ValueError('Minimum limit cannot be higher than maximum')
self._min_value = value
self.setValue(self.value())
@@ -122,7 +111,7 @@ def setMinimum(self, value: float) -> None: # pylint: disable=invalid-name
def setMaximum(self, value: float) -> None: # pylint: disable=invalid-name
"""Set the maximum value of the slider."""
if value < self._min_value: # pragma: no cover
- raise ValueError("Minimum limit cannot be higher than maximum")
+ raise ValueError('Minimum limit cannot be higher than maximum')
self._max_value = value
self.setValue(self.value())
@@ -152,9 +141,7 @@ def __init__(
self.minimum = minimum
self.maximum = maximum
- self.spinbox = QDoubleSpinBox(
- value=value, minimum=minimum, maximum=maximum, decimals=4
- )
+ self.spinbox = QDoubleSpinBox(value=value, minimum=minimum, maximum=maximum, decimals=4)
self.addWidget(self.slider)
self.addWidget(self.spinbox)
@@ -166,11 +153,17 @@ def __init__(
return None
- def update_spinbox(self, value: float) -> None: # pylint: disable=unused-argument
+ def update_spinbox(
+ self,
+ value: float, # pylint: disable=unused-argument
+ ) -> None:
"""Set the value of the internal spinbox."""
self.spinbox.setValue(self.slider.value())
- def update_value(self, value: float) -> None: # pylint: disable=unused-argument
+ def update_value(
+ self,
+ value: float, # pylint: disable=unused-argument
+ ) -> None:
"""Update the value of the internal slider."""
# if self.spinbox.value() < self.minimum:
# self.spinbox.setValue(self.minimum)
@@ -200,9 +193,7 @@ class ScaleAxesDialog(QDialog):
accepted = Signal(float)
signal_close = Signal()
- def __init__(
- self, parent: MainWindow, plotter: pv.Plotter, show: bool = True
- ) -> None:
+ def __init__(self, parent: MainWindow, plotter: pv.Plotter, show: bool = True) -> None:
"""Initialize the scaling dialog."""
super().__init__(parent)
self.setGeometry(300, 300, 50, 50)
@@ -211,20 +202,14 @@ def __init__(
self.plotter = plotter
self.plotter.app_window.signal_close.connect(self.close)
- self.x_slider_group = RangeGroup(
- parent, self.update_scale, value=plotter.scale[0]
- )
- self.y_slider_group = RangeGroup(
- parent, self.update_scale, value=plotter.scale[1]
- )
- self.z_slider_group = RangeGroup(
- parent, self.update_scale, value=plotter.scale[2]
- )
+ self.x_slider_group = RangeGroup(parent, self.update_scale, value=plotter.scale[0])
+ self.y_slider_group = RangeGroup(parent, self.update_scale, value=plotter.scale[1])
+ self.z_slider_group = RangeGroup(parent, self.update_scale, value=plotter.scale[2])
form_layout = QFormLayout(self)
- form_layout.addRow("X Scale", self.x_slider_group)
- form_layout.addRow("Y Scale", self.y_slider_group)
- form_layout.addRow("Z Scale", self.z_slider_group)
+ form_layout.addRow('X Scale', self.x_slider_group)
+ form_layout.addRow('Y Scale', self.y_slider_group)
+ form_layout.addRow('Z Scale', self.z_slider_group)
self.setLayout(form_layout)
diff --git a/pyvistaqt/editor.py b/pyvistaqt/editor.py
index 96dabab9..bcf9398a 100644
--- a/pyvistaqt/editor.py
+++ b/pyvistaqt/editor.py
@@ -44,7 +44,7 @@ def _selection_callback() -> None:
self.tree_widget.itemSelectionChanged.connect(_selection_callback)
self.setLayout(self.layout)
- self.setWindowTitle("Editor")
+ self.setWindowTitle('Editor')
self.setModal(True)
self.update()
@@ -55,7 +55,7 @@ def update(self) -> None:
for idx, renderer in enumerate(self.renderers):
actors = renderer._actors # pylint: disable=protected-access
widget_idx = self.stacked_widget.addWidget(_get_renderer_widget(renderer))
- top_item = QTreeWidgetItem(self.tree_widget, [f"Renderer {idx}"])
+ top_item = QTreeWidgetItem(self.tree_widget, [f'Renderer {idx}'])
top_item.setData(0, Qt.ItemDataRole.UserRole, widget_idx)
self.tree_widget.addTopLevelItem(top_item)
for name, actor in actors.items():
@@ -86,8 +86,8 @@ def _axes_callback(state: bool) -> None:
else:
renderer.hide_axes()
- axes = QCheckBox("Axes")
- if hasattr(renderer, "axes_widget"):
+ axes = QCheckBox('Axes')
+ if hasattr(renderer, 'axes_widget'):
axes.setChecked(renderer.axes_widget.GetEnabled())
else:
axes.setChecked(False)
@@ -105,7 +105,7 @@ def _get_actor_widget(actor: vtkActor) -> QWidget:
prop = actor.GetProperty()
# visibility
- visibility = QCheckBox("Visibility")
+ visibility = QCheckBox('Visibility')
visibility.setChecked(actor.GetVisibility())
visibility.toggled.connect(actor.SetVisibility)
layout.addWidget(visibility)
@@ -117,7 +117,7 @@ def _get_actor_widget(actor: vtkActor) -> QWidget:
opacity.setMaximum(1.0)
opacity.setValue(prop.GetOpacity())
opacity.valueChanged.connect(prop.SetOpacity)
- tmp_layout.addWidget(QLabel("Opacity"))
+ tmp_layout.addWidget(QLabel('Opacity'))
tmp_layout.addWidget(opacity)
layout.addLayout(tmp_layout)
diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py
index 496ce88d..ad21f8d0 100644
--- a/pyvistaqt/plotting.py
+++ b/pyvistaqt/plotting.py
@@ -89,11 +89,10 @@
else:
from qtpy import QtGui # pylint: disable=ungrouped-imports
-LOG = logging.getLogger("pyvistaqt")
+LOG = logging.getLogger('pyvistaqt')
LOG.setLevel(logging.CRITICAL)
LOG.addHandler(logging.StreamHandler())
-
# for display bugs due to older intel integrated GPUs, setting
# vtkmodules.qt.QVTKRWIBase = 'QGLWidget' could help. However, its use
# is discouraged and does not work well on VTK9+, so let's not bother
@@ -104,8 +103,8 @@
# LOG = logging.getLogger(__name__)
# LOG.setLevel('DEBUG')
-SAVE_CAM_BUTTON_TEXT = "Save Camera"
-CLEAR_CAMS_BUTTON_TEXT = "Clear Cameras"
+SAVE_CAM_BUTTON_TEXT = 'Save Camera'
+CLEAR_CAMS_BUTTON_TEXT = 'Clear Cameras'
def resample_image(arr: np.ndarray, max_size: int = 400) -> np.ndarray:
@@ -184,7 +183,7 @@ class QtInteractor(QVTKRenderWindowInteractor, BasePlotter):
key_press_event_signal = Signal(vtkGenericRenderWindowInteractor, str)
# pylint: disable=too-many-arguments
- def __init__(
+ def __init__( # noqa: C901
self,
parent: MainWindow = None,
title: str = None,
@@ -198,14 +197,14 @@ def __init__(
) -> None:
# pylint: disable=too-many-branches
"""Initialize Qt interactor."""
- LOG.debug("QtInteractor init start")
+ LOG.debug('QtInteractor init start')
self.url: QtCore.QUrl = None
# Cannot use super() here because
# QVTKRenderWindowInteractor silently swallows all kwargs
# because they use **kwargs in their constructor...
qvtk_kwargs = dict(parent=parent)
- for key in ("stereo", "iren", "rw", "wflags"):
+ for key in ('stereo', 'iren', 'rw', 'wflags'):
if key in kwargs:
qvtk_kwargs[key] = kwargs.pop(key)
with _no_base_plotter_init():
@@ -262,32 +261,26 @@ def __init__(
self.render_timer.timeout.connect(self.render)
self.render_timer.start(twait)
- if global_theme.depth_peeling["enabled"]:
+ if global_theme.depth_peeling['enabled']:
if self.enable_depth_peeling():
for renderer in self.renderers:
renderer.enable_depth_peeling()
self._first_time = False # Crucial!
- LOG.debug("QtInteractor init stop")
+ LOG.debug('QtInteractor init stop')
def _setup_interactor(self, off_screen: bool) -> None:
if off_screen:
self.iren: Any = None
else:
- self.iren = RenderWindowInteractor(
- self, interactor=self.ren_win.GetInteractor()
- )
- self.iren.interactor.RemoveObservers(
- "MouseMoveEvent"
- ) # slows window update?
+ self.iren = RenderWindowInteractor(self, interactor=self.ren_win.GetInteractor())
+ self.iren.interactor.RemoveObservers('MouseMoveEvent') # slows window update?
self.iren.initialize()
self.enable_trackball_style()
def _setup_key_press(self) -> None:
- self._observers: Dict[
- None, None
- ] = {} # Map of events to observers of self.iren
- self.iren.add_observer("KeyPressEvent", self.key_press_event)
+ self._observers: Dict[None, None] = {} # Map of events to observers of self.iren
+ self.iren.add_observer('KeyPressEvent', self.key_press_event)
self.reset_key_events()
def gesture_event(self, event: QGestureEvent) -> bool:
@@ -308,7 +301,7 @@ def _render(self, *args: Any, **kwargs: Any) -> BasePlotter.render:
"""Wrap ``BasePlotter.render``."""
return BasePlotter.render(self, *args, **kwargs)
- @conditional_decorator(threaded, platform.system() == "Darwin")
+ @conditional_decorator(threaded, platform.system() == 'Darwin')
def render(self) -> None:
"""Override the ``render`` method to handle threading issues."""
return self.render_signal.emit()
@@ -356,8 +349,8 @@ def link_views_across_plotters(
if not np.issubdtype(other_views.dtype, int):
raise TypeError(
- "Expected `other_views` type is int, or list or tuple of ints, "
- f"but {other_views.dtype} is given"
+ 'Expected `other_views` type is int, or list or tuple of ints, '
+ f'but {other_views.dtype} is given'
)
renderer = self.renderers[view]
@@ -378,7 +371,7 @@ def dragEnterEvent(self, event: QtGui.QDragEnterEvent) -> None:
# only call accept on files
event.accept()
except IOError as exception: # pragma: no cover
- warnings.warn(f"Exception when dragging files: {str(exception)}")
+ warnings.warn(f'Exception when dragging files: {str(exception)}')
# pylint: disable=invalid-name,useless-return
def dropEvent(self, event: QtCore.QEvent) -> None:
@@ -390,13 +383,13 @@ def dropEvent(self, event: QtCore.QEvent) -> None:
if os.path.isfile(filename):
self.add_mesh(pyvista.read(filename))
except IOError as exception: # pragma: no cover
- warnings.warn(f"Exception when dropping files: {str(exception)}")
+ warnings.warn(f'Exception when dropping files: {str(exception)}')
def close(self) -> None:
"""Quit application."""
if self._closed:
return
- if hasattr(self, "render_timer"):
+ if hasattr(self, 'render_timer'):
self.render_timer.stop()
BasePlotter.close(self)
QVTKRenderWindowInteractor.close(self)
@@ -426,7 +419,7 @@ class BackgroundPlotter(QtInteractor):
screenshots or debug testing.
allow_quit_keypress :
- Allow user to exit by pressing ``"q"``.
+ Allow user to exit by pressing ``'q'``.
toolbar : bool
If True, display the default camera toolbar. Defaults to True.
@@ -505,16 +498,16 @@ def __init__(
# self._closed=True until the BasePlotter.__init__
# is called
self._closed = True
- LOG.debug("BackgroundPlotter init start")
- _check_type(show, "show", [bool])
- _check_type(app, "app", [QApplication, type(None)])
- _check_type(window_size, "window_size", [tuple, type(None)])
- _check_type(off_screen, "off_screen", [bool, type(None)])
- _check_type(allow_quit_keypress, "allow_quit_keypress", [bool])
- _check_type(toolbar, "toolbar", [bool])
- _check_type(menu_bar, "menu_bar", [bool])
- _check_type(editor, "editor", [bool])
- _check_type(update_app_icon, "update_app_icon", [bool, type(None)])
+ LOG.debug('BackgroundPlotter init start')
+ _check_type(show, 'show', [bool])
+ _check_type(app, 'app', [QApplication, type(None)])
+ _check_type(window_size, 'window_size', [tuple, type(None)])
+ _check_type(off_screen, 'off_screen', [bool, type(None)])
+ _check_type(allow_quit_keypress, 'allow_quit_keypress', [bool])
+ _check_type(toolbar, 'toolbar', [bool])
+ _check_type(menu_bar, 'menu_bar', [bool])
+ _check_type(editor, 'editor', [bool])
+ _check_type(update_app_icon, 'update_app_icon', [bool, type(None)])
# toolbar
self._view_action: QAction = None
@@ -538,16 +531,14 @@ def __init__(
window_size = global_theme.window_size
# Remove notebook argument in case user passed it
- kwargs.pop("notebook", None)
+ kwargs.pop('notebook', None)
self.ipython = _setup_ipython()
self.app = _setup_application(app)
self.off_screen = _setup_off_screen(off_screen)
if app_window_class is None:
app_window_class = MainWindow
- self.app_window = app_window_class(
- title=kwargs.get("title", global_theme.title)
- )
+ self.app_window = app_window_class(title=kwargs.get('title', global_theme.title))
self.frame = QFrame(parent=self.app_window)
self.frame.setFrameStyle(QFrame.NoFrame)
vlayout = QVBoxLayout()
@@ -580,7 +571,9 @@ def __init__(
elif update_app_icon is None:
self.set_icon(
os.path.join(
- os.path.dirname(__file__), "data", "pyvista_logo_square.png"
+ os.path.dirname(__file__),
+ 'data',
+ 'pyvista_logo_square.png',
)
)
else:
@@ -588,8 +581,8 @@ def __init__(
# Keypress events
if self.iren is not None:
- self.add_key_event("S", self._qt_screenshot) # shift + s
- LOG.debug("BackgroundPlotter init stop")
+ self.add_key_event('S', self._qt_screenshot) # shift + s
+ LOG.debug('BackgroundPlotter init stop')
def reset_key_events(self) -> None:
"""Reset all of the key press events to their defaults.
@@ -599,7 +592,7 @@ def reset_key_events(self) -> None:
super().reset_key_events()
if self.allow_quit_keypress:
# pylint: disable=unnecessary-lambda
- self.add_key_event("q", lambda: self.close())
+ self.add_key_event('q', lambda: self.close())
def scale_axes_dialog(self, show: bool = True) -> ScaleAxesDialog:
"""Open scale axes dialog."""
@@ -629,9 +622,7 @@ def _close(self) -> None:
def update_app_icon(self) -> None:
"""Update the app icon if the user is not trying to resize the window."""
- if os.name == "nt" or not hasattr(
- self, "_last_window_size"
- ): # pragma: no cover
+ if os.name == 'nt' or not hasattr(self, '_last_window_size'): # pragma: no cover
# DO NOT EVEN ATTEMPT TO UPDATE ICON ON WINDOWS
return
cur_time = time.time()
@@ -677,16 +668,14 @@ def set_icon(self, img: Union[np.ndarray, str]) -> None:
and img.shape[-1] in (3, 4)
) and not isinstance(img, str):
raise ValueError(
- "img must be 3D uint8 ndarray with shape[1] == shape[2] and "
- "shape[2] == 3 or 4, or str"
+ 'img must be 3D uint8 ndarray with shape[1] == shape[2] and '
+ 'shape[2] == 3 or 4, or str'
)
if isinstance(img, np.ndarray):
- fmt_str = "Format_RGB"
- fmt_str += ("A8" if img.shape[2] == 4 else "") + "888"
+ fmt_str = 'Format_RGB'
+ fmt_str += ('A8' if img.shape[2] == 4 else '') + '888'
fmt = getattr(QtGui.QImage, fmt_str)
- img = QtGui.QPixmap.fromImage(
- QtGui.QImage(img.copy(), img.shape[1], img.shape[0], fmt)
- )
+ img = QtGui.QPixmap.fromImage(QtGui.QImage(img.copy(), img.shape[1], img.shape[0], fmt))
# Currently no way to check if str/path is actually correct (want to
# allow resource paths and the like so os.path.isfile is no good)
# and icon.isNull() returns False even if the path is bogus.
@@ -695,7 +684,7 @@ def set_icon(self, img: Union[np.ndarray, str]) -> None:
def _qt_screenshot(self, show: bool = True) -> FileDialog:
return FileDialog(
self.app_window,
- filefilter=["Image File (*.png)", "JPEG (*.jpeg)"],
+ filefilter=['Image File (*.png)', 'JPEG (*.jpeg)'],
show=show,
directory=bool(os.getcwd()),
callback=self.screenshot,
@@ -705,14 +694,14 @@ def _qt_export_vtkjs(self, show: bool = True) -> FileDialog:
"""Spawn an save file dialog to export a vtkjs file."""
return FileDialog(
self.app_window,
- filefilter=["VTK JS File(*.vtkjs)"],
+ filefilter=['VTK JS File(*.vtkjs)'],
show=show,
directory=bool(os.getcwd()),
callback=self.export_vtkjs,
)
def _toggle_edl(self) -> None:
- if hasattr(self.renderer, "edl_pass"):
+ if hasattr(self.renderer, 'edl_pass'):
return self.renderer.disable_eye_dome_lighting()
return self.renderer.enable_eye_dome_lighting()
@@ -741,7 +730,10 @@ def __del__(self) -> None: # pragma: no cover
self.app_window.close()
def add_callback(
- self, func: Callable, interval: int = 1000, count: Optional[int] = None
+ self,
+ func: Callable,
+ interval: int = 1000,
+ count: Optional[int] = None,
) -> None:
"""Add a function that can update the scene in the background.
@@ -776,21 +768,24 @@ def save_camera_position(self) -> None:
if self.camera_position is not None:
camera_position: Any = self.camera_position[:] # py2.7 copy compatibility
- if hasattr(self, "saved_cameras_tool_bar"):
+ if hasattr(self, 'saved_cameras_tool_bar'):
def load_camera_position() -> None:
# pylint: disable=attribute-defined-outside-init
self.camera_position = camera_position
- self.saved_cameras_tool_bar.addAction(f"Cam {ncam}", load_camera_position)
+ self.saved_cameras_tool_bar.addAction(f'Cam {ncam}', load_camera_position)
if ncam < 10:
self.add_key_event(str(ncam), load_camera_position)
def clear_camera_positions(self) -> None:
"""Clear all camera positions."""
- if hasattr(self, "saved_cameras_tool_bar"):
+ if hasattr(self, 'saved_cameras_tool_bar'):
for action in self.saved_cameras_tool_bar.actions():
- if action.text() not in [SAVE_CAM_BUTTON_TEXT, CLEAR_CAMS_BUTTON_TEXT]:
+ if action.text() not in [
+ SAVE_CAM_BUTTON_TEXT,
+ CLEAR_CAMS_BUTTON_TEXT,
+ ]:
self.saved_cameras_tool_bar.removeAction(action)
self.saved_camera_positions = []
@@ -803,38 +798,38 @@ def _add_action(self, tool_bar: QToolBar, key: str, method: Any) -> QAction:
def add_toolbars(self) -> None:
"""Add the toolbars."""
# Camera toolbar
- self.default_camera_tool_bar = self.app_window.addToolBar("Camera Position")
+ self.default_camera_tool_bar = self.app_window.addToolBar('Camera Position')
def _view_vector(*args: Any) -> None:
return self.view_vector(*args)
cvec_setters = {
# Viewing vector then view up vector
- "Top (-Z)": lambda: _view_vector((0, 0, 1), (0, 1, 0)),
- "Bottom (+Z)": lambda: _view_vector((0, 0, -1), (0, 1, 0)),
- "Front (-Y)": lambda: _view_vector((0, 1, 0), (0, 0, 1)),
- "Back (+Y)": lambda: _view_vector((0, -1, 0), (0, 0, 1)),
- "Left (-X)": lambda: _view_vector((1, 0, 0), (0, 0, 1)),
- "Right (+X)": lambda: _view_vector((-1, 0, 0), (0, 0, 1)),
- "Isometric": lambda: _view_vector((1, 1, 1), (0, 0, 1)),
+ 'Top (-Z)': lambda: _view_vector((0, 0, 1), (0, 1, 0)),
+ 'Bottom (+Z)': lambda: _view_vector((0, 0, -1), (0, 1, 0)),
+ 'Front (-Y)': lambda: _view_vector((0, 1, 0), (0, 0, 1)),
+ 'Back (+Y)': lambda: _view_vector((0, -1, 0), (0, 0, 1)),
+ 'Left (-X)': lambda: _view_vector((1, 0, 0), (0, 0, 1)),
+ 'Right (+X)': lambda: _view_vector((-1, 0, 0), (0, 0, 1)),
+ 'Isometric': lambda: _view_vector((1, 1, 1), (0, 0, 1)),
}
for key, method in cvec_setters.items():
- self._view_action = self._add_action(
- self.default_camera_tool_bar, key, method
- )
+ self._view_action = self._add_action(self.default_camera_tool_bar, key, method)
# pylint: disable=unnecessary-lambda
self._add_action(
- self.default_camera_tool_bar, "Reset", lambda: self.reset_camera()
+ self.default_camera_tool_bar,
+ 'Reset',
+ lambda: self.reset_camera(),
)
# Saved camera locations toolbar
self.saved_camera_positions = []
- self.saved_cameras_tool_bar = self.app_window.addToolBar(
- "Saved Camera Positions"
- )
+ self.saved_cameras_tool_bar = self.app_window.addToolBar('Saved Camera Positions')
self._add_action(
- self.saved_cameras_tool_bar, SAVE_CAM_BUTTON_TEXT, self.save_camera_position
+ self.saved_cameras_tool_bar,
+ SAVE_CAM_BUTTON_TEXT,
+ self.save_camera_position,
)
self._add_action(
self.saved_cameras_tool_bar,
@@ -847,45 +842,44 @@ def add_menu_bar(self) -> None:
self.main_menu = _create_menu_bar(parent=self.app_window)
self.app_window.signal_close.connect(self.main_menu.clear)
- file_menu = self.main_menu.addMenu("File")
- file_menu.addAction("Take Screenshot", self._qt_screenshot)
- file_menu.addAction("Export as VTKjs", self._qt_export_vtkjs)
+ file_menu = self.main_menu.addMenu('File')
+ file_menu.addAction('Take Screenshot', self._qt_screenshot)
+ file_menu.addAction('Export as VTKjs', self._qt_export_vtkjs)
file_menu.addSeparator()
# member variable for testing only
- self._menu_close_action = file_menu.addAction("Exit", self.app_window.close)
+ self._menu_close_action = file_menu.addAction('Exit', self.app_window.close)
- view_menu = self.main_menu.addMenu("View")
- self._edl_action = view_menu.addAction(
- "Toggle Eye Dome Lighting", self._toggle_edl
- )
- view_menu.addAction("Scale Axes", self.scale_axes_dialog)
- view_menu.addAction("Clear All", self.clear)
+ view_menu = self.main_menu.addMenu('View')
+ self._edl_action = view_menu.addAction('Toggle Eye Dome Lighting', self._toggle_edl)
+ view_menu.addAction('Scale Axes', self.scale_axes_dialog)
+ view_menu.addAction('Clear All', self.clear)
- tool_menu = self.main_menu.addMenu("Tools")
- tool_menu.addAction("Enable Cell Picking (through)", self.enable_cell_picking)
+ tool_menu = self.main_menu.addMenu('Tools')
+ tool_menu.addAction('Enable Cell Picking (through)', self.enable_cell_picking)
tool_menu.addAction(
- "Enable Cell Picking (visible)",
+ 'Enable Cell Picking (visible)',
lambda: self.enable_cell_picking(through=False),
)
- cam_menu = view_menu.addMenu("Camera")
+ cam_menu = view_menu.addMenu('Camera')
self._parallel_projection_action = cam_menu.addAction(
- "Toggle Parallel Projection", self._toggle_parallel_projection
+ 'Toggle Parallel Projection',
+ self._toggle_parallel_projection,
)
view_menu.addSeparator()
# Orientation marker
- orien_menu = view_menu.addMenu("Orientation Marker")
- orien_menu.addAction("Show All", self.show_axes_all)
- orien_menu.addAction("Hide All", self.hide_axes_all)
+ orien_menu = view_menu.addMenu('Orientation Marker')
+ orien_menu.addAction('Show All', self.show_axes_all)
+ orien_menu.addAction('Hide All', self.hide_axes_all)
# Bounds axes
- axes_menu = view_menu.addMenu("Bounds Axes")
- axes_menu.addAction("Add Bounds Axes (front)", self.show_bounds)
- axes_menu.addAction("Add Bounds Grid (back)", self.show_grid)
- axes_menu.addAction("Add Bounding Box", self.add_bounding_box)
+ axes_menu = view_menu.addMenu('Bounds Axes')
+ axes_menu.addAction('Add Bounds Axes (front)', self.show_bounds)
+ axes_menu.addAction('Add Bounds Grid (back)', self.show_grid)
+ axes_menu.addAction('Add Bounding Box', self.add_bounding_box)
axes_menu.addSeparator()
- axes_menu.addAction("Remove Bounding Box", self.remove_bounding_box)
- axes_menu.addAction("Remove Bounds", self.remove_bounds_axes)
+ axes_menu.addAction('Remove Bounding Box', self.remove_bounding_box)
+ axes_menu.addAction('Remove Bounds', self.remove_bounds_axes)
# A final separator to separate OS options
view_menu.addSeparator()
@@ -893,7 +887,7 @@ def add_menu_bar(self) -> None:
def add_editor(self) -> None:
"""Add the editor."""
self.editor = Editor(parent=self.app_window, renderers=self.renderers)
- self._editor_action = self.main_menu.addAction("Editor", self.editor.toggle)
+ self._editor_action = self.main_menu.addAction('Editor', self.editor.toggle)
self.app_window.signal_close.connect(self.editor.close)
@@ -944,13 +938,13 @@ def __init__(
**kwargs: Any,
) -> None:
"""Initialize the multi plotter."""
- _check_type(app, "app", [QApplication, type(None)])
- _check_type(nrows, "nrows", [int])
- _check_type(ncols, "ncols", [int])
- _check_type(show, "show", [bool])
- _check_type(window_size, "window_size", [tuple, type(None)])
- _check_type(title, "title", [str, type(None)])
- _check_type(off_screen, "off_screen", [bool, type(None)])
+ _check_type(app, 'app', [QApplication, type(None)])
+ _check_type(nrows, 'nrows', [int])
+ _check_type(ncols, 'ncols', [int])
+ _check_type(show, 'show', [bool])
+ _check_type(window_size, 'window_size', [tuple, type(None)])
+ _check_type(title, 'title', [str, type(None)])
+ _check_type(off_screen, 'off_screen', [bool, type(None)])
self.ipython = _setup_ipython()
self.app = _setup_application(app)
self.off_screen = _setup_off_screen(off_screen)
diff --git a/pyvistaqt/py.typed b/pyvistaqt/py.typed
new file mode 100644
index 00000000..270b15e2
--- /dev/null
+++ b/pyvistaqt/py.typed
@@ -0,0 +1 @@
+partial\n
\ No newline at end of file
diff --git a/pyvistaqt/rwi.py b/pyvistaqt/rwi.py
index 3865bbfd..078dae80 100644
--- a/pyvistaqt/rwi.py
+++ b/pyvistaqt/rwi.py
@@ -59,17 +59,19 @@
# Check whether a specific PyQt implementation was chosen
try:
import vtkmodules.qt
+
PyQtImpl = vtkmodules.qt.PyQtImpl
except ImportError:
pass
# Check whether a specific QVTKRenderWindowInteractor base
-# class was chosen, can be set to "QGLWidget" in
+# class was chosen, can be set to 'QGLWidget' in
# PyQt implementation version lower than Pyside6,
-# or "QOpenGLWidget" in Pyside6
-QVTKRWIBase = "QWidget"
+# or 'QOpenGLWidget' in Pyside6
+QVTKRWIBase = 'QWidget'
try:
import vtkmodules.qt
+
QVTKRWIBase = vtkmodules.qt.QVTKRWIBase
except ImportError:
pass
@@ -81,127 +83,91 @@
# Autodetect the PyQt implementation to use
try:
import PyQt6
- PyQtImpl = "PyQt6"
+
+ PyQtImpl = 'PyQt6'
except ImportError:
try:
import PySide6
- PyQtImpl = "PySide6"
+
+ PyQtImpl = 'PySide6'
except ImportError:
try:
import PyQt5
- PyQtImpl = "PyQt5"
+
+ PyQtImpl = 'PyQt5'
except ImportError:
try:
import PySide2
- PyQtImpl = "PySide2"
+
+ PyQtImpl = 'PySide2'
except ImportError:
try:
import PyQt4
- PyQtImpl = "PyQt4"
+
+ PyQtImpl = 'PyQt4'
except ImportError:
try:
import PySide
- PyQtImpl = "PySide"
+
+ PyQtImpl = 'PySide'
except ImportError:
- raise ImportError("Cannot load either PyQt or PySide")
+ raise ImportError('Cannot load either PyQt or PySide')
# Check the compatibility of PyQtImpl and QVTKRWIBase
-if QVTKRWIBase != "QWidget":
- if PyQtImpl in ["PyQt6", "PySide6"] and QVTKRWIBase == "QOpenGLWidget":
+if QVTKRWIBase != 'QWidget':
+ if PyQtImpl in ['PyQt6', 'PySide6'] and QVTKRWIBase == 'QOpenGLWidget':
pass # compatible
- elif PyQtImpl in ["PyQt5", "PySide2","PyQt4", "PySide"] and QVTKRWIBase == "QGLWidget":
+ elif PyQtImpl in ['PyQt5', 'PySide2', 'PyQt4', 'PySide'] and QVTKRWIBase == 'QGLWidget':
pass # compatible
else:
- raise ImportError("Cannot load " + QVTKRWIBase + " from " + PyQtImpl)
+ raise ImportError('Cannot load ' + QVTKRWIBase + ' from ' + PyQtImpl)
-if PyQtImpl == "PyQt6":
- if QVTKRWIBase == "QOpenGLWidget":
+if PyQtImpl == 'PyQt6':
+ if QVTKRWIBase == 'QOpenGLWidget':
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
- from PyQt6.QtWidgets import QWidget
- from PyQt6.QtWidgets import QSizePolicy
- from PyQt6.QtWidgets import QApplication
- from PyQt6.QtWidgets import QMainWindow
+ from PyQt6.QtCore import QEvent, QObject, QSize, Qt, QTimer
from PyQt6.QtGui import QCursor
- from PyQt6.QtCore import Qt
- from PyQt6.QtCore import QTimer
- from PyQt6.QtCore import QObject
- from PyQt6.QtCore import QSize
- from PyQt6.QtCore import QEvent
-elif PyQtImpl == "PySide6":
- if QVTKRWIBase == "QOpenGLWidget":
+ from PyQt6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QWidget
+elif PyQtImpl == 'PySide6':
+ if QVTKRWIBase == 'QOpenGLWidget':
from PySide6.QtOpenGLWidgets import QOpenGLWidget
- from PySide6.QtWidgets import QWidget
- from PySide6.QtWidgets import QSizePolicy
- from PySide6.QtWidgets import QApplication
- from PySide6.QtWidgets import QMainWindow
+ from PySide6.QtCore import QEvent, QObject, QSize, Qt, QTimer
from PySide6.QtGui import QCursor
- from PySide6.QtCore import Qt
- from PySide6.QtCore import QTimer
- from PySide6.QtCore import QObject
- from PySide6.QtCore import QSize
- from PySide6.QtCore import QEvent
-elif PyQtImpl == "PyQt5":
- if QVTKRWIBase == "QGLWidget":
+ from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QWidget
+elif PyQtImpl == 'PyQt5':
+ if QVTKRWIBase == 'QGLWidget':
from PyQt5.QtOpenGL import QGLWidget
- from PyQt5.QtWidgets import QWidget
- from PyQt5.QtWidgets import QSizePolicy
- from PyQt5.QtWidgets import QApplication
- from PyQt5.QtWidgets import QMainWindow
+ from PyQt5.QtCore import QEvent, QObject, QSize, Qt, QTimer
from PyQt5.QtGui import QCursor
- from PyQt5.QtCore import Qt
- from PyQt5.QtCore import QTimer
- from PyQt5.QtCore import QObject
- from PyQt5.QtCore import QSize
- from PyQt5.QtCore import QEvent
-elif PyQtImpl == "PySide2":
- if QVTKRWIBase == "QGLWidget":
+ from PyQt5.QtWidgets import QApplication, QMainWindow, QSizePolicy, QWidget
+elif PyQtImpl == 'PySide2':
+ if QVTKRWIBase == 'QGLWidget':
from PySide2.QtOpenGL import QGLWidget
- from PySide2.QtWidgets import QWidget
- from PySide2.QtWidgets import QSizePolicy
- from PySide2.QtWidgets import QApplication
- from PySide2.QtWidgets import QMainWindow
+ from PySide2.QtCore import QEvent, QObject, QSize, Qt, QTimer
from PySide2.QtGui import QCursor
- from PySide2.QtCore import Qt
- from PySide2.QtCore import QTimer
- from PySide2.QtCore import QObject
- from PySide2.QtCore import QSize
- from PySide2.QtCore import QEvent
-elif PyQtImpl == "PyQt4":
- if QVTKRWIBase == "QGLWidget":
+ from PySide2.QtWidgets import QApplication, QMainWindow, QSizePolicy, QWidget
+elif PyQtImpl == 'PyQt4':
+ if QVTKRWIBase == 'QGLWidget':
from PyQt4.QtOpenGL import QGLWidget
- from PyQt4.QtGui import QWidget
- from PyQt4.QtGui import QSizePolicy
- from PyQt4.QtGui import QApplication
- from PyQt4.QtGui import QMainWindow
- from PyQt4.QtCore import Qt
- from PyQt4.QtCore import QTimer
- from PyQt4.QtCore import QObject
- from PyQt4.QtCore import QSize
- from PyQt4.QtCore import QEvent
-elif PyQtImpl == "PySide":
- if QVTKRWIBase == "QGLWidget":
+ from PyQt4.QtCore import QEvent, QObject, QSize, Qt, QTimer
+ from PyQt4.QtGui import QApplication, QMainWindow, QSizePolicy, QWidget
+elif PyQtImpl == 'PySide':
+ if QVTKRWIBase == 'QGLWidget':
from PySide.QtOpenGL import QGLWidget
- from PySide.QtGui import QWidget
- from PySide.QtGui import QSizePolicy
- from PySide.QtGui import QApplication
- from PySide.QtGui import QMainWindow
- from PySide.QtCore import Qt
- from PySide.QtCore import QTimer
- from PySide.QtCore import QObject
- from PySide.QtCore import QSize
- from PySide.QtCore import QEvent
+ from PySide.QtCore import QEvent, QObject, QSize, Qt, QTimer
+ from PySide.QtGui import QApplication, QMainWindow, QSizePolicy, QWidget
else:
- raise ImportError("Unknown PyQt implementation " + repr(PyQtImpl))
+ raise ImportError('Unknown PyQt implementation ' + repr(PyQtImpl))
# Define types for base class, based on string
-if QVTKRWIBase == "QWidget":
+if QVTKRWIBase == 'QWidget':
QVTKRWIBaseClass = QWidget
-elif QVTKRWIBase == "QGLWidget":
+elif QVTKRWIBase == 'QGLWidget':
QVTKRWIBaseClass = QGLWidget
-elif QVTKRWIBase == "QOpenGLWidget":
+elif QVTKRWIBase == 'QOpenGLWidget':
QVTKRWIBaseClass = QOpenGLWidget
else:
- raise ImportError("Unknown base class for QVTKRenderWindowInteractor " + QVTKRWIBase)
+ raise ImportError('Unknown base class for QVTKRenderWindowInteractor ' + QVTKRWIBase)
if PyQtImpl == 'PyQt6':
CursorShape = Qt.CursorShape
@@ -215,8 +181,9 @@
SizePolicy = QSizePolicy.Policy
EventType = QEvent.Type
else:
- CursorShape = MouseButton = WindowType = WidgetAttribute = \
- KeyboardModifier = FocusPolicy = ConnectionType = Key = Qt
+ CursorShape = (
+ MouseButton
+ ) = WindowType = WidgetAttribute = KeyboardModifier = FocusPolicy = ConnectionType = Key = Qt
SizePolicy = QSizePolicy
EventType = QEvent
@@ -235,7 +202,7 @@ def _get_event_pos(ev):
class QVTKRenderWindowInteractor(QVTKRWIBaseClass):
- """ A QVTKRenderWindowInteractor for Python and Qt. Uses a
+ """A QVTKRenderWindowInteractor for Python and Qt. Uses a
vtkGenericRenderWindowInteractor to handle the interactions. Use
GetRenderWindow() to get the vtkRenderWindow. Create with the
keyword stereo=1 in order to generate a stereo-capable window.
@@ -304,17 +271,17 @@ class QVTKRenderWindowInteractor(QVTKRWIBaseClass):
# Map between VTK and Qt cursors.
_CURSOR_MAP = {
- 0: CursorShape.ArrowCursor, # VTK_CURSOR_DEFAULT
- 1: CursorShape.ArrowCursor, # VTK_CURSOR_ARROW
- 2: CursorShape.SizeBDiagCursor, # VTK_CURSOR_SIZENE
- 3: CursorShape.SizeFDiagCursor, # VTK_CURSOR_SIZENWSE
- 4: CursorShape.SizeBDiagCursor, # VTK_CURSOR_SIZESW
- 5: CursorShape.SizeFDiagCursor, # VTK_CURSOR_SIZESE
- 6: CursorShape.SizeVerCursor, # VTK_CURSOR_SIZENS
- 7: CursorShape.SizeHorCursor, # VTK_CURSOR_SIZEWE
- 8: CursorShape.SizeAllCursor, # VTK_CURSOR_SIZEALL
- 9: CursorShape.PointingHandCursor, # VTK_CURSOR_HAND
- 10: CursorShape.CrossCursor, # VTK_CURSOR_CROSSHAIR
+ 0: CursorShape.ArrowCursor, # VTK_CURSOR_DEFAULT
+ 1: CursorShape.ArrowCursor, # VTK_CURSOR_ARROW
+ 2: CursorShape.SizeBDiagCursor, # VTK_CURSOR_SIZENE
+ 3: CursorShape.SizeFDiagCursor, # VTK_CURSOR_SIZENWSE
+ 4: CursorShape.SizeBDiagCursor, # VTK_CURSOR_SIZESW
+ 5: CursorShape.SizeFDiagCursor, # VTK_CURSOR_SIZESE
+ 6: CursorShape.SizeVerCursor, # VTK_CURSOR_SIZENS
+ 7: CursorShape.SizeHorCursor, # VTK_CURSOR_SIZEWE
+ 8: CursorShape.SizeAllCursor, # VTK_CURSOR_SIZEALL
+ 9: CursorShape.PointingHandCursor, # VTK_CURSOR_HAND
+ 10: CursorShape.CrossCursor, # VTK_CURSOR_CROSSHAIR
}
def __init__(self, parent=None, **kw):
@@ -342,18 +309,18 @@ def __init__(self, parent=None, **kw):
rw = None
# create base qt-level widget
- if QVTKRWIBase == "QWidget":
- if "wflags" in kw:
+ if QVTKRWIBase == 'QWidget':
+ if 'wflags' in kw:
wflags = kw['wflags']
else:
wflags = Qt.WindowType.Widget
QWidget.__init__(self, parent, wflags | WindowType.MSWindowsOwnDC)
- elif QVTKRWIBase == "QGLWidget":
+ elif QVTKRWIBase == 'QGLWidget':
QGLWidget.__init__(self, parent)
- elif QVTKRWIBase == "QOpenGLWidget":
+ elif QVTKRWIBase == 'QOpenGLWidget':
QOpenGLWidget.__init__(self, parent)
- if rw: # user-supplied render window
+ if rw: # user-supplied render window
self._RenderWindow = rw
else:
self._RenderWindow = vtkRenderWindow()
@@ -362,30 +329,33 @@ def __init__(self, parent=None, **kw):
# Python2
if type(WId).__name__ == 'PyCObject':
- from ctypes import pythonapi, c_void_p, py_object
+ from ctypes import c_void_p, py_object, pythonapi
- pythonapi.PyCObject_AsVoidPtr.restype = c_void_p
+ pythonapi.PyCObject_AsVoidPtr.restype = c_void_p
pythonapi.PyCObject_AsVoidPtr.argtypes = [py_object]
WId = pythonapi.PyCObject_AsVoidPtr(WId)
# Python3
elif type(WId).__name__ == 'PyCapsule':
- from ctypes import pythonapi, c_void_p, py_object, c_char_p
+ from ctypes import c_char_p, c_void_p, py_object, pythonapi
pythonapi.PyCapsule_GetName.restype = c_char_p
pythonapi.PyCapsule_GetName.argtypes = [py_object]
name = pythonapi.PyCapsule_GetName(WId)
- pythonapi.PyCapsule_GetPointer.restype = c_void_p
- pythonapi.PyCapsule_GetPointer.argtypes = [py_object, c_char_p]
+ pythonapi.PyCapsule_GetPointer.restype = c_void_p
+ pythonapi.PyCapsule_GetPointer.argtypes = [
+ py_object,
+ c_char_p,
+ ]
WId = pythonapi.PyCapsule_GetPointer(WId, name)
self._RenderWindow.SetWindowInfo(str(int(WId)))
- if stereo: # stereo mode
+ if stereo: # stereo mode
self._RenderWindow.StereoCapableWindowOn()
self._RenderWindow.SetStereoTypeToCrystalEyes()
@@ -398,7 +368,7 @@ def __init__(self, parent=None, **kw):
# do all the necessary qt setup
self.setAttribute(WidgetAttribute.WA_OpaquePaintEvent)
self.setAttribute(WidgetAttribute.WA_PaintOnScreen)
- self.setMouseTracking(True) # get all mouse events
+ self.setMouseTracking(True) # get all mouse events
self.setFocusPolicy(FocusPolicy.WheelFocus)
self.setSizePolicy(QSizePolicy(SizePolicy.Expanding, SizePolicy.Expanding))
@@ -407,8 +377,7 @@ def __init__(self, parent=None, **kw):
self._Iren.AddObserver('CreateTimerEvent', self.CreateTimer)
self._Iren.AddObserver('DestroyTimerEvent', self.DestroyTimer)
- self._Iren.GetRenderWindow().AddObserver('CursorChangedEvent',
- self.CursorChangedEvent)
+ self._Iren.GetRenderWindow().AddObserver('CursorChangedEvent', self.CursorChangedEvent)
# If we've a parent, it does not close the child when closed.
# Connect the parent's destroyed signal to this widget's close
@@ -423,13 +392,12 @@ def __getattr__(self, attr):
elif hasattr(self._Iren, attr):
return getattr(self._Iren, attr)
else:
- raise AttributeError(self.__class__.__name__ +
- " has no attribute named " + attr)
+ raise AttributeError(self.__class__.__name__ + ' has no attribute named ' + attr)
def Finalize(self):
- '''
+ """
Call internal cleanup method on VTK objects
- '''
+ """
self._RenderWindow.Finalize()
def CreateTimer(self, obj, evt):
@@ -473,16 +441,16 @@ def paintEvent(self, ev):
def resizeEvent(self, ev):
scale = self._getPixelRatio()
- w = int(round(scale*self.width()))
- h = int(round(scale*self.height()))
- self._RenderWindow.SetDPI(int(round(72*scale)))
+ w = int(round(scale * self.width()))
+ h = int(round(scale * self.height()))
+ self._RenderWindow.SetDPI(int(round(72 * scale)))
vtkRenderWindow.SetSize(self._RenderWindow, w, h)
self._Iren.SetSize(w, h)
self._Iren.ConfigureEvent()
self.update()
def _GetKeyCharAndKeySym(self, ev):
- """ Convert a Qt key into a char and a vtk keysym.
+ """Convert a Qt key into a char and a vtk keysym.
This is essentially copied from the c++ implementation in
GUISupport/Qt/QVTKInteractorAdapter.cxx.
@@ -502,9 +470,9 @@ def _GetKeyCharAndKeySym(self, ev):
except KeyError:
keySym = None
- # use "None" as a fallback
+ # use 'None' as a fallback
if keySym is None:
- keySym = "None"
+ keySym = 'None'
return keyChar, keySym
@@ -526,7 +494,7 @@ def _GetCtrlShift(self, ev):
@staticmethod
def _getPixelRatio():
- if PyQtImpl in ["PyQt5", "PySide2", "PySide6"]:
+ if PyQtImpl in ['PyQt5', 'PySide2', 'PySide6']:
# Source: https://stackoverflow.com/a/40053864/3388962
pos = QCursor.pos()
for screen in QApplication.screens():
@@ -538,25 +506,28 @@ def _getPixelRatio():
else:
# Qt4 seems not to provide any cross-platform means to get the
# pixel ratio.
- return 1.
+ return 1.0
- def _setEventInformation(self, x, y, ctrl, shift,
- key, repeat=0, keysum=None):
+ def _setEventInformation(self, x, y, ctrl, shift, key, repeat=0, keysum=None):
scale = self._getPixelRatio()
- self._Iren.SetEventInformation(int(round(x*scale)),
- int(round((self.height()-y-1)*scale)),
- ctrl, shift, key, repeat, keysum)
+ self._Iren.SetEventInformation(
+ int(round(x * scale)),
+ int(round((self.height() - y - 1) * scale)),
+ ctrl,
+ shift,
+ key,
+ repeat,
+ keysum,
+ )
def enterEvent(self, ev):
ctrl, shift = self._GetCtrlShift(ev)
- self._setEventInformation(self.__saveX, self.__saveY,
- ctrl, shift, chr(0), 0, None)
+ self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None)
self._Iren.EnterEvent()
def leaveEvent(self, ev):
ctrl, shift = self._GetCtrlShift(ev)
- self._setEventInformation(self.__saveX, self.__saveY,
- ctrl, shift, chr(0), 0, None)
+ self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None)
self._Iren.LeaveEvent()
def mousePressEvent(self, ev):
@@ -565,8 +536,7 @@ def mousePressEvent(self, ev):
repeat = 0
if ev.type() == EventType.MouseButtonDblClick:
repeat = 1
- self._setEventInformation(pos_x, pos_y,
- ctrl, shift, chr(0), repeat, None)
+ self._setEventInformation(pos_x, pos_y, ctrl, shift, chr(0), repeat, None)
self._ActiveButton = ev.button()
@@ -580,8 +550,7 @@ def mousePressEvent(self, ev):
def mouseReleaseEvent(self, ev):
pos_x, pos_y = _get_event_pos(ev)
ctrl, shift = self._GetCtrlShift(ev)
- self._setEventInformation(pos_x, pos_y,
- ctrl, shift, chr(0), 0, None)
+ self._setEventInformation(pos_x, pos_y, ctrl, shift, chr(0), 0, None)
if self._ActiveButton == MouseButton.LeftButton:
self._Iren.LeftButtonReleaseEvent()
@@ -598,23 +567,20 @@ def mouseMoveEvent(self, ev):
self.__saveY = pos_y
ctrl, shift = self._GetCtrlShift(ev)
- self._setEventInformation(pos_x, pos_y,
- ctrl, shift, chr(0), 0, None)
+ self._setEventInformation(pos_x, pos_y, ctrl, shift, chr(0), 0, None)
self._Iren.MouseMoveEvent()
def keyPressEvent(self, ev):
key, keySym = self._GetKeyCharAndKeySym(ev)
ctrl, shift = self._GetCtrlShift(ev)
- self._setEventInformation(self.__saveX, self.__saveY,
- ctrl, shift, key, 0, keySym)
+ self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, key, 0, keySym)
self._Iren.KeyPressEvent()
self._Iren.CharEvent()
def keyReleaseEvent(self, ev):
key, keySym = self._GetKeyCharAndKeySym(ev)
ctrl, shift = self._GetCtrlShift(ev)
- self._setEventInformation(self.__saveX, self.__saveY,
- ctrl, shift, key, 0, keySym)
+ self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, key, 0, keySym)
self._Iren.KeyReleaseEvent()
def wheelEvent(self, ev):
@@ -640,16 +606,17 @@ def Render(self):
def QVTKRenderWidgetConeExample(block=False):
"""A simple example that uses the QVTKRenderWindowInteractor class."""
- from vtkmodules.vtkFiltersSources import vtkConeSource
- from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper, vtkRenderer
+ import vtkmodules.vtkInteractionStyle
+
# load implementations for rendering and interaction factory classes
import vtkmodules.vtkRenderingOpenGL2
- import vtkmodules.vtkInteractionStyle
+ from vtkmodules.vtkFiltersSources import vtkConeSource
+ from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper, vtkRenderer
# every QT app needs an app
app = QApplication.instance()
if not app: # pragma: no cover
- app = QApplication(["PyVista"])
+ app = QApplication(['PyVista'])
window = QMainWindow()
@@ -657,7 +624,7 @@ def QVTKRenderWidgetConeExample(block=False):
widget = QVTKRenderWindowInteractor(window)
window.setCentralWidget(widget)
# if you don't want the 'q' key to exit comment this.
- widget.AddObserver("ExitEvent", lambda o, e, a=app: a.quit())
+ widget.AddObserver('ExitEvent', lambda o, e, a=app: a.quit())
ren = vtkRenderer()
widget.GetRenderWindow().AddRenderer(ren)
@@ -691,26 +658,135 @@ def QVTKRenderWidgetConeExample(block=False):
_keysyms_for_ascii = (
- None, None, None, None, None, None, None, None,
- None, "Tab", None, None, None, None, None, None,
- None, None, None, None, None, None, None, None,
- None, None, None, None, None, None, None, None,
- "space", "exclam", "quotedbl", "numbersign",
- "dollar", "percent", "ampersand", "quoteright",
- "parenleft", "parenright", "asterisk", "plus",
- "comma", "minus", "period", "slash",
- "0", "1", "2", "3", "4", "5", "6", "7",
- "8", "9", "colon", "semicolon", "less", "equal", "greater", "question",
- "at", "A", "B", "C", "D", "E", "F", "G",
- "H", "I", "J", "K", "L", "M", "N", "O",
- "P", "Q", "R", "S", "T", "U", "V", "W",
- "X", "Y", "Z", "bracketleft",
- "backslash", "bracketright", "asciicircum", "underscore",
- "quoteleft", "a", "b", "c", "d", "e", "f", "g",
- "h", "i", "j", "k", "l", "m", "n", "o",
- "p", "q", "r", "s", "t", "u", "v", "w",
- "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "Delete",
- )
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ 'Tab',
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ 'space',
+ 'exclam',
+ 'quotedbl',
+ 'numbersign',
+ 'dollar',
+ 'percent',
+ 'ampersand',
+ 'quoteright',
+ 'parenleft',
+ 'parenright',
+ 'asterisk',
+ 'plus',
+ 'comma',
+ 'minus',
+ 'period',
+ 'slash',
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'at',
+ 'A',
+ 'B',
+ 'C',
+ 'D',
+ 'E',
+ 'F',
+ 'G',
+ 'H',
+ 'I',
+ 'J',
+ 'K',
+ 'L',
+ 'M',
+ 'N',
+ 'O',
+ 'P',
+ 'Q',
+ 'R',
+ 'S',
+ 'T',
+ 'U',
+ 'V',
+ 'W',
+ 'X',
+ 'Y',
+ 'Z',
+ 'bracketleft',
+ 'backslash',
+ 'bracketright',
+ 'asciicircum',
+ 'underscore',
+ 'quoteleft',
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i',
+ 'j',
+ 'k',
+ 'l',
+ 'm',
+ 'n',
+ 'o',
+ 'p',
+ 'q',
+ 'r',
+ 's',
+ 't',
+ 'u',
+ 'v',
+ 'w',
+ 'x',
+ 'y',
+ 'z',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'asciitilde',
+ 'Delete',
+)
_keysyms = {
Key.Key_Backspace: 'BackSpace',
@@ -805,9 +881,9 @@ def QVTKRenderWidgetConeExample(block=False):
Key.Key_F24: 'F24',
Key.Key_NumLock: 'Num_Lock',
Key.Key_ScrollLock: 'Scroll_Lock',
- }
+}
-if __name__ == "__main__":
+if __name__ == '__main__':
print(PyQtImpl)
QVTKRenderWidgetConeExample()
diff --git a/pyvistaqt/utils.py b/pyvistaqt/utils.py
index 497b3227..56d1d3ff 100644
--- a/pyvistaqt/utils.py
+++ b/pyvistaqt/utils.py
@@ -10,8 +10,7 @@ def _check_type(var: Any, var_name: str, var_types: List[Type[Any]]) -> None:
types = tuple(var_types)
if not isinstance(var, types):
raise TypeError(
- f"Expected type for ``{var_name}`` is {str(types)}"
- f" but {type(var)} was given."
+ f'Expected type for ``{var_name}`` is {str(types)}' f' but {type(var)} was given.'
)
@@ -37,7 +36,7 @@ def _setup_ipython(ipython: Any = None) -> Any:
from IPython import get_ipython
ipython = get_ipython()
- ipython.run_line_magic("gui", "qt")
+ ipython.run_line_magic('gui', 'qt')
# pylint: disable=redefined-outer-name
# pylint: disable=import-outside-toplevel
@@ -47,12 +46,14 @@ def _setup_ipython(ipython: Any = None) -> Any:
return ipython
-def _setup_application(app: Optional[QApplication] = None) -> QApplication:
+def _setup_application(
+ app: Optional[QApplication] = None,
+) -> QApplication:
# run within python
if app is None:
app = QApplication.instance()
if not app: # pragma: no cover
- app = QApplication(["PyVista"])
+ app = QApplication(['PyVista'])
return app
diff --git a/requirements_mypy.txt b/requirements_mypy.txt
new file mode 100644
index 00000000..e0fef0cd
--- /dev/null
+++ b/requirements_mypy.txt
@@ -0,0 +1,5 @@
+pytest-mypy-plugins
+IceSpringPySideStubs-PySide6
+PyQt5-stubs
+PySide2-stubs
+types-setuptools
\ No newline at end of file
diff --git a/requirements_test.txt b/requirements_test.txt
index a61fb0a8..d9e25cc7 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -16,3 +16,9 @@ pytest-qt==4.1.0
pyvista==0.34.1
QtPy==2.1.0
scooby==0.5.12
+flake8-black==0.3.2
+flake8-isort==4.1.1
+flake8-quotes==3.3.1
+check-jsonschema==0.16.2
+pre-commit==2.19.0
+pre-commit-hooks==4.3.0
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 00000000..203a3492
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,39 @@
+[metadata]
+name = pyvistaqt
+version = attr: pyvistaqt._version.__version__
+description = pyvista qt plotter
+long_description = file: README.rst, LICENSE
+author = PyVista Developers
+author_email = info@pyvista.org
+license = MIT
+classifiers =
+ Development Status :: 4 - Beta,
+ Intended Audience :: Science/Research,
+ Topic :: Scientific/Engineering :: Information Analysis,
+ License :: OSI Approved :: MIT License,
+ Operating System :: Microsoft :: Windows,
+ Operating System :: POSIX,
+ Operating System :: MacOS,
+ Programming Language :: Python :: 3.7,
+ Programming Language :: Python :: 3.8,
+ Programming Language :: Python :: 3.9
+url = https://github.com/pyvista/pyvistaqt
+keywords =
+ vtk
+ numpy
+ plotting
+ mesh
+ qt
+
+[options]
+zip_safe = False
+include_package_data = True
+packages = find:
+python_requires = >=3.7
+install_requires =
+ pyvista>="0.32.0"
+ QtPy>="1.9.0"
+
+[options.package_data]
+pyvistaqt =
+ data/*.png
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 23a0a23d..28478b53 100644
--- a/setup.py
+++ b/setup.py
@@ -1,52 +1,6 @@
-"""
-Installation file for python pyvistaqt module
-"""
-import os
-from io import open as io_open
+"""Installation file for python pyvistaqt module."""
+from __future__ import annotations
from setuptools import setup
-package_name = 'pyvistaqt'
-
-__version__ = None
-filepath = os.path.dirname(__file__)
-version_file = os.path.join(filepath, package_name, '_version.py')
-with io_open(version_file, mode='r') as fd:
- exec(fd.read())
-
-readme_file = os.path.join(filepath, 'README.rst')
-
-setup(
- name=package_name,
- packages=[package_name, package_name],
- version=__version__,
- description='pyvista qt plotter',
- long_description=io_open(readme_file, encoding="utf-8").read(),
- author='PyVista Developers',
- author_email='info@pyvista.org',
- license='MIT',
- classifiers=[
- 'Development Status :: 4 - Beta',
- 'Intended Audience :: Science/Research',
- 'Topic :: Scientific/Engineering :: Information Analysis',
- 'License :: OSI Approved :: MIT License',
- 'Operating System :: Microsoft :: Windows',
- 'Operating System :: POSIX',
- 'Operating System :: MacOS',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
- ],
-
- url='https://github.com/pyvista/pyvistaqt',
- keywords='vtk numpy plotting mesh qt',
- python_requires='>=3.6.*',
- install_requires=[
- 'pyvista>=0.32.0',
- 'QtPy>=1.9.0',
- ],
- package_data={'pyvistaqt': [
- os.path.join('data', '*.png'),
- ]}
-
-)
+setup()
diff --git a/tests/conftest.py b/tests/conftest.py
index 56dae794..718f8741 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,42 +1,12 @@
-import importlib
-import sys
-
import pytest
from pyvista.plotting import system_supports_plotting
-import pyvistaqt
NO_PLOTTING = not system_supports_plotting()
-def _check_qt_installed():
- try:
- from qtpy import QtCore # noqa
- except Exception:
- return False
- else:
- return True
-
-
@pytest.fixture()
-def plotting():
+def plotting() -> None:
"""Require plotting."""
if NO_PLOTTING:
- pytest.skip(NO_PLOTTING, reason="Requires system to support plotting")
- yield
-
-
-@pytest.fixture()
-def no_qt(monkeypatch):
- """Require plotting."""
- need_reload = False
- if _check_qt_installed():
- need_reload = True
- monkeypatch.setenv('QT_API', 'bad_name')
- sys.modules.pop('qtpy')
- importlib.reload(pyvistaqt)
- assert 'qtpy' not in sys.modules
+ pytest.skip(NO_PLOTTING, reason='Requires system to support plotting')
yield
- monkeypatch.undo()
- if need_reload:
- importlib.reload(pyvistaqt)
- assert 'qtpy' in sys.modules
diff --git a/tests/test_imports.py b/tests/test_imports.py
new file mode 100644
index 00000000..e82a674e
--- /dev/null
+++ b/tests/test_imports.py
@@ -0,0 +1,37 @@
+"""Tests for typehinting."""
+from __future__ import annotations
+
+import typing
+
+from mypy import api
+
+if typing.TYPE_CHECKING: # pragma: no cover
+ from qtpy import QtWidgets
+
+
+def test_import(
+ qapp: QtWidgets.QApplication, # pylint: disable=unused-argument
+) -> None:
+ """Regression test for `Issue #163`_.
+
+ Args:
+ qapp (QtWidgets.QApplication): ``pytest-qt`` fixture for holding
+ the ``QApplication`` instance.
+
+ A ``QApplication`` must exist before a ``QWidget`` can be
+ created. This fixture is used implicitly in this test.
+
+ .. _`Issue #163`:
+ https://github.com/pyvista/pyvistaqt/issues/163
+ """
+
+ src = """
+from pyvistaqt import MainWindow
+
+window = MainWindow()
+window.setWindowTitle('window')
+"""
+
+ stdout, _, _ = api.run(['-c', src])
+
+ assert '"MainWindow" has no attribute "setWindowTitle"' not in stdout
diff --git a/tests/test_plotting.py b/tests/test_plotting.py
index d099b8a2..9fcbfc9f 100644
--- a/tests/test_plotting.py
+++ b/tests/test_plotting.py
@@ -1,26 +1,41 @@
import os
-from packaging.version import Version
import platform
import numpy as np
import pytest
import pyvista
import vtk
-from qtpy.QtWidgets import QAction, QFrame, QMenuBar, QToolBar, QVBoxLayout
+from packaging.version import Version
+from pyvista import BasePlotter
+from pyvista.plotting import Renderer
from qtpy import QtCore
-from qtpy.QtCore import Qt, QPoint, QPointF, QMimeData, QUrl
+from qtpy.QtCore import QMimeData, QPoint, QPointF, Qt, QUrl
from qtpy.QtGui import QDragEnterEvent, QDropEvent
-from qtpy.QtWidgets import (QTreeWidget, QStackedWidget, QCheckBox,
- QGestureEvent, QPinchGesture)
-from pyvistaqt.plotting import global_theme
-from pyvista.plotting import Renderer
+from qtpy.QtWidgets import (
+ QAction,
+ QCheckBox,
+ QFrame,
+ QGestureEvent,
+ QMenuBar,
+ QPinchGesture,
+ QStackedWidget,
+ QToolBar,
+ QTreeWidget,
+ QVBoxLayout,
+)
import pyvistaqt
-from pyvistaqt import MultiPlotter, BackgroundPlotter, MainWindow, QtInteractor
-from pyvistaqt.plotting import Counter, QTimer, QVTKRenderWindowInteractor
-from pyvistaqt.editor import Editor
+from pyvistaqt import BackgroundPlotter, MainWindow, MultiPlotter, QtInteractor
from pyvistaqt.dialog import FileDialog
-from pyvistaqt.utils import _setup_application, _create_menu_bar, _check_type
+from pyvistaqt.editor import Editor
+from pyvistaqt.plotting import (
+ Counter,
+ QTimer,
+ QVTKRenderWindowInteractor,
+ _no_base_plotter_init,
+ global_theme,
+)
+from pyvistaqt.utils import _check_type, _create_menu_bar, _setup_application
class TstWindow(MainWindow):
@@ -59,14 +74,18 @@ def __init__(self, parent=None, show=True, off_screen=True):
self.show()
def add_sphere(self):
- sphere = pyvista.Sphere(
- phi_resolution=6,
- theta_resolution=6
- )
+ sphere = pyvista.Sphere(phi_resolution=6, theta_resolution=6)
self.vtk_widget.add_mesh(sphere)
self.vtk_widget.reset_camera()
+def test_base_plotter_noop(qtbot):
+ with _no_base_plotter_init():
+ kwargs = {}
+ assert BasePlotter.__init__(kwargs) is None
+ assert BasePlotter.__init__ is not None
+
+
def test_create_menu_bar(qtbot):
menu_bar = _create_menu_bar(parent=None)
qtbot.addWidget(menu_bar)
@@ -87,7 +106,7 @@ def test_file_dialog(tmpdir, qtbot):
dialog.emit_accepted() # test no result
- p = tmpdir.mkdir("tmp").join("foo.png")
+ p = tmpdir.mkdir('tmp').join('foo.png')
p.write('foo')
assert os.path.isfile(p)
@@ -107,10 +126,10 @@ def test_file_dialog(tmpdir, qtbot):
def test_check_type():
- with pytest.raises(TypeError, match="Expected type"):
- _check_type(0, "foo", [str])
- _check_type(0, "foo", [int, float])
- _check_type("foo", "foo", [str])
+ with pytest.raises(TypeError, match='Expected type'):
+ _check_type(0, 'foo', [str])
+ _check_type(0, 'foo', [int, float])
+ _check_type('foo', 'foo', [str])
def test_mouse_interactions(qtbot):
@@ -124,12 +143,18 @@ def test_mouse_interactions(qtbot):
plotter.close()
-@pytest.mark.skipif(platform.system()=="Windows" and platform.python_version()[:-1]=="3.8.", reason="#51")
+@pytest.mark.skipif(
+ platform.system() == 'Windows' and platform.python_version()[:-1] == '3.8.',
+ reason='#51',
+)
def test_ipython(qapp):
import IPython
- cmd = "from pyvistaqt import BackgroundPlotter as Plotter;" \
- "p = Plotter(show=False, off_screen=False); p.close(); exit()"
- IPython.start_ipython(argv=["-c", cmd])
+
+ cmd = (
+ 'from pyvistaqt import BackgroundPlotter as Plotter;'
+ 'p = Plotter(show=False, off_screen=False); p.close(); exit()'
+ )
+ IPython.start_ipython(argv=['-c', cmd])
class SuperWindow(MainWindow):
@@ -141,14 +166,14 @@ def test_depth_peeling(qtbot):
qtbot.addWidget(plotter.app_window)
assert not plotter.renderer.GetUseDepthPeeling()
plotter.close()
- global_theme.depth_peeling["enabled"] = True
+ global_theme.depth_peeling['enabled'] = True
plotter = BackgroundPlotter(app_window_class=SuperWindow)
assert isinstance(plotter.app_window, SuperWindow)
assert isinstance(plotter.app_window, MainWindow)
qtbot.addWidget(plotter.app_window)
assert plotter.renderer.GetUseDepthPeeling()
plotter.close()
- global_theme.depth_peeling["enabled"] = False
+ global_theme.depth_peeling['enabled'] = False
def test_off_screen(qtbot):
@@ -204,7 +229,7 @@ def test_editor(qtbot, plotting):
# test editor closing
plotter = BackgroundPlotter(editor=True, off_screen=False)
qtbot.addWidget(plotter.app_window)
- assert_hasattr(plotter, "editor", Editor)
+ assert_hasattr(plotter, 'editor', Editor)
editor = plotter.editor
assert not editor.isVisible()
with qtbot.wait_exposed(editor):
@@ -227,7 +252,7 @@ def test_editor(qtbot, plotting):
plotter.subplot(1, 0)
plotter.show_axes()
- assert_hasattr(editor, "tree_widget", QTreeWidget)
+ assert_hasattr(editor, 'tree_widget', QTreeWidget)
tree_widget = editor.tree_widget
top_item = tree_widget.topLevelItem(0) # any renderer will do
assert top_item is not None
@@ -238,7 +263,7 @@ def test_editor(qtbot, plotting):
# toggle all the renderer-associated checkboxes twice
# to ensure that slots are called for True and False
- assert_hasattr(editor, "stacked_widget", QStackedWidget)
+ assert_hasattr(editor, 'stacked_widget', QStackedWidget)
stacked_widget = editor.stacked_widget
page_idx = top_item.data(0, Qt.ItemDataRole.UserRole)
page_widget = stacked_widget.widget(page_idx)
@@ -260,6 +285,7 @@ def test_editor(qtbot, plotting):
def test_qt_interactor(qtbot, plotting):
from pyvista.plotting.plotting import _ALL_PLOTTERS, close_all
+
close_all() # this is necessary to test _ALL_PLOTTERS
assert len(_ALL_PLOTTERS) == 0
@@ -267,18 +293,18 @@ def test_qt_interactor(qtbot, plotting):
qtbot.addWidget(window) # register the main widget
# check that TstWindow.__init__() is called
- assert_hasattr(window, "vtk_widget", QtInteractor)
+ assert_hasattr(window, 'vtk_widget', QtInteractor)
vtk_widget = window.vtk_widget # QtInteractor
# check that QtInteractor.__init__() is called
- assert hasattr(vtk_widget, "iren")
- assert_hasattr(vtk_widget, "render_timer", QTimer)
+ assert hasattr(vtk_widget, 'iren')
+ assert_hasattr(vtk_widget, 'render_timer', QTimer)
# check that BasePlotter.__init__() is called
- assert_hasattr(vtk_widget, "_closed", bool)
- assert_hasattr(vtk_widget, "renderer", vtk.vtkRenderer)
+ assert_hasattr(vtk_widget, '_closed', bool)
+ assert_hasattr(vtk_widget, 'renderer', vtk.vtkRenderer)
# check that QVTKRenderWindowInteractorAdapter.__init__() is called
- assert_hasattr(vtk_widget, "interactor", QVTKRenderWindowInteractor)
+ assert_hasattr(vtk_widget, 'interactor', QVTKRenderWindowInteractor)
interactor = vtk_widget.interactor # QVTKRenderWindowInteractor
render_timer = vtk_widget.render_timer # QTimer
@@ -315,24 +341,23 @@ def test_qt_interactor(qtbot, plotting):
# check that BasePlotter.close() is called
if Version(pyvista.__version__) < Version('0.27.0'):
- assert not hasattr(vtk_widget, "iren")
+ assert not hasattr(vtk_widget, 'iren')
assert vtk_widget._closed
# check that BasePlotter.__init__() is called only once
assert len(_ALL_PLOTTERS) == 1
-@pytest.mark.parametrize('show_plotter', [
- True,
- False,
- ])
+@pytest.mark.parametrize(
+ 'show_plotter',
+ [
+ True,
+ False,
+ ],
+)
def test_background_plotting_axes_scale(qtbot, show_plotter, plotting):
- plotter = BackgroundPlotter(
- show=show_plotter,
- off_screen=False,
- title='Testing Window'
- )
- assert_hasattr(plotter, "app_window", MainWindow)
+ plotter = BackgroundPlotter(show=show_plotter, off_screen=False, title='Testing Window')
+ assert_hasattr(plotter, 'app_window', MainWindow)
window = plotter.app_window # MainWindow
qtbot.addWidget(window) # register the window
@@ -344,7 +369,7 @@ def test_background_plotting_axes_scale(qtbot, show_plotter, plotting):
assert window.isVisible()
plotter.add_mesh(pyvista.Sphere())
- assert_hasattr(plotter, "renderer", Renderer)
+ assert_hasattr(plotter, 'renderer', Renderer)
renderer = plotter.renderer
assert len(renderer._actors) == 1
assert np.any(plotter.mesh.points)
@@ -381,7 +406,11 @@ def test_background_plotting_camera(qtbot, plotting):
cpos = [(0.0, 0.0, 1.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
plotter.camera_position = cpos
plotter.save_camera_position()
- plotter.camera_position = [(0.0, 0.0, 3.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
+ plotter.camera_position = [
+ (0.0, 0.0, 3.0),
+ (0.0, 0.0, 0.0),
+ (0.0, 1.0, 0.0),
+ ]
# load existing position
# NOTE: 2 because first two (0 and 1) buttons save and clear positions
@@ -396,7 +425,6 @@ def test_background_plotting_camera(qtbot, plotting):
@pytest.mark.parametrize('other_views', [None, 0, [0]])
def test_link_views_across_plotters(other_views):
-
def _to_array(camera_position):
return np.asarray([list(row) for row in camera_position])
@@ -408,20 +436,32 @@ def _to_array(camera_position):
plotter_one.link_views_across_plotters(plotter_two, other_views=other_views)
- plotter_one.camera_position = [(0.0, 0.0, 1.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
+ plotter_one.camera_position = [
+ (0.0, 0.0, 1.0),
+ (0.0, 0.0, 0.0),
+ (0.0, 1.0, 0.0),
+ ]
np.testing.assert_allclose(
_to_array(plotter_one.camera_position),
_to_array(plotter_two.camera_position),
)
- plotter_two.camera_position = [(0.0, 0.0, 3.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
+ plotter_two.camera_position = [
+ (0.0, 0.0, 3.0),
+ (0.0, 0.0, 0.0),
+ (0.0, 1.0, 0.0),
+ ]
np.testing.assert_allclose(
_to_array(plotter_one.camera_position),
_to_array(plotter_two.camera_position),
)
plotter_one.unlink_views()
- plotter_one.camera_position = [(0.0, 0.0, 1.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
+ plotter_one.camera_position = [
+ (0.0, 0.0, 1.0),
+ (0.0, 0.0, 0.0),
+ (0.0, 1.0, 0.0),
+ ]
with pytest.raises(AssertionError):
np.testing.assert_allclose(
@@ -433,21 +473,21 @@ def _to_array(camera_position):
with pytest.raises(TypeError, match=match):
plotter_one.link_views_across_plotters(plotter_two, other_views=[0.0])
-@pytest.mark.parametrize('show_plotter', [
- True,
- False,
- ])
+
+@pytest.mark.parametrize(
+ 'show_plotter',
+ [
+ True,
+ False,
+ ],
+)
def test_background_plotter_export_files(qtbot, tmpdir, show_plotter, plotting):
# setup filesystem
- output_dir = str(tmpdir.mkdir("tmpdir"))
+ output_dir = str(tmpdir.mkdir('tmpdir'))
assert os.path.isdir(output_dir)
- plotter = BackgroundPlotter(
- show=show_plotter,
- off_screen=False,
- title='Testing Window'
- )
- assert_hasattr(plotter, "app_window", MainWindow)
+ plotter = BackgroundPlotter(show=show_plotter, off_screen=False, title='Testing Window')
+ assert_hasattr(plotter, 'app_window', MainWindow)
window = plotter.app_window # MainWindow
qtbot.addWidget(window) # register the window
@@ -459,7 +499,7 @@ def test_background_plotter_export_files(qtbot, tmpdir, show_plotter, plotting):
assert window.isVisible()
plotter.add_mesh(pyvista.Sphere())
- assert_hasattr(plotter, "renderer", Renderer)
+ assert_hasattr(plotter, 'renderer', Renderer)
renderer = plotter.renderer
assert len(renderer._actors) == 1
assert np.any(plotter.mesh.points)
@@ -467,7 +507,7 @@ def test_background_plotter_export_files(qtbot, tmpdir, show_plotter, plotting):
dlg = plotter._qt_screenshot(show=False) # FileDialog
qtbot.addWidget(dlg) # register the dialog
- filename = str(os.path.join(output_dir, "tmp.png"))
+ filename = str(os.path.join(output_dir, 'tmp.png'))
dlg.selectFile(filename)
# show the dialog
@@ -486,21 +526,20 @@ def test_background_plotter_export_files(qtbot, tmpdir, show_plotter, plotting):
assert os.path.isfile(filename)
-@pytest.mark.parametrize('show_plotter', [
- True,
- False,
- ])
+@pytest.mark.parametrize(
+ 'show_plotter',
+ [
+ True,
+ False,
+ ],
+)
def test_background_plotter_export_vtkjs(qtbot, tmpdir, show_plotter, plotting):
# setup filesystem
- output_dir = str(tmpdir.mkdir("tmpdir"))
+ output_dir = str(tmpdir.mkdir('tmpdir'))
assert os.path.isdir(output_dir)
- plotter = BackgroundPlotter(
- show=show_plotter,
- off_screen=False,
- title='Testing Window'
- )
- assert_hasattr(plotter, "app_window", MainWindow)
+ plotter = BackgroundPlotter(show=show_plotter, off_screen=False, title='Testing Window')
+ assert_hasattr(plotter, 'app_window', MainWindow)
window = plotter.app_window # MainWindow
qtbot.addWidget(window) # register the window
@@ -512,7 +551,7 @@ def test_background_plotter_export_vtkjs(qtbot, tmpdir, show_plotter, plotting):
assert window.isVisible()
plotter.add_mesh(pyvista.Sphere())
- assert_hasattr(plotter, "renderer", Renderer)
+ assert_hasattr(plotter, 'renderer', Renderer)
renderer = plotter.renderer
assert len(renderer._actors) == 1
assert np.any(plotter.mesh.points)
@@ -520,7 +559,7 @@ def test_background_plotter_export_vtkjs(qtbot, tmpdir, show_plotter, plotting):
dlg = plotter._qt_export_vtkjs(show=False) # FileDialog
qtbot.addWidget(dlg) # register the dialog
- filename = str(os.path.join(output_dir, "tmp"))
+ filename = str(os.path.join(output_dir, 'tmp'))
dlg.selectFile(filename)
# show the dialog
@@ -549,7 +588,7 @@ def test_background_plotting_orbit(qtbot, plotting):
def test_background_plotting_toolbar(qtbot, plotting):
with pytest.raises(TypeError, match='toolbar'):
- p = BackgroundPlotter(off_screen=False, toolbar="foo")
+ p = BackgroundPlotter(off_screen=False, toolbar='foo')
p.close()
plotter = BackgroundPlotter(off_screen=False, toolbar=False)
@@ -560,10 +599,10 @@ def test_background_plotting_toolbar(qtbot, plotting):
plotter = BackgroundPlotter(off_screen=False)
- assert_hasattr(plotter, "app_window", MainWindow)
- assert_hasattr(plotter, "default_camera_tool_bar", QToolBar)
- assert_hasattr(plotter, "saved_camera_positions", list)
- assert_hasattr(plotter, "saved_cameras_tool_bar", QToolBar)
+ assert_hasattr(plotter, 'app_window', MainWindow)
+ assert_hasattr(plotter, 'default_camera_tool_bar', QToolBar)
+ assert_hasattr(plotter, 'saved_camera_positions', list)
+ assert_hasattr(plotter, 'saved_cameras_tool_bar', QToolBar)
window = plotter.app_window
default_camera_tool_bar = plotter.default_camera_tool_bar
@@ -583,7 +622,7 @@ def test_background_plotting_toolbar(qtbot, plotting):
def test_background_plotting_menu_bar(qtbot, plotting):
with pytest.raises(TypeError, match='menu_bar'):
- p = BackgroundPlotter(off_screen=False, menu_bar="foo")
+ p = BackgroundPlotter(off_screen=False, menu_bar='foo')
p.close()
plotter = BackgroundPlotter(off_screen=False, menu_bar=False)
@@ -593,11 +632,11 @@ def test_background_plotting_menu_bar(qtbot, plotting):
plotter = BackgroundPlotter(off_screen=False) # menu_bar=True
- assert_hasattr(plotter, "app_window", MainWindow)
- assert_hasattr(plotter, "main_menu", QMenuBar)
- assert_hasattr(plotter, "_menu_close_action", QAction)
- assert_hasattr(plotter, "_edl_action", QAction)
- assert_hasattr(plotter, "_parallel_projection_action", QAction)
+ assert_hasattr(plotter, 'app_window', MainWindow)
+ assert_hasattr(plotter, 'main_menu', QMenuBar)
+ assert_hasattr(plotter, '_menu_close_action', QAction)
+ assert_hasattr(plotter, '_edl_action', QAction)
+ assert_hasattr(plotter, '_parallel_projection_action', QAction)
window = plotter.app_window
main_menu = plotter.main_menu
@@ -627,8 +666,8 @@ def test_background_plotting_menu_bar(qtbot, plotting):
def test_drop_event(tmpdir):
- output_dir = str(tmpdir.mkdir("tmpdir"))
- filename = str(os.path.join(output_dir, "tmp.vtk"))
+ output_dir = str(tmpdir.mkdir('tmpdir'))
+ filename = str(os.path.join(output_dir, 'tmp.vtk'))
mesh = pyvista.Cone()
mesh.save(filename)
assert os.path.isfile(filename)
@@ -648,8 +687,8 @@ def test_drop_event(tmpdir):
def test_drag_event(tmpdir):
- output_dir = str(tmpdir.mkdir("tmpdir"))
- filename = str(os.path.join(output_dir, "tmp.vtk"))
+ output_dir = str(tmpdir.mkdir('tmpdir'))
+ filename = str(os.path.join(output_dir, 'tmp.vtk'))
mesh = pyvista.Cone()
mesh.save(filename)
assert os.path.isfile(filename)
@@ -698,9 +737,9 @@ def update_app_icon(slf):
title='Testing Window',
update_app_icon=True, # also does add_callback
)
- assert_hasattr(plotter, "app_window", MainWindow)
- assert_hasattr(plotter, "_callback_timer", QTimer)
- assert_hasattr(plotter, "counters", list)
+ assert_hasattr(plotter, 'app_window', MainWindow)
+ assert_hasattr(plotter, '_callback_timer', QTimer)
+ assert_hasattr(plotter, 'counters', list)
assert plotter._last_update_time == -np.inf
sphere = pyvista.Sphere()
@@ -721,13 +760,17 @@ def update_app_icon(slf):
assert update_count[0] in [1, 2]
plotter.update_app_icon() # should be a no-op
assert update_count[0] in [2, 3]
- with pytest.raises(ValueError, match="ndarray with shape"):
- plotter.set_icon(0.)
- # Maybe someday manually setting "set_icon" should disable update_app_icon?
+ with pytest.raises(ValueError, match='ndarray with shape'):
+ plotter.set_icon(0.0)
+ # Maybe someday manually setting 'set_icon' should disable update_app_icon?
# Strings also supported directly by QIcon
- plotter.set_icon(os.path.join(
- os.path.dirname(pyvistaqt.__file__), "data",
- "pyvista_logo_square.png"))
+ plotter.set_icon(
+ os.path.join(
+ os.path.dirname(pyvistaqt.__file__),
+ 'data',
+ 'pyvista_logo_square.png',
+ )
+ )
callback_timer.stop()
assert not callback_timer.isActive()
@@ -757,34 +800,41 @@ def update_app_icon(slf):
assert not callback_timer.isActive() # window stops the callback
-@pytest.mark.parametrize('close_event', [
- "plotter_close",
- "window_close",
- "q_key_press",
- "menu_exit",
- "del_finalizer",
- ])
-@pytest.mark.parametrize('empty_scene', [
- True,
- False,
- ])
+@pytest.mark.parametrize(
+ 'close_event',
+ [
+ 'plotter_close',
+ 'window_close',
+ 'q_key_press',
+ 'menu_exit',
+ 'del_finalizer',
+ ],
+)
+@pytest.mark.parametrize(
+ 'empty_scene',
+ [
+ True,
+ False,
+ ],
+)
def test_background_plotting_close(qtbot, close_event, empty_scene, plotting):
from pyvista.plotting.plotting import _ALL_PLOTTERS, close_all
+
close_all() # this is necessary to test _ALL_PLOTTERS
assert len(_ALL_PLOTTERS) == 0
plotter = _create_testing_scene(empty_scene)
# check that BackgroundPlotter.__init__() is called
- assert_hasattr(plotter, "app_window", MainWindow)
- assert_hasattr(plotter, "main_menu", QMenuBar)
+ assert_hasattr(plotter, 'app_window', MainWindow)
+ assert_hasattr(plotter, 'main_menu', QMenuBar)
# check that QtInteractor.__init__() is called
- assert hasattr(plotter, "iren")
- assert_hasattr(plotter, "render_timer", QTimer)
+ assert hasattr(plotter, 'iren')
+ assert_hasattr(plotter, 'render_timer', QTimer)
# check that BasePlotter.__init__() is called
- assert_hasattr(plotter, "_closed", bool)
+ assert_hasattr(plotter, '_closed', bool)
# check that QVTKRenderWindowInteractorAdapter._init__() is called
- assert_hasattr(plotter, "interactor", QVTKRenderWindowInteractor)
+ assert_hasattr(plotter, 'interactor', QVTKRenderWindowInteractor)
window = plotter.app_window # MainWindow
main_menu = plotter.main_menu
@@ -812,15 +862,15 @@ def test_background_plotting_close(qtbot, close_event, empty_scene, plotting):
assert not plotter._closed
with qtbot.wait_signals([window.signal_close], timeout=500):
- if close_event == "plotter_close":
+ if close_event == 'plotter_close':
plotter.close()
- elif close_event == "window_close":
+ elif close_event == 'window_close':
window.close()
- elif close_event == "q_key_press":
- qtbot.keyClick(interactor, "q")
- elif close_event == "menu_exit":
+ elif close_event == 'q_key_press':
+ qtbot.keyClick(interactor, 'q')
+ elif close_event == 'menu_exit':
plotter._menu_close_action.trigger()
- elif close_event == "del_finalizer":
+ elif close_event == 'del_finalizer':
plotter.__del__()
# check that the widgets are closed
@@ -831,7 +881,7 @@ def test_background_plotting_close(qtbot, close_event, empty_scene, plotting):
# check that BasePlotter.close() is called
if Version(pyvista.__version__) < Version('0.27.0'):
- assert not hasattr(window.vtk_widget, "iren")
+ assert not hasattr(window.vtk_widget, 'iren')
assert plotter._closed
# check that BasePlotter.__init__() is called only once
@@ -899,10 +949,7 @@ def _create_testing_scene(empty_scene, show=False, off_screen=False):
plotter.add_mesh(cylinder, smooth_shading=True)
plotter.show_bounds()
plotter.subplot(1, 1)
- sphere = pyvista.Sphere(
- phi_resolution=6,
- theta_resolution=6
- )
+ sphere = pyvista.Sphere(phi_resolution=6, theta_resolution=6)
plotter.add_mesh(sphere)
plotter.enable_cell_picking()
return plotter
diff --git a/tests/test_qt.py b/tests/test_qt.py
index abb74984..63de4de2 100644
--- a/tests/test_qt.py
+++ b/tests/test_qt.py
@@ -1,13 +1,30 @@
+import importlib
+import sys
+
import pytest
+import pyvistaqt
+
+
+def _check_qt_installed():
+ try:
+ from qtpy import QtCore # noqa
+ except Exception:
+ return False
+ else:
+ return True
+
-def test_no_qt_binding(no_qt):
- from pyvistaqt import BackgroundPlotter, MainWindow, MultiPlotter, QtInteractor
- with pytest.raises(RuntimeError, match="No Qt binding"):
- BackgroundPlotter()
- with pytest.raises(RuntimeError, match="No Qt binding"):
- MainWindow()
- with pytest.raises(RuntimeError, match="No Qt binding"):
- MultiPlotter()
- with pytest.raises(RuntimeError, match="No Qt binding"):
- QtInteractor()
+def test_no_qt_binding(monkeypatch):
+ need_reload = False
+ if _check_qt_installed():
+ need_reload = True
+ monkeypatch.setenv('QT_API', 'bad_name')
+ sys.modules.pop('qtpy')
+ assert 'qtpy' not in sys.modules
+ with pytest.raises(RuntimeError, match='No Qt binding'):
+ importlib.reload(pyvistaqt)
+ monkeypatch.undo()
+ if need_reload:
+ importlib.reload(pyvistaqt)
+ assert 'qtpy' in sys.modules