Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/qtpy #61

Merged
merged 23 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
81f8ed5
Add basic qtpy layer
GuillaumeFavelier Jun 5, 2020
c08ec9e
Add to env
GuillaumeFavelier Jun 5, 2020
c4d892e
Merge branch 'master' into feat/qtpy
GuillaumeFavelier Jul 3, 2020
86b96d8
Merge branch 'master' into feat/qtpy
GuillaumeFavelier Jul 3, 2020
cebd70f
Merge branch 'master' into feat/qtpy
GuillaumeFavelier Jul 15, 2020
204d38e
Merge branch 'master' into feat/qtpy
GuillaumeFavelier Aug 4, 2020
b85d112
Merge branch 'master' into feat/qtpy
Oct 6, 2020
7d87815
tests run again; they don't pass on my machine, though
Oct 6, 2020
5307cc5
replaced pyqt5 requirement with qtpy
Oct 6, 2020
67c2bd2
fixed linting errors in plotting.py
Oct 7, 2020
81a142e
restored counter docstring; restored counter typehint; removed uneces…
Oct 8, 2020
7b80509
restored QtPy5 in install_requires
Oct 9, 2020
78eaf7a
updated docs to mention QtPy, and give basic info on how to use PySide2
Oct 9, 2020
c31306d
restored pylint disable too few public methods for FileDialog class
Oct 9, 2020
8695c93
formatted plotting.py
Oct 9, 2020
866aa6a
mypy errors - ignore missing imports for qtpy
Oct 9, 2020
210c4fc
isort - reformatted imports
Oct 9, 2020
4145853
BUG (I think) - fixed typo 'pyvista' -> 'pyvistaqt' for running pydoc…
Oct 9, 2020
9cb7056
pydocstyle fixes
Oct 9, 2020
2775c88
fixed trailing space (by running black)
Oct 9, 2020
2d1ebce
TST: Add more dependencies
GuillaumeFavelier Oct 16, 2020
d6fd04b
Try another set of deps
GuillaumeFavelier Oct 16, 2020
52c5972
Add comment and link
GuillaumeFavelier Oct 16, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .ci/azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ jobs:

- script: |
sudo apt-get install python3-tk
# The following linux deps are necessary to use the builtin xcb:
# https://github.com/pyvista/pyvistaqt/pull/61#issuecomment-709320826
sudo apt-get install -y libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 \
libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \
libxcb-xinerama0 libxcb-xfixes0
pip install -r requirements_docs.txt
displayName: 'Install dependencies'

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ codespell:

pydocstyle:
@echo "Running pydocstyle"
@pydocstyle pyvista
@pydocstyle pyvistaqt

coverage:
@echo "Running coverage"
Expand Down
18 changes: 15 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ Overview
:alt: MIT License

The python package ``pyvistaqt`` extends the
functionality of ``pyvista`` through the usage of ``PyQt5``. Since ``PyQt5`` operates in a separate thread than VTK, you can similtaniously have an active VTK plot and a non-blocking Python session.
functionality of ``pyvista`` through the usage of *Qt*.
Since *Qt* applications operates in a separate thread than VTK,
you can simultaneously have an active VTK plot and a non-blocking Python session.

.. figure:: ./images/user-generated/qt_multiplot.png
:width: 450pt
Expand Down Expand Up @@ -84,6 +86,16 @@ sphere.
License
*******

While ``pyvistaqt`` is under the MIT license, ``pyqt5`` is subject to
the GPL license. Please see deails at
``pyvistaqt`` is under the MIT license.
However, Qt bindings have licenses of their own.

Historically, ``pyvistaqt`` has used ``pyqt5``, which is subject
to the GPL license. See details at
`Riverbank License FAQ <https://www.riverbankcomputing.com/commercial/license-faq>`_.

``pyvistaqt`` is transitioning to using ``qtpy``

> QtPy is a small abstraction layer that lets you write applications using a single API call to either PyQt or PySide.
Comment on lines +92 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the leading >? I would make this more compact like:

Suggested change
Historically, ``pyvistaqt`` has used ``pyqt5``, which is subject
to the GPL license. See details at
`Riverbank License FAQ <https://www.riverbankcomputing.com/commercial/license-faq>`_.
``pyvistaqt`` is transitioning to using ``qtpy``
> QtPy is a small abstraction layer that lets you write applications using a single API call to either PyQt or PySide.
Historically, ``pyvistaqt`` has used ``pyqt5``, which is subject
to the GPL license. See details at
`Riverbank License FAQ <https://www.riverbankcomputing.com/commercial/license-faq>`_. ``pyvistaqt`` now uses QtPy if present, and will require it after version 0.3. QtPy is a small abstraction layer that lets you write applications using a single API call to either PyQt or PySide (which has a LGPL license).

The wording about being required or not should be adjusted based on whether or not there is actually going to be a deprecation period.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The leading > is because it's a quote from the qtpy docs... But I'm open to changing it to something that looks nicer.


Please refer to the `QtPy documentation <https://github.com/spyder-ide/qtpy>`_
to learn more.
51 changes: 40 additions & 11 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Usage
PyVista has an interface for placing plots in ``pyvistaqt`` that extends the
functionality of the ``QVTKRenderWindowInteractor`` class.
The ``pyvistaqt.QtInteractor`` class allows you to have the same functionality
of the ``Plotter`` class within a ``PyQt5`` application.
of the ``Plotter`` class within a Qt application.
This simplifies adding meshes, updating, and controlling them when using
``PyQt5``.
Qt.


Background Plotting
Expand Down Expand Up @@ -43,20 +43,26 @@ sphere to an empty plotting window.

import sys

from PyQt5 import Qt
# Setting the Qt bindings for QtPy
import os
os.environ["QT_API"] = "pyqt5"

from qtpy import QtWidgets
from qtpy.QtWidgets import QMainWindow

import numpy as np

import pyvista as pv
from pyvistaqt import QtInteractor

class MainWindow(Qt.QMainWindow):
class MainWindow(QMainWindow):

def __init__(self, parent=None, show=True):
Qt.QMainWindow.__init__(self, parent)
QtWidgets.QMainWindow.__init__(self, parent)

# create the frame
self.frame = Qt.QFrame()
vlayout = Qt.QVBoxLayout()
self.frame = QtWidgets.QFrame()
vlayout = QtWidgets.QVBoxLayout()

# add the pyvista interactor object
self.plotter = QtInteractor(self.frame)
Expand All @@ -68,14 +74,14 @@ sphere to an empty plotting window.
# simple menu to demo functions
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu('File')
exitButton = Qt.QAction('Exit', self)
exitButton = QtWidgets.QAction('Exit', self)
exitButton.setShortcut('Ctrl+Q')
exitButton.triggered.connect(self.close)
fileMenu.addAction(exitButton)

# allow adding a sphere
meshMenu = mainMenu.addMenu('Mesh')
self.add_sphere_action = Qt.QAction('Add Sphere', self)
self.add_sphere_action = QtWidgets.QAction('Add Sphere', self)
self.add_sphere_action.triggered.connect(self.add_sphere)
meshMenu.addAction(self.add_sphere_action)

Expand All @@ -85,12 +91,12 @@ sphere to an empty plotting window.
def add_sphere(self):
""" add a sphere to the pyqt frame """
sphere = pv.Sphere()
self.plotter.add_mesh(sphere)
self.plotter.add_mesh(sphere, show_edges=True)
self.plotter.reset_camera()


if __name__ == '__main__':
app = Qt.QApplication(sys.argv)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())

Expand All @@ -99,3 +105,26 @@ sphere to an empty plotting window.
:width: 600pt

PyQt5 pyvista QtInteractor


Using Different Qt bindings
~~~~~~~~~~~~~~~~~~~~~~~~~~~

To use different Qt bindings you must first install them.
For example, to use *PySide2*, you install it via:

.. code:: bash

pip install PySide2


Then you set the ``QT_API`` value to the specific binding you would
like to use:

.. code:: python

os.environ["QT_API"] = "pyside2"

Please refer to the
`*QtPy* documentation page <https://github.com/spyder-ide/qtpy>`_
for more information.
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies:
- matplotlib
- appdirs
- pyqt
- qtpy
- imageio>=2.5.0
- imageio-ffmpeg
- colorcet
Expand Down
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ignore_missing_imports = True
[mypy-pyvista.*]
ignore_missing_imports = True

[mypy-PyQt5.*]
[mypy-qtpy.*]
ignore_missing_imports = True

[mypy-IPython.*]
Expand Down
15 changes: 6 additions & 9 deletions pyvistaqt/counter.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
"""
This module contains a basic Qt-compatible counter class.
"""
nicobako marked this conversation as resolved.
Show resolved Hide resolved
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
"""This module contains a basic Qt-compatible counter class."""

from qtpy.QtCore import QObject, Signal, Slot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will immediately break any pyvistaqt code without deprecation. We colud make a small compatibility layer just by making a little ._qtpy file that contains:

try:
    from qtpy import QtCore
except ImportError:
    warn('qtpy not found; falling back to PyQt5. qtpy will be required in pyvistaqt 0.4')
    from PyQt5.QtCore import ...
else:
    from qtpy import ...

And then in this file do:

Suggested change
from qtpy.QtCore import QObject, Signal, Slot
from ._qtpy.QtCore import QObject, Signal, Slot

And once we cut version 0.3, we can revert the from ._qtpy import ... lines to just be from qtpy import ...

But maybe this is overkill since pyvistaqt is still relatively new. Up to the main pyvista devs I guess...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the core developers think there needs to be a deprecation period, then this would be a good solution. I'm open to this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just going ahead without the depreciation works. Users can always downgrade to an older version, and adding in a depreciation is a bit of a pain. Plus, installing qtpy should be in the setup.py, so it will be included upon install/upgrade.

We can always give a more descriptive error on import if we wish, but it's a bit of extra work making it backwards compatible and adding a depreciation warning as well.



class Counter(QObject):
"""
Counter class with Qt signal/slot.
"""
"""Counter class with Qt signal/slot."""

# pylint: disable=too-few-public-methods

signal_finished = pyqtSignal()
signal_finished = Signal()

def __init__(self, count: int) -> None:
"""Initialize the counter."""
Expand All @@ -25,7 +22,7 @@ def __init__(self, count: int) -> None:
else:
raise ValueError("count is not strictly positive.")

@pyqtSlot()
@Slot()
def decrease(self) -> None:
nicobako marked this conversation as resolved.
Show resolved Hide resolved
"""Decrease the count."""
self.count -= 1
Expand Down
20 changes: 8 additions & 12 deletions pyvistaqt/dialog.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"""
This module contains Qt dialog widgets.
"""
"""This module contains Qt dialog widgets."""
import os
from typing import Any, List

import numpy as np # type: ignore
import pyvista as pv
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import (
from qtpy import QtCore
from qtpy.QtCore import Signal
from qtpy.QtWidgets import (
QDialog,
QDoubleSpinBox,
QFileDialog,
Expand All @@ -29,7 +27,7 @@ class FileDialog(QFileDialog):

# pylint: disable=too-few-public-methods
nicobako marked this conversation as resolved.
Show resolved Hide resolved

dlg_accepted = pyqtSignal(str)
dlg_accepted = Signal(str)

# pylint: disable=too-many-arguments
def __init__(
Expand Down Expand Up @@ -196,14 +194,12 @@ def value(self, new_value: float) -> None:


class ScaleAxesDialog(QDialog):
"""
Dialog to control axes scaling.
"""
"""Dialog to control axes scaling."""

# pylint: disable=too-few-public-methods

accepted = pyqtSignal(float)
signal_close = pyqtSignal()
accepted = Signal(float)
signal_close = Signal()

def __init__(
self, parent: MainWindow, plotter: pv.Plotter, show: bool = True
Expand Down
11 changes: 5 additions & 6 deletions pyvistaqt/editor.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""
This module contains the Qt scene editor.
"""
"""This module contains the Qt scene editor."""

from typing import List

import vtk
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
from pyvista import Renderer
from qtpy.QtCore import Qt
from qtpy.QtWidgets import (
QCheckBox,
QDialog,
QDoubleSpinBox,
Expand All @@ -17,7 +17,6 @@
QVBoxLayout,
QWidget,
)
from pyvista import Renderer

from .window import MainWindow

Expand Down
24 changes: 12 additions & 12 deletions pyvistaqt/plotting.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""
This module contains the QtInteractor and BackgroundPlotter.

Diagram
^^^^^^^

Expand Down Expand Up @@ -49,9 +51,12 @@
import pyvista
import scooby # type: ignore
import vtk
from PyQt5 import QtCore
from PyQt5.QtCore import QSize, QTimer, pyqtSignal
from PyQt5.QtWidgets import ( # pylint: disable=unused-import
from pyvista.plotting.plotting import BasePlotter
from pyvista.plotting.theme import rcParams
from pyvista.utilities import conditional_decorator, threaded
from qtpy import QtCore
from qtpy.QtCore import QSize, QTimer, Signal
from qtpy.QtWidgets import (
QAction,
QApplication,
QFrame,
Expand All @@ -60,9 +65,6 @@
QToolBar,
QVBoxLayout,
)
from pyvista.plotting.plotting import BasePlotter
from pyvista.plotting.theme import rcParams
from pyvista.utilities import conditional_decorator, threaded
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor

from .counter import Counter
Expand All @@ -74,7 +76,7 @@
# pylint: disable=unused-import
from IPython.external.qt_for_kernel import QtGui
else:
from PyQt5 import QtGui # pylint: disable=ungrouped-imports
from qtpy import QtGui # pylint: disable=ungrouped-imports

LOG = logging.getLogger("pyvistaqt")
LOG.setLevel(logging.CRITICAL)
Expand Down Expand Up @@ -167,8 +169,8 @@ class QtInteractor(QVTKRenderWindowInteractor, BasePlotter):
# pylint: disable=too-many-statements

# Signals must be class attributes
render_signal = pyqtSignal()
key_press_event_signal = pyqtSignal(vtk.vtkGenericRenderWindowInteractor, str)
render_signal = Signal()
key_press_event_signal = Signal(vtk.vtkGenericRenderWindowInteractor, str)

# pylint: disable=too-many-arguments
def __init__(
Expand Down Expand Up @@ -301,6 +303,7 @@ def render(self) -> None:
# pylint: disable=invalid-name,no-self-use
def dragEnterEvent(self, event: QtGui.QDragEnterEvent) -> None:
"""Event is called when something is dropped onto the vtk window.

Only triggers event when event contains file paths that
exist. User can drop anything in this window and we only want
to allow files.
Expand Down Expand Up @@ -592,9 +595,6 @@ def __init__(

# run within python
if app is None:
# pylint: disable=import-outside-toplevel,redefined-outer-name,reimported
from PyQt5.QtWidgets import QApplication

app = QApplication.instance()
if not app: # pragma: no cover
app = QApplication(["PyVista"])
Expand Down
19 changes: 8 additions & 11 deletions pyvistaqt/window.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
"""
This module contains a Qt-compatible MainWindow class.
"""
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QMainWindow
"""This module contains a Qt-compatible MainWindow class."""

from qtpy import QtCore
from qtpy.QtCore import Signal
from qtpy.QtWidgets import QMainWindow


class MainWindow(QMainWindow):
"""
Convenience MainWindow that manages the application.
"""
"""Convenience MainWindow that manages the application."""

signal_close = pyqtSignal()
signal_gesture = pyqtSignal(QtCore.QEvent)
signal_close = Signal()
signal_gesture = Signal(QtCore.QEvent)

def event(self, event: QtCore.QEvent) -> bool:
"""Manage window events and filter the gesture event."""
Expand Down
1 change: 1 addition & 0 deletions requirements_docs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ imageio-ffmpeg
colorcet
cmocean
meshio>=4.0.3, <5.0
qtpy
PyQt5==5.11.3
pytest-sphinx
Sphinx
Expand Down
1 change: 1 addition & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pytest
pytest-cov
pytest-memprof
codecov
qtpy
PyQt5==5.11.3
pytest-qt
imageio>=2.5.0
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
keywords='vtk numpy plotting mesh qt pyqt',
python_requires='>=3.6.*',
install_requires=['pyvista>=0.25.0',
'PyQt5>=5.11.3',
'QtPy>=1.9.0',
'PyQt5>=5.11.3'
'imageio>=2.5.0',
],
package_data={'pyvistaqt': [
Expand Down