From f4a4af8e2e5e6c3dc882ef4200196606ff25655a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 22 Oct 2020 14:22:29 -0400 Subject: [PATCH 01/35] MAINT: Add GC tests --- .ci/azure-pipelines.yml | 10 ++++++- pyvistaqt/editor.py | 7 +++-- pyvistaqt/plotting.py | 32 +++++++++++++++++++++ tests/test_plotting.py | 62 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 101 insertions(+), 10 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 0ce9a9dd..66a6e3ed 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -114,6 +114,11 @@ jobs: python.version: '3.6' Python37: python.version: '3.7' + Python38: + python.version: '3.8' + variables: + PYVISTA_PRE: true + GC_TEST: true steps: - task: UsePythonVersion@0 @@ -123,6 +128,9 @@ jobs: - script: | pip install wheel --upgrade + if [ "$PYVISTA_PRE" == "true" ]; then + pip install https://github.com/larsoner/pyvista/zipball/mem + fi; python setup.py bdist_wheel pip install dist/pyvistaqt*.whl displayName: Build wheel and install pyvistaqt @@ -369,7 +377,7 @@ jobs: displayName: Upload Documentation to pyvistaqt-docs env: GH_TOKEN: $(gh.token) - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) # don't run job on no-ci builds condition: not(contains(variables['System.PullRequest.SourceBranch'], 'no-ci')) diff --git a/pyvistaqt/editor.py b/pyvistaqt/editor.py index 1b467ea5..ea1db658 100644 --- a/pyvistaqt/editor.py +++ b/pyvistaqt/editor.py @@ -1,5 +1,6 @@ """This module contains the Qt scene editor.""" +import weakref from typing import List import vtk @@ -105,9 +106,10 @@ def _get_actor_widget(actor: vtk.vtkActor) -> QWidget: prop = actor.GetProperty() # visibility + sv = weakref.ref(actor.SetVisibility) visibility = QCheckBox("Visibility") visibility.setChecked(actor.GetVisibility()) - visibility.toggled.connect(actor.SetVisibility) + visibility.toggled.connect(lambda v: sv()(v)) layout.addWidget(visibility) if prop is not None: @@ -116,7 +118,8 @@ def _get_actor_widget(actor: vtk.vtkActor) -> QWidget: opacity = QDoubleSpinBox() opacity.setMaximum(1.0) opacity.setValue(prop.GetOpacity()) - opacity.valueChanged.connect(prop.SetOpacity) + so = weakref.ref(prop.SetOpacity) + opacity.valueChanged.connect(lambda v: so()(v)) tmp_layout.addWidget(QLabel("Opacity")) tmp_layout.addWidget(opacity) layout.addLayout(tmp_layout) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 088da258..4d35371a 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -474,6 +474,7 @@ def close(self) -> None: if hasattr(self, "render_timer"): self.render_timer.stop() BasePlotter.close(self) + BasePlotter.deep_clean(self) QVTKRenderWindowInteractor.close(self) @@ -810,6 +811,14 @@ def __del__(self) -> None: # pragma: no cover """Delete the qt plotter.""" if not self._closed: self.app_window.close() + # Qt LeaveEvent requires _Iren so we use _FakeIren instead of None + # to resolve the ref to vtkGenericRenderWindowInteractor + self._Iren = _FakeEventHandler() + for key in ('_RenderWindow', 'renderer'): + try: + setattr(self, key, None) + except AttributeError: + pass def add_callback( self, func: Callable, interval: int = 1000, count: Optional[int] = None @@ -856,3 +865,26 @@ def _create_menu_bar(parent: Any) -> QMenuBar: if parent is not None: parent.setMenuBar(menu_bar) return menu_bar + + +class _FakeEventHandler(): + def SetDPI(self, dpi): + pass + + def EnterEvent(self): + pass + + def MouseMoveEvent(self): + pass + + def LeaveEvent(self): + pass + + def SetEventInformation(self, *args, **kwargs): + pass + + def SetSize(self, *args, **kwargs): + pass + + def ConfigureEvent(self, *args, **kwargs): + pass \ No newline at end of file diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 5e613d71..9e368bb8 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -1,5 +1,7 @@ +import gc import os import platform +import weakref import numpy as np import pytest @@ -20,6 +22,38 @@ NO_PLOTTING = not system_supports_plotting() +# Adapted from PyVista +def _is_vtk(obj): + try: + return obj.__class__.__name__.startswith('vtk') + except Exception: # old Python sometimes no __class__.__name__ + return False + + +@pytest.fixture(autouse=True) +def check_gc(request): + """Ensure that all VTK objects are garbage-collected by Python.""" + if 'test_ipython' in request.node.name: # XXX this keeps a ref + yield + return + # We need https://github.com/pyvista/pyvista/pull/958 to actually run + # this test. Eventually we should use LooseVersion, but as of 2020/10/22 + # 0.26.1 is the latest PyPi version and on master the version is weirdly + # 0.26.0 (as opposed to 0.26.2.dev0 or 0.27.dev0) so we can't. So for now + # let's use an env var (GC_TEST) instead of: + # if LooseVersion(pyvista.__version__) < LooseVersion('0.26.2'): + if os.getenv('GC_TEST', '').lower() != 'true': + yield + return + before = set(id(o) for o in gc.get_objects() if _is_vtk(o)) + yield + pyvista.close_all() + gc.collect() + after = [o for o in gc.get_objects() if _is_vtk(o) and id(o) not in before] + after = sorted(o.__class__.__name__ for o in after) + assert len(after) == 0, 'Not all objects GCed:\n' + '\n'.join(after) + + class TstWindow(MainWindow): def __init__(self, parent=None, show=True, off_screen=True): MainWindow.__init__(self, parent) @@ -140,7 +174,8 @@ def test_editor(qtbot): # add at least an actor plotter.subplot(0, 0) - plotter.add_mesh(pyvista.Sphere()) + pd = pyvista.Sphere() + actor = plotter.add_mesh(pd) plotter.subplot(1, 0) plotter.show_axes() @@ -178,12 +213,17 @@ def test_editor(qtbot): # hide the editor for coverage editor.toggle() + plotter.remove_actor(actor) + pd.ReleaseData() + del pd, actor + plotter.mesh = None plotter.close() + plotter.deep_clean() - plotter = BackgroundPlotter(editor=False) - qtbot.addWidget(plotter.app_window) - assert plotter.editor is None - plotter.close() + #plotter = BackgroundPlotter(editor=False) + #qtbot.addWidget(plotter.app_window) + #assert plotter.editor is None + #plotter.close() @pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") @@ -524,10 +564,10 @@ def test_background_plotting_menu_bar(qtbot): def test_background_plotting_add_callback(qtbot, monkeypatch): class CallBack(object): def __init__(self, sphere): - self.sphere = sphere + self.sphere = weakref.ref(sphere) def __call__(self): - self.sphere.points *= 0.5 + self.sphere().points[:] = self.sphere().points * 0.5 update_count = [0] orig_update_app_icon = BackgroundPlotter.update_app_icon @@ -595,6 +635,7 @@ def update_app_icon(slf): assert callback_timer.isActive() plotter.close() assert not callback_timer.isActive() # window stops the callback + sphere.ReleaseData() @pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") @@ -680,6 +721,13 @@ def test_background_plotting_close(qtbot, close_event, empty_scene): # check that BasePlotter.__init__() is called only once assert len(_ALL_PLOTTERS) == 1 + # clean up + del qtbot, window, main_menu, interactor, render_timer + _ALL_PLOTTERS.clear() + + #if not empty_scene: # XXX unclear why this is needed... + # plotter.__del__() + def _create_testing_scene(empty_scene, show=False, off_screen=False): if empty_scene: From 2705025ed6db87154c58c3b182e5a9099a1ae40a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 22 Oct 2020 14:52:02 -0400 Subject: [PATCH 02/35] FIX: Missed one --- tests/test_plotting.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 9e368bb8..7063f6d5 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -220,10 +220,10 @@ def test_editor(qtbot): plotter.close() plotter.deep_clean() - #plotter = BackgroundPlotter(editor=False) - #qtbot.addWidget(plotter.app_window) - #assert plotter.editor is None - #plotter.close() + plotter = BackgroundPlotter(editor=False) + qtbot.addWidget(plotter.app_window) + assert plotter.editor is None + plotter.close() @pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") From c5e1cdd029fabd7e64a2d34206f983e80afb35d4 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 22 Oct 2020 14:53:33 -0400 Subject: [PATCH 03/35] FIX: Missed more --- tests/test_plotting.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 7063f6d5..186e1418 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -214,11 +214,7 @@ def test_editor(qtbot): # hide the editor for coverage editor.toggle() plotter.remove_actor(actor) - pd.ReleaseData() - del pd, actor - plotter.mesh = None plotter.close() - plotter.deep_clean() plotter = BackgroundPlotter(editor=False) qtbot.addWidget(plotter.app_window) @@ -721,13 +717,6 @@ def test_background_plotting_close(qtbot, close_event, empty_scene): # check that BasePlotter.__init__() is called only once assert len(_ALL_PLOTTERS) == 1 - # clean up - del qtbot, window, main_menu, interactor, render_timer - _ALL_PLOTTERS.clear() - - #if not empty_scene: # XXX unclear why this is needed... - # plotter.__del__() - def _create_testing_scene(empty_scene, show=False, off_screen=False): if empty_scene: From 6cb8afe5119dcd745d569affe222d8b0fe3cea50 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 22 Oct 2020 14:54:15 -0400 Subject: [PATCH 04/35] FIX: More --- tests/test_plotting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 186e1418..4cb33171 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -631,7 +631,6 @@ def update_app_icon(slf): assert callback_timer.isActive() plotter.close() assert not callback_timer.isActive() # window stops the callback - sphere.ReleaseData() @pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") From fc01eaf77282ac49b87691e85816e512550eb743 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:16:18 -0400 Subject: [PATCH 05/35] FIX: Revert deep --- pyvistaqt/plotting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 4d35371a..596fe7f0 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -474,7 +474,6 @@ def close(self) -> None: if hasattr(self, "render_timer"): self.render_timer.stop() BasePlotter.close(self) - BasePlotter.deep_clean(self) QVTKRenderWindowInteractor.close(self) From 1bba5756c07b77b250a3e2810252b7cd8e3d1d20 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:17:18 -0400 Subject: [PATCH 06/35] FIX: Revert Azure addition --- .ci/azure-pipelines.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 66a6e3ed..b00f6fd8 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -114,8 +114,6 @@ jobs: python.version: '3.6' Python37: python.version: '3.7' - Python38: - python.version: '3.8' variables: PYVISTA_PRE: true GC_TEST: true From 6341359464006a87662feb17e12affa8db79ff7c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:23:47 -0400 Subject: [PATCH 07/35] FIX: Try again? --- .ci/azure-pipelines.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index b00f6fd8..925ab115 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -112,11 +112,12 @@ jobs: matrix: Python36: python.version: '3.6' + PYVISTA_PRE: false + GC_TEST: false Python37: python.version: '3.7' - variables: - PYVISTA_PRE: true - GC_TEST: true + PYVISTA_PRE: true + GC_TEST: true steps: - task: UsePythonVersion@0 @@ -126,7 +127,7 @@ jobs: - script: | pip install wheel --upgrade - if [ "$PYVISTA_PRE" == "true" ]; then + if [ "$(PYVISTA_PRE)" == "true" ]; then pip install https://github.com/larsoner/pyvista/zipball/mem fi; python setup.py bdist_wheel @@ -147,7 +148,7 @@ jobs: - script: | pip install pytest-azurepipelines - pytest -v --cov pyvistaqt --cov-report xml + GC_TEST=$(GC_TEST) pytest -v --cov pyvistaqt --cov-report xml displayName: 'Test Core API' - script: | # this must be right after the core API From 82cebae274fed7eeffb40fcab47f5166e3808acf Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:41:50 -0400 Subject: [PATCH 08/35] MAINT: Reorg --- .ci/azure-pipelines.yml | 7 ----- tests/test_plotting.py | 70 +++++++---------------------------------- 2 files changed, 12 insertions(+), 65 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 925ab115..1ce9bd71 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -112,12 +112,8 @@ jobs: matrix: Python36: python.version: '3.6' - PYVISTA_PRE: false - GC_TEST: false Python37: python.version: '3.7' - PYVISTA_PRE: true - GC_TEST: true steps: - task: UsePythonVersion@0 @@ -127,9 +123,6 @@ jobs: - script: | pip install wheel --upgrade - if [ "$(PYVISTA_PRE)" == "true" ]; then - pip install https://github.com/larsoner/pyvista/zipball/mem - fi; python setup.py bdist_wheel pip install dist/pyvistaqt*.whl displayName: Build wheel and install pyvistaqt diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 4cb33171..d008b369 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -1,4 +1,3 @@ -import gc import os import platform import weakref @@ -11,7 +10,7 @@ from qtpy.QtCore import Qt from qtpy.QtWidgets import QTreeWidget, QStackedWidget, QCheckBox from pyvista import rcParams -from pyvista.plotting import Renderer, system_supports_plotting +from pyvista.plotting import Renderer import pyvistaqt from pyvistaqt import BackgroundPlotter, MainWindow, QtInteractor @@ -19,40 +18,6 @@ _create_menu_bar) from pyvistaqt.editor import Editor -NO_PLOTTING = not system_supports_plotting() - - -# Adapted from PyVista -def _is_vtk(obj): - try: - return obj.__class__.__name__.startswith('vtk') - except Exception: # old Python sometimes no __class__.__name__ - return False - - -@pytest.fixture(autouse=True) -def check_gc(request): - """Ensure that all VTK objects are garbage-collected by Python.""" - if 'test_ipython' in request.node.name: # XXX this keeps a ref - yield - return - # We need https://github.com/pyvista/pyvista/pull/958 to actually run - # this test. Eventually we should use LooseVersion, but as of 2020/10/22 - # 0.26.1 is the latest PyPi version and on master the version is weirdly - # 0.26.0 (as opposed to 0.26.2.dev0 or 0.27.dev0) so we can't. So for now - # let's use an env var (GC_TEST) instead of: - # if LooseVersion(pyvista.__version__) < LooseVersion('0.26.2'): - if os.getenv('GC_TEST', '').lower() != 'true': - yield - return - before = set(id(o) for o in gc.get_objects() if _is_vtk(o)) - yield - pyvista.close_all() - gc.collect() - after = [o for o in gc.get_objects() if _is_vtk(o) and id(o) not in before] - after = sorted(o.__class__.__name__ for o in after) - assert len(after) == 0, 'Not all objects GCed:\n' + '\n'.join(after) - class TstWindow(MainWindow): def __init__(self, parent=None, show=True, off_screen=True): @@ -163,8 +128,7 @@ def test_counter(qtbot): assert counter.count == 0 -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") -def test_editor(qtbot): +def test_editor(qtbot, plotting): timeout = 1000 # adjusted timeout for MacOS # editor=True by default @@ -222,8 +186,7 @@ def test_editor(qtbot): plotter.close() -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") -def test_qt_interactor(qtbot): +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 @@ -286,12 +249,11 @@ def test_qt_interactor(qtbot): assert len(_ALL_PLOTTERS) == 1 -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") @pytest.mark.parametrize('show_plotter', [ True, False, ]) -def test_background_plotting_axes_scale(qtbot, show_plotter): +def test_background_plotting_axes_scale(qtbot, show_plotter, plotting): plotter = BackgroundPlotter( show=show_plotter, off_screen=False, @@ -339,8 +301,7 @@ def test_background_plotting_axes_scale(qtbot, show_plotter): assert not dlg.isVisible() -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") -def test_background_plotting_camera(qtbot): +def test_background_plotting_camera(qtbot, plotting): plotter = BackgroundPlotter(off_screen=False, title='Testing Window') plotter.add_mesh(pyvista.Sphere()) @@ -360,12 +321,11 @@ def test_background_plotting_camera(qtbot): plotter.close() -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") @pytest.mark.parametrize('show_plotter', [ True, False, ]) -def test_background_plotter_export_files(qtbot, tmpdir, show_plotter): +def test_background_plotter_export_files(qtbot, tmpdir, show_plotter, plotting): # setup filesystem output_dir = str(tmpdir.mkdir("tmpdir")) assert os.path.isdir(output_dir) @@ -414,12 +374,11 @@ def test_background_plotter_export_files(qtbot, tmpdir, show_plotter): assert os.path.isfile(filename) -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") @pytest.mark.parametrize('show_plotter', [ True, False, ]) -def test_background_plotter_export_vtkjs(qtbot, tmpdir, show_plotter): +def test_background_plotter_export_vtkjs(qtbot, tmpdir, show_plotter, plotting): # setup filesystem output_dir = str(tmpdir.mkdir("tmpdir")) assert os.path.isdir(output_dir) @@ -468,8 +427,7 @@ def test_background_plotter_export_vtkjs(qtbot, tmpdir, show_plotter): assert os.path.isfile(filename + '.vtkjs') -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") -def test_background_plotting_orbit(qtbot): +def test_background_plotting_orbit(qtbot, plotting): plotter = BackgroundPlotter(off_screen=False, title='Testing Window') plotter.add_mesh(pyvista.Sphere()) # perform the orbit: @@ -477,8 +435,7 @@ def test_background_plotting_orbit(qtbot): plotter.close() -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") -def test_background_plotting_toolbar(qtbot): +def test_background_plotting_toolbar(qtbot, plotting): with pytest.raises(TypeError, match='toolbar'): BackgroundPlotter(off_screen=False, toolbar="foo") @@ -511,8 +468,7 @@ def test_background_plotting_toolbar(qtbot): plotter.close() -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") -def test_background_plotting_menu_bar(qtbot): +def test_background_plotting_menu_bar(qtbot, plotting): with pytest.raises(TypeError, match='menu_bar'): BackgroundPlotter(off_screen=False, menu_bar="foo") @@ -556,8 +512,7 @@ def test_background_plotting_menu_bar(qtbot): assert plotter._last_update_time == -np.inf -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") -def test_background_plotting_add_callback(qtbot, monkeypatch): +def test_background_plotting_add_callback(qtbot, monkeypatch, plotting): class CallBack(object): def __init__(self, sphere): self.sphere = weakref.ref(sphere) @@ -633,7 +588,6 @@ def update_app_icon(slf): assert not callback_timer.isActive() # window stops the callback -@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting") @pytest.mark.parametrize('close_event', [ "plotter_close", "window_close", @@ -645,7 +599,7 @@ def update_app_icon(slf): True, False, ]) -def test_background_plotting_close(qtbot, close_event, empty_scene): +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 From 06f3fe97dc38d1f2d88c9a581959898628b57650 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:42:34 -0400 Subject: [PATCH 09/35] FIX: Old VTK --- pyvistaqt/plotting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 596fe7f0..bb4f930e 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -886,4 +886,7 @@ def SetSize(self, *args, **kwargs): pass def ConfigureEvent(self, *args, **kwargs): - pass \ No newline at end of file + pass + + def SetEventInformationFlipY(self, *args, **kwargs): + pass From d5a71013a74281829c39ff6ad5124f7f1fea62c7 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:48:23 -0400 Subject: [PATCH 10/35] FIX: Simplify --- pyvistaqt/plotting.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index bb4f930e..1cf8fa52 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -867,26 +867,9 @@ def _create_menu_bar(parent: Any) -> QMenuBar: class _FakeEventHandler(): - def SetDPI(self, dpi): - pass - - def EnterEvent(self): - pass - - def MouseMoveEvent(self): - pass - - def LeaveEvent(self): - pass - - def SetEventInformation(self, *args, **kwargs): - pass - def SetSize(self, *args, **kwargs): + def _noop(self, *args, **kwargs): pass - def ConfigureEvent(self, *args, **kwargs): - pass - - def SetEventInformationFlipY(self, *args, **kwargs): - pass + SetDPI = EnterEvent = MouseMoveEvent = LeaveEvent = SetSize = _noop + SetEventInformation = ConfigureEvent = SetEventInformationFlipY = _noop From c59bf2e85c7f907cd7125c9823114fa98ca39314 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:49:01 -0400 Subject: [PATCH 11/35] FIX: Missing --- tests/conftest.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..2b41869f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,60 @@ +import gc +import os +import pytest +import pyvista +from pyvista.plotting import system_supports_plotting + + +NO_PLOTTING = not system_supports_plotting() +GC_TEST = os.getenv('GC_TEST', '').lower() == 'true' + + +def pytest_collection_finish(session): + from py.io import TerminalWriter + writer = TerminalWriter() + writer.line( + f'{"Excluding" if NO_PLOTTING else "Including"} plotting tests ' + f'(NO_PLOTTING={NO_PLOTTING})') + writer.line( + f'{"Including" if GC_TEST else "Excluding"} garbage collection tests ' + f'(GC_TEST={GC_TEST})') + + +# Adapted from PyVista +def _is_vtk(obj): + try: + return obj.__class__.__name__.startswith('vtk') + except Exception: # old Python sometimes no __class__.__name__ + return False + + +@pytest.fixture(autouse=True) +def check_gc(request): + """Ensure that all VTK objects are garbage-collected by Python.""" + if 'test_ipython' in request.node.name: # XXX this keeps a ref + yield + return + # We need https://github.com/pyvista/pyvista/pull/958 to actually run + # this test. Eventually we should use LooseVersion, but as of 2020/10/22 + # 0.26.1 is the latest PyPi version and on master the version is weirdly + # 0.26.0 (as opposed to 0.26.2.dev0 or 0.27.dev0) so we can't. So for now + # let's use an env var (GC_TEST) instead of: + # if LooseVersion(pyvista.__version__) < LooseVersion('0.26.2'): + if os.getenv('GC_TEST', '').lower() != 'true': + yield + return + before = set(id(o) for o in gc.get_objects() if _is_vtk(o)) + yield + pyvista.close_all() + gc.collect() + after = [o for o in gc.get_objects() if _is_vtk(o) and id(o) not in before] + after = sorted(o.__class__.__name__ for o in after) + assert len(after) == 0, 'Not all objects GCed:\n' + '\n'.join(after) + + +@pytest.fixture() +def plotting(): + """Require plotting.""" + if NO_PLOTTING: + pytest.skip(NO_PLOTTING, reason="Requires system to support plotting") + yield From 9f8bb888d79a89799cdd7676102897cbc562129e Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 07:58:18 -0400 Subject: [PATCH 12/35] FIX: Pre --- .ci/azure-pipelines.yml | 5 +++++ tests/conftest.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 1ce9bd71..892f9e1c 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -114,6 +114,8 @@ jobs: python.version: '3.6' Python37: python.version: '3.7' + GC_TEST: 'true' + PV_PRE: 'true' steps: - task: UsePythonVersion@0 @@ -124,6 +126,9 @@ jobs: - script: | pip install wheel --upgrade python setup.py bdist_wheel + if [ $(PV_PRE) == "true" ]; then + pip install https://github.com/larsoner/pyvista/zipball/mem + fi pip install dist/pyvistaqt*.whl displayName: Build wheel and install pyvistaqt diff --git a/tests/conftest.py b/tests/conftest.py index 2b41869f..fbfd2f5b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,10 +14,10 @@ def pytest_collection_finish(session): writer = TerminalWriter() writer.line( f'{"Excluding" if NO_PLOTTING else "Including"} plotting tests ' - f'(NO_PLOTTING={NO_PLOTTING})') + f'(ALLOW_PLOTTING={os.getenv("ALLOW_PLOTTING", "")})') writer.line( f'{"Including" if GC_TEST else "Excluding"} garbage collection tests ' - f'(GC_TEST={GC_TEST})') + f'(GC_TEST={os.getenv("GC_TEST", "")})') # Adapted from PyVista From 515627c63d100152a92d5a0592634b6aa098380f Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 09:35:14 -0400 Subject: [PATCH 13/35] STY: Flake --- pyvistaqt/editor.py | 9 +++++---- pyvistaqt/plotting.py | 11 +++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyvistaqt/editor.py b/pyvistaqt/editor.py index ea1db658..7464c4a2 100644 --- a/pyvistaqt/editor.py +++ b/pyvistaqt/editor.py @@ -100,16 +100,17 @@ def _axes_callback(state: bool) -> None: def _get_actor_widget(actor: vtk.vtkActor) -> QWidget: + # pylint: disable=unnecessary-lambda widget = QWidget() layout = QVBoxLayout() prop = actor.GetProperty() # visibility - sv = weakref.ref(actor.SetVisibility) + set_vis = weakref.ref(actor.SetVisibility) visibility = QCheckBox("Visibility") visibility.setChecked(actor.GetVisibility()) - visibility.toggled.connect(lambda v: sv()(v)) + visibility.toggled.connect(lambda v: set_vis()(v)) layout.addWidget(visibility) if prop is not None: @@ -118,8 +119,8 @@ def _get_actor_widget(actor: vtk.vtkActor) -> QWidget: opacity = QDoubleSpinBox() opacity.setMaximum(1.0) opacity.setValue(prop.GetOpacity()) - so = weakref.ref(prop.SetOpacity) - opacity.valueChanged.connect(lambda v: so()(v)) + set_opacity = weakref.ref(prop.SetOpacity) + opacity.valueChanged.connect(lambda v: set_opacity()(v)) tmp_layout.addWidget(QLabel("Opacity")) tmp_layout.addWidget(opacity) layout.addLayout(tmp_layout) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 1cf8fa52..68947f7c 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -231,7 +231,7 @@ def __init__( self.ren_win.AddRenderer(renderer) self.render_signal.connect(self._render) - self.key_press_event_signal.connect(super(QtInteractor, self).key_press_event) + self.key_press_event_signal.connect(super().key_press_event) self.background_color = rcParams["background"] if self.title: @@ -620,9 +620,7 @@ def __init__( self.app_window.setCentralWidget(self.frame) vlayout = QVBoxLayout() self.frame.setLayout(vlayout) - super(BackgroundPlotter, self).__init__( - parent=self.frame, off_screen=off_screen, **kwargs - ) + super().__init__(parent=self.frame, off_screen=off_screen, **kwargs) vlayout.addWidget(self) self.app_window.grabGesture(QtCore.Qt.PinchGesture) self.app_window.signal_gesture.connect(self.gesture_event) @@ -676,7 +674,7 @@ def reset_key_events(self) -> None: Handles closing configuration for q-key. """ - super(BackgroundPlotter, self).reset_key_events() + super().reset_key_events() if self.allow_quit_keypress: # pylint: disable=unnecessary-lambda self.add_key_event("q", lambda: self.close()) @@ -812,7 +810,7 @@ def __del__(self) -> None: # pragma: no cover self.app_window.close() # Qt LeaveEvent requires _Iren so we use _FakeIren instead of None # to resolve the ref to vtkGenericRenderWindowInteractor - self._Iren = _FakeEventHandler() + self._Iren = _FakeEventHandler() # pylint: disable=invalid-name,attribute-defined-outside-init for key in ('_RenderWindow', 'renderer'): try: setattr(self, key, None) @@ -867,6 +865,7 @@ def _create_menu_bar(parent: Any) -> QMenuBar: class _FakeEventHandler(): + # pylint: disable=too-few-public-methods def _noop(self, *args, **kwargs): pass From f999e8c35bd0dbcf1d876f87a9ddf7bc158bf627 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 09:53:16 -0400 Subject: [PATCH 14/35] FIX: Black --- pyvistaqt/plotting.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 68947f7c..22eb4671 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -810,8 +810,10 @@ def __del__(self) -> None: # pragma: no cover self.app_window.close() # Qt LeaveEvent requires _Iren so we use _FakeIren instead of None # to resolve the ref to vtkGenericRenderWindowInteractor - self._Iren = _FakeEventHandler() # pylint: disable=invalid-name,attribute-defined-outside-init - for key in ('_RenderWindow', 'renderer'): + self._Iren = ( + _FakeEventHandler() + ) # pylint: disable=invalid-name,attribute-defined-outside-init + for key in ("_RenderWindow", "renderer"): try: setattr(self, key, None) except AttributeError: @@ -864,7 +866,7 @@ def _create_menu_bar(parent: Any) -> QMenuBar: return menu_bar -class _FakeEventHandler(): +class _FakeEventHandler: # pylint: disable=too-few-public-methods def _noop(self, *args, **kwargs): From 795e6420841dce4d386d53a3d0a87cba18140944 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 09:54:57 -0400 Subject: [PATCH 15/35] FIX: Sty --- pyvistaqt/plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 22eb4671..d4bccc94 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -810,9 +810,9 @@ def __del__(self) -> None: # pragma: no cover self.app_window.close() # Qt LeaveEvent requires _Iren so we use _FakeIren instead of None # to resolve the ref to vtkGenericRenderWindowInteractor - self._Iren = ( + self._Iren = ( # pylint: disable=invalid-name,attribute-defined-outside-init _FakeEventHandler() - ) # pylint: disable=invalid-name,attribute-defined-outside-init + ) for key in ("_RenderWindow", "renderer"): try: setattr(self, key, None) From 76acb42b633f1c8b9d0afd3d382e5b7b17159245 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 10:21:13 -0400 Subject: [PATCH 16/35] STY: More --- pyvistaqt/editor.py | 21 ++++++++++++++++----- pyvistaqt/plotting.py | 6 +++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pyvistaqt/editor.py b/pyvistaqt/editor.py index 7464c4a2..0a9af039 100644 --- a/pyvistaqt/editor.py +++ b/pyvistaqt/editor.py @@ -100,17 +100,22 @@ def _axes_callback(state: bool) -> None: def _get_actor_widget(actor: vtk.vtkActor) -> QWidget: - # pylint: disable=unnecessary-lambda widget = QWidget() layout = QVBoxLayout() prop = actor.GetProperty() # visibility - set_vis = weakref.ref(actor.SetVisibility) + set_vis_ref = weakref.ref(actor.SetVisibility) + + def _set_vis(visibility: bool) -> None: + set_vis = set_vis_ref() + if set_vis is not None: + set_vis(visibility) + visibility = QCheckBox("Visibility") visibility.setChecked(actor.GetVisibility()) - visibility.toggled.connect(lambda v: set_vis()(v)) + visibility.toggled.connect(_set_vis) layout.addWidget(visibility) if prop is not None: @@ -119,8 +124,14 @@ def _get_actor_widget(actor: vtk.vtkActor) -> QWidget: opacity = QDoubleSpinBox() opacity.setMaximum(1.0) opacity.setValue(prop.GetOpacity()) - set_opacity = weakref.ref(prop.SetOpacity) - opacity.valueChanged.connect(lambda v: set_opacity()(v)) + set_opacity_ref = weakref.ref(prop.SetOpacity) + + def _set_opacity(opacity: float) -> None: + set_opacity = set_opacity_ref() + if set_opacity is not None: + set_opacity(opacity) + + opacity.valueChanged.connect(_set_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 d4bccc94..ca39a441 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -723,12 +723,12 @@ def update_app_icon(self) -> None: # Update trackers self._last_window_size = self.window_size - def set_icon(self, img: np.ndarray) -> None: + def set_icon(self, img: Union[np.ndarray, str]) -> None: """Set the icon image. Parameters ---------- - img : shape (w, h, c) | str + img : ndarray, shape (w, h, c) | str The image. Should be uint8 and square (w == h). Can have 3 or 4 color/alpha channels (``c``). Can also be a string path that QIcon can load. @@ -869,7 +869,7 @@ def _create_menu_bar(parent: Any) -> QMenuBar: class _FakeEventHandler: # pylint: disable=too-few-public-methods - def _noop(self, *args, **kwargs): + def _noop(self, *args: tuple, **kwargs: dict) -> None: pass SetDPI = EnterEvent = MouseMoveEvent = LeaveEvent = SetSize = _noop From 85eee8df044d2768504e6c375e8c7bab8e8d65b5 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 23 Oct 2020 11:07:36 -0400 Subject: [PATCH 17/35] FIX: Just use master --- .ci/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 892f9e1c..aca36bf1 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -127,7 +127,7 @@ jobs: pip install wheel --upgrade python setup.py bdist_wheel if [ $(PV_PRE) == "true" ]; then - pip install https://github.com/larsoner/pyvista/zipball/mem + pip install https://github.com/pyvista/pyvista/zipball/master fi pip install dist/pyvistaqt*.whl displayName: Build wheel and install pyvistaqt From 936e7da6e1a4a8341833cd838f4d7d2d355a047c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:26:33 -0500 Subject: [PATCH 18/35] FIX: Hopefully --- .ci/azure-pipelines.yml | 5 --- pytest.ini | 2 ++ pyvistaqt/editor.py | 17 ++++++--- pyvistaqt/plotting.py | 22 ++++++------ tests/conftest.py | 78 +++++++++++++++++++++++++++++------------ tests/test_plotting.py | 32 +++++++++++------ 6 files changed, 104 insertions(+), 52 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 3d5b6f68..c4294cd6 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -123,8 +123,6 @@ stages: TWINE_USERNAME: $(twine.username) TWINE_PASSWORD: $(twine.password) TWINE_REPOSITORY_URL: "https://upload.pypi.org/legacy/" - GC_TEST: 'true' - PV_PRE: 'true' # don't run job on no-ci builds condition: not(contains(variables['System.PullRequest.SourceBranch'], 'no-ci')) @@ -132,9 +130,6 @@ stages: - script: | pip install wheel --upgrade python setup.py bdist_wheel - if [ $(PV_PRE) == "true" ]; then - pip install https://github.com/pyvista/pyvista/zipball/master - fi pip install dist/pyvistaqt*.whl displayName: Build wheel and install pyvistaqt diff --git a/pytest.ini b/pytest.ini index 52f3c7f5..30a995b5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,7 @@ [pytest] junit_family=legacy +addopts = + --durations=10 -ra --cov-report= --tb=short filterwarnings = error ignore::ResourceWarning diff --git a/pyvistaqt/editor.py b/pyvistaqt/editor.py index bc49620b..2b118ba2 100644 --- a/pyvistaqt/editor.py +++ b/pyvistaqt/editor.py @@ -29,6 +29,7 @@ def __init__(self, parent: MainWindow, renderers: List[Renderer]) -> None: """Initialize the Editor.""" super().__init__(parent=parent) self.renderers = renderers + del renderers self.tree_widget = QTreeWidget() self.tree_widget.setHeaderHidden(True) @@ -79,19 +80,25 @@ def toggle(self) -> None: def _get_renderer_widget(renderer: Renderer) -> QWidget: widget = QWidget() layout = QVBoxLayout() + axes = QCheckBox("Axes") + if hasattr(renderer, "axes_widget"): + axes.setChecked(renderer.axes_widget.GetEnabled()) + else: + axes.setChecked(False) + + renderer_ref = weakref.ref(renderer) + del renderer # axes def _axes_callback(state: bool) -> None: + renderer = renderer_ref() + if renderer is None: + return if state: renderer.show_axes() else: renderer.hide_axes() - axes = QCheckBox("Axes") - if hasattr(renderer, "axes_widget"): - axes.setChecked(renderer.axes_widget.GetEnabled()) - else: - axes.setChecked(False) axes.toggled.connect(_axes_callback) layout.addWidget(axes) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 000de4b0..7eb1f2ce 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -45,6 +45,7 @@ import platform import time import warnings +import weakref from functools import wraps from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union @@ -400,6 +401,16 @@ def close(self) -> None: self.render_timer.stop() BasePlotter.close(self) QVTKRenderWindowInteractor.close(self) + # Qt LeaveEvent requires _Iren so we use _FakeIren instead of None + # to resolve the ref to vtkGenericRenderWindowInteractor + self._Iren = ( # pylint: disable=invalid-name,attribute-defined-outside-init + _FakeEventHandler() + ) + for key in ("_RenderWindow", "renderer"): + try: + setattr(self, key, None) + except AttributeError: + pass class BackgroundPlotter(QtInteractor): @@ -738,16 +749,6 @@ def window_size(self, window_size: QSize) -> None: def __del__(self) -> None: # pragma: no cover """Delete the qt plotter.""" self.close() - # Qt LeaveEvent requires _Iren so we use _FakeIren instead of None - # to resolve the ref to vtkGenericRenderWindowInteractor - self._Iren = ( # pylint: disable=invalid-name,attribute-defined-outside-init - _FakeEventHandler() - ) - for key in ("_RenderWindow", "renderer"): - try: - setattr(self, key, None) - except AttributeError: - pass def add_callback( self, func: Callable, interval: int = 1000, count: Optional[int] = None @@ -1033,3 +1034,4 @@ def _noop(self, *args: tuple, **kwargs: dict) -> None: SetDPI = EnterEvent = MouseMoveEvent = LeaveEvent = SetSize = _noop SetEventInformation = ConfigureEvent = SetEventInformationFlipY = _noop + KeyReleaseEvent = CharEvent = _noop diff --git a/tests/conftest.py b/tests/conftest.py index 6e7c962a..f96eabc7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,24 +1,21 @@ import gc -import os -from packaging.version import Version +import sys +import importlib +import inspect + import pytest import pyvista from pyvista.plotting import system_supports_plotting import pyvistaqt NO_PLOTTING = not system_supports_plotting() -GC_TEST = Version(pyvista.__version__) >= Version('0.35') -def pytest_collection_finish(session): - from py.io import TerminalWriter - writer = TerminalWriter() - writer.line( - f'{"Excluding" if NO_PLOTTING else "Including"} plotting tests ' - f'(ALLOW_PLOTTING={os.getenv("ALLOW_PLOTTING", "")})') - writer.line( - f'{"Including" if GC_TEST else "Excluding"} garbage collection tests ' - f'(GC_TEST={os.getenv("GC_TEST", "")})') +def pytest_configure(config): + """Configure pytest options.""" + # Fixtures + for fixture in ('check_gc',): + config.addinivalue_line('usefixtures', fixture) # Adapted from PyVista @@ -44,22 +41,53 @@ def check_gc(request): if 'test_ipython' in request.node.name: # XXX this keeps a ref yield return - # We need https://github.com/pyvista/pyvista/pull/958 to actually run - # this test. Eventually we should use LooseVersion, but as of 2020/10/22 - # 0.26.1 is the latest PyPi version and on master the version is weirdly - # 0.26.0 (as opposed to 0.26.2.dev0 or 0.27.dev0) so we can't. So for now - # let's use an env var (GC_TEST) instead of: - # if LooseVersion(pyvista.__version__) < LooseVersion('0.26.2'): - if os.getenv('GC_TEST', '').lower() != 'true': + try: + from qtpy import API_NAME + except Exception: + API_NAME = '' + if 'allow_bad_gc' in request.fixturenames and API_NAME == 'PySide6': yield return + gc.collect() before = set(id(o) for o in gc.get_objects() if _is_vtk(o)) yield pyvista.close_all() gc.collect() - after = [o for o in gc.get_objects() if _is_vtk(o) and id(o) not in before] - after = sorted(o.__class__.__name__ for o in after) - assert len(after) == 0, 'Not all objects GCed:\n' + '\n'.join(after) + after = [ + o + for o in gc.get_objects() + if _is_vtk(o) and id(o) not in before + ] + msg = 'Not all objects GCed:\n' + for obj in after: + cn = obj.__class__.__name__ + cf = inspect.currentframe() + referrers = [ + v for v in gc.get_referrers(obj) + if v is not after and v is not cf + ] + del cf + for ri, referrer in enumerate(referrers): + if isinstance(referrer, dict): + for k, v in referrer.items(): + if k is obj: + referrers[ri] = f'dict: d key' + del k, v + break + elif v is obj: + referrers[ri] = f'dict: d[{k!r}]' + #raise RuntimeError(referrers[ri]) + del k, v + break + del k, v + else: + referrers[ri] = f'dict: len={len(referrer)}' + else: + referrers[ri] = repr(referrer) + del ri, referrer + msg += f'{cn}: {referrers}\n' + del cn, referrers + assert len(after) == 0, msg @pytest.fixture() @@ -85,3 +113,9 @@ def no_qt(monkeypatch): if need_reload: importlib.reload(pyvistaqt) assert 'qtpy' in sys.modules + + +@pytest.fixture +def allow_bad_gc(): + """Allow VTK objects not to be cleaned up.""" + pass diff --git a/tests/test_plotting.py b/tests/test_plotting.py index b7754661..44f0c9d6 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -118,13 +118,13 @@ def test_check_type(): def test_mouse_interactions(qtbot): - plotter = BackgroundPlotter() - window = plotter.app_window - interactor = plotter.interactor - qtbot.addWidget(window) - point = QPoint(0, 0) - qtbot.mouseMove(interactor, point) - qtbot.mouseClick(interactor, QtCore.Qt.LeftButton) + plotter = BackgroundPlotter(show=True) + #window = plotter.app_window + #interactor = plotter.interactor + #qtbot.addWidget(window) + #point = QPoint(0, 0) + #qtbot.mouseMove(interactor, point) + #qtbot.mouseClick(interactor, QtCore.Qt.LeftButton) plotter.close() @@ -198,7 +198,13 @@ def test_counter(qtbot): assert counter.count == 0 -def test_editor(qtbot, plotting): +@pytest.mark.parametrize('border', (True, False)) +def test_subplot_gc(border, allow_bad_gc): + BackgroundPlotter(shape=(2, 1), update_app_icon=False, border=border) + + + +def test_editor(qtbot, plotting, allow_bad_gc): # test editor=False plotter = BackgroundPlotter(editor=False, off_screen=False) qtbot.addWidget(plotter.app_window) @@ -587,9 +593,10 @@ def test_background_plotting_toolbar(qtbot, plotting): plotter.close() +# TODO: _render_passes not GC'ed @pytest.mark.skipif( platform.system() == 'Windows', reason='Segfaults on Windows') -def test_background_plotting_menu_bar(qtbot, plotting): +def test_background_plotting_menu_bar(qtbot, plotting, allow_bad_gc): with pytest.raises(TypeError, match='menu_bar'): BackgroundPlotter(off_screen=False, menu_bar="foo") @@ -770,6 +777,10 @@ def update_app_icon(slf): assert not callback_timer.isActive() # window stops the callback +# TODO: Need to fix this allow_bad_gc: +# - the actors are not cleaned up in the non-empty scene case +# - the q_key_press leaves a lingering vtkUnsignedCharArray referred to by +# a "managedbuffer" object @pytest.mark.parametrize('close_event', [ "plotter_close", "window_close", @@ -781,7 +792,8 @@ def update_app_icon(slf): True, False, ]) -def test_background_plotting_close(qtbot, close_event, empty_scene, plotting): +def test_background_plotting_close(qtbot, close_event, empty_scene, plotting, + allow_bad_gc): from pyvista.plotting.plotting import _ALL_PLOTTERS, close_all close_all() # this is necessary to test _ALL_PLOTTERS assert len(_ALL_PLOTTERS) == 0 From dbab0dec366b8a994e32d31a31c59c09d9ffd8f4 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:27:25 -0500 Subject: [PATCH 19/35] FIX: Placeholder --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index f96eabc7..9ccb0837 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -71,7 +71,7 @@ def check_gc(request): if isinstance(referrer, dict): for k, v in referrer.items(): if k is obj: - referrers[ri] = f'dict: d key' + referrers[ri] = 'dict: d key' del k, v break elif v is obj: From 7a44256053adf0f6bf27fb256f9df58c39223ab5 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:29:15 -0500 Subject: [PATCH 20/35] FIX: Rebase --- .ci/azure-pipelines.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index c4294cd6..933fee00 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -81,12 +81,6 @@ stages: versionSpec: '$(python.version)' displayName: 'Use Python $(python.version)' - - script: | - pip install wheel --upgrade - python setup.py bdist_wheel - pip install dist/pyvistaqt*.whl - displayName: Build wheel and install pyvistaqt - - script: | .ci/setup_headless_display.sh displayName: Install headless display From c009a71b26a72da87c8c55648d81c4b36863023a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:30:22 -0500 Subject: [PATCH 21/35] FIX: Order --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9ccb0837..264a89d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ import gc -import sys import importlib import inspect +import sys import pytest import pyvista From 39ca333eaff51b55f2a0c36c9cb8b9c5905bd9c6 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:31:20 -0500 Subject: [PATCH 22/35] FIX: More --- tests/test_plotting.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 44f0c9d6..139fc706 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -119,12 +119,12 @@ def test_check_type(): def test_mouse_interactions(qtbot): plotter = BackgroundPlotter(show=True) - #window = plotter.app_window - #interactor = plotter.interactor - #qtbot.addWidget(window) - #point = QPoint(0, 0) - #qtbot.mouseMove(interactor, point) - #qtbot.mouseClick(interactor, QtCore.Qt.LeftButton) + window = plotter.app_window + interactor = plotter.interactor + qtbot.addWidget(window) + point = QPoint(0, 0) + qtbot.mouseMove(interactor, point) + qtbot.mouseClick(interactor, QtCore.Qt.LeftButton) plotter.close() @@ -203,7 +203,6 @@ def test_subplot_gc(border, allow_bad_gc): BackgroundPlotter(shape=(2, 1), update_app_icon=False, border=border) - def test_editor(qtbot, plotting, allow_bad_gc): # test editor=False plotter = BackgroundPlotter(editor=False, off_screen=False) From 7cdde115c20b7d1b64ec926e616b313a2bf584e1 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:33:27 -0500 Subject: [PATCH 23/35] FIX: Flake --- pyvistaqt/plotting.py | 1 - tests/test_plotting.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 7eb1f2ce..74982b11 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -45,7 +45,6 @@ import platform import time import warnings -import weakref from functools import wraps from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 139fc706..2f690d86 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -118,7 +118,7 @@ def test_check_type(): def test_mouse_interactions(qtbot): - plotter = BackgroundPlotter(show=True) + plotter = BackgroundPlotter() window = plotter.app_window interactor = plotter.interactor qtbot.addWidget(window) @@ -198,6 +198,7 @@ def test_counter(qtbot): assert counter.count == 0 +# TODO: Fix gc on PySide6 @pytest.mark.parametrize('border', (True, False)) def test_subplot_gc(border, allow_bad_gc): BackgroundPlotter(shape=(2, 1), update_app_icon=False, border=border) From 78d119c0c0d2580e1d4b1e0fa2060e13bd47f421 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:47:07 -0500 Subject: [PATCH 24/35] FIX: Always bad --- tests/conftest.py | 8 +++++++- tests/test_plotting.py | 15 +++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 264a89d5..dd276ec0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,9 @@ def pytest_configure(config): # Fixtures for fixture in ('check_gc',): config.addinivalue_line('usefixtures', fixture) + # Markers + for marker in ('allow_bad_gc', 'allow_bad_gc_pyside6'): + config.addinivalue_line('markers', marker) # Adapted from PyVista @@ -45,7 +48,10 @@ def check_gc(request): from qtpy import API_NAME except Exception: API_NAME = '' - if 'allow_bad_gc' in request.fixturenames and API_NAME == 'PySide6': + if 'allow_bad_gc' in request.node.iter_markers(): + yield + return + if 'allow_bad_gc_pyside6' in request.node.iter_markers() and API_NAME == 'PySide6': yield return gc.collect() diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 2f690d86..6fa2ffaa 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -200,11 +200,13 @@ def test_counter(qtbot): # TODO: Fix gc on PySide6 @pytest.mark.parametrize('border', (True, False)) -def test_subplot_gc(border, allow_bad_gc): +@pytest.mark.allow_bad_gc_pyside6 +def test_subplot_gc(border): BackgroundPlotter(shape=(2, 1), update_app_icon=False, border=border) -def test_editor(qtbot, plotting, allow_bad_gc): +@pytest.mark.allow_bad_gc_pyside6 +def test_editor(qtbot, plotting): # test editor=False plotter = BackgroundPlotter(editor=False, off_screen=False) qtbot.addWidget(plotter.app_window) @@ -594,9 +596,10 @@ def test_background_plotting_toolbar(qtbot, plotting): # TODO: _render_passes not GC'ed +@pytest.mark.allow_bad_gc_pyside6 @pytest.mark.skipif( platform.system() == 'Windows', reason='Segfaults on Windows') -def test_background_plotting_menu_bar(qtbot, plotting, allow_bad_gc): +def test_background_plotting_menu_bar(qtbot, plotting): with pytest.raises(TypeError, match='menu_bar'): BackgroundPlotter(off_screen=False, menu_bar="foo") @@ -781,10 +784,11 @@ def update_app_icon(slf): # - the actors are not cleaned up in the non-empty scene case # - the q_key_press leaves a lingering vtkUnsignedCharArray referred to by # a "managedbuffer" object +@pytest.mark.allow_bad_gc_pyside6 @pytest.mark.parametrize('close_event', [ "plotter_close", "window_close", - "q_key_press", + pytest.param("q_key_press", marks=pytest.mark.allow_bad_gc), "menu_exit", "del_finalizer", ]) @@ -792,8 +796,7 @@ def update_app_icon(slf): True, False, ]) -def test_background_plotting_close(qtbot, close_event, empty_scene, plotting, - allow_bad_gc): +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 From a52db70fccd9473670bc40e9c0b86f86b1495f30 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:48:31 -0500 Subject: [PATCH 25/35] FIX: Fixture --- tests/conftest.py | 10 ++-------- tests/test_plotting.py | 8 ++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index dd276ec0..5fe53f76 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,7 +17,7 @@ def pytest_configure(config): for fixture in ('check_gc',): config.addinivalue_line('usefixtures', fixture) # Markers - for marker in ('allow_bad_gc', 'allow_bad_gc_pyside6'): + for marker in ('allow_bad_gc', 'allow_bad_gc_pyside'): config.addinivalue_line('markers', marker) @@ -51,7 +51,7 @@ def check_gc(request): if 'allow_bad_gc' in request.node.iter_markers(): yield return - if 'allow_bad_gc_pyside6' in request.node.iter_markers() and API_NAME == 'PySide6': + if 'allow_bad_gc_pyside' in request.node.iter_markers() and API_NAME.lower().startswith('pyside'): yield return gc.collect() @@ -119,9 +119,3 @@ def no_qt(monkeypatch): if need_reload: importlib.reload(pyvistaqt) assert 'qtpy' in sys.modules - - -@pytest.fixture -def allow_bad_gc(): - """Allow VTK objects not to be cleaned up.""" - pass diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 6fa2ffaa..ed46c9ea 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -200,12 +200,12 @@ def test_counter(qtbot): # TODO: Fix gc on PySide6 @pytest.mark.parametrize('border', (True, False)) -@pytest.mark.allow_bad_gc_pyside6 +@pytest.mark.allow_bad_gc_pyside def test_subplot_gc(border): BackgroundPlotter(shape=(2, 1), update_app_icon=False, border=border) -@pytest.mark.allow_bad_gc_pyside6 +@pytest.mark.allow_bad_gc_pyside def test_editor(qtbot, plotting): # test editor=False plotter = BackgroundPlotter(editor=False, off_screen=False) @@ -596,7 +596,7 @@ def test_background_plotting_toolbar(qtbot, plotting): # TODO: _render_passes not GC'ed -@pytest.mark.allow_bad_gc_pyside6 +@pytest.mark.allow_bad_gc_pyside @pytest.mark.skipif( platform.system() == 'Windows', reason='Segfaults on Windows') def test_background_plotting_menu_bar(qtbot, plotting): @@ -784,7 +784,7 @@ def update_app_icon(slf): # - the actors are not cleaned up in the non-empty scene case # - the q_key_press leaves a lingering vtkUnsignedCharArray referred to by # a "managedbuffer" object -@pytest.mark.allow_bad_gc_pyside6 +@pytest.mark.allow_bad_gc_pyside @pytest.mark.parametrize('close_event', [ "plotter_close", "window_close", From 3339845232a86455e769826c8cc2557a1ba20b50 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 14:54:39 -0500 Subject: [PATCH 26/35] FIX: Use name --- tests/conftest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5fe53f76..3c3fdefe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,10 +48,11 @@ def check_gc(request): from qtpy import API_NAME except Exception: API_NAME = '' - if 'allow_bad_gc' in request.node.iter_markers(): + marks = set(mark.name for mark in request.node.iter_markers()) + if 'allow_bad_gc' in marks: yield return - if 'allow_bad_gc_pyside' in request.node.iter_markers() and API_NAME.lower().startswith('pyside'): + if 'allow_bad_gc_pyside' in marks and API_NAME.lower().startswith('pyside'): yield return gc.collect() From f363aae2fd9eed1cb9a6148177a35285b962f5f6 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:03:44 -0500 Subject: [PATCH 27/35] FIX: Dynamic --- tests/conftest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 3c3fdefe..3e959307 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -97,6 +97,13 @@ def check_gc(request): assert len(after) == 0, msg +def pytest_collection_modifyitems(items): + for item in items: + if "test_background_plotting_close" in item.nodeid: + if Version(pyvista.__version__) < Version('0.37'): + item.add_marker(pytest.mark.allow_bad_gc) + + @pytest.fixture() def plotting(): """Require plotting.""" From c6e3b28ade2824ecca31dd3c1d4e9211cba2260a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:04:01 -0500 Subject: [PATCH 28/35] FIX: Pack --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 3e959307..d5e95740 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,8 @@ import inspect import sys +from packaging.version import Version + import pytest import pyvista from pyvista.plotting import system_supports_plotting From dcb59c9b0ff2ce028346638d0893dd2e8a9be8f4 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:06:17 -0500 Subject: [PATCH 29/35] FIX: Better --- tests/conftest.py | 7 ------- tests/test_plotting.py | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d5e95740..d709febf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -99,13 +99,6 @@ def check_gc(request): assert len(after) == 0, msg -def pytest_collection_modifyitems(items): - for item in items: - if "test_background_plotting_close" in item.nodeid: - if Version(pyvista.__version__) < Version('0.37'): - item.add_marker(pytest.mark.allow_bad_gc) - - @pytest.fixture() def plotting(): """Require plotting.""" diff --git a/tests/test_plotting.py b/tests/test_plotting.py index ed46c9ea..a333dd6e 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -780,10 +780,18 @@ def update_app_icon(slf): assert not callback_timer.isActive() # window stops the callback +def allow_bad_gc_old_pyvista(func): + if Version(pyvista.__version__) < Version('0.37'): + return pytest.mark.allow_bad_gc(func) + else: + return func + + # TODO: Need to fix this allow_bad_gc: # - the actors are not cleaned up in the non-empty scene case # - the q_key_press leaves a lingering vtkUnsignedCharArray referred to by # a "managedbuffer" object +@allow_bad_gc_old_pyvista @pytest.mark.allow_bad_gc_pyside @pytest.mark.parametrize('close_event', [ "plotter_close", From 166b3a79bd78e20f5bc5c991d060957da33ec952 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:07:10 -0500 Subject: [PATCH 30/35] FIX: Revert --- .ci/azure-pipelines.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 933fee00..0f841fe8 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -81,6 +81,12 @@ stages: versionSpec: '$(python.version)' displayName: 'Use Python $(python.version)' + - script: | + pip install wheel --upgrade + python setup.py bdist_wheel + pip install dist/pyvistaqt*.whl + displayName: Build wheel and install pyvistaqt + - script: | .ci/setup_headless_display.sh displayName: Install headless display @@ -121,12 +127,6 @@ stages: # don't run job on no-ci builds condition: not(contains(variables['System.PullRequest.SourceBranch'], 'no-ci')) - - script: | - pip install wheel --upgrade - python setup.py bdist_wheel - pip install dist/pyvistaqt*.whl - displayName: Build wheel and install pyvistaqt - # DESCRIPTION: Core API and doc string testing across VTK versions using conda - job: LinuxConda pool: From d33afea5e229195b5b8b5751f337b3daad185e4a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:07:30 -0500 Subject: [PATCH 31/35] FIX: Line --- .ci/azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 0f841fe8..bfa79ab4 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -127,6 +127,7 @@ stages: # don't run job on no-ci builds condition: not(contains(variables['System.PullRequest.SourceBranch'], 'no-ci')) + # DESCRIPTION: Core API and doc string testing across VTK versions using conda - job: LinuxConda pool: From 7b5baa97444c6d182c75a59aeb8755227dd56166 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:08:13 -0500 Subject: [PATCH 32/35] STY: Flake --- tests/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d709febf..3c3fdefe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,8 +3,6 @@ import inspect import sys -from packaging.version import Version - import pytest import pyvista from pyvista.plotting import system_supports_plotting From 908330b92dc2a2a13c49e84b53bb85299c99d4b2 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:23:25 -0500 Subject: [PATCH 33/35] FIX: Del --- pyvistaqt/plotting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 74982b11..5552b0db 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -311,7 +311,10 @@ def _render(self, *args: Any, **kwargs: Any) -> BasePlotter.render: @conditional_decorator(threaded, platform.system() == "Darwin") def render(self) -> None: """Override the ``render`` method to handle threading issues.""" - return self.render_signal.emit() + try: + return self.render_signal.emit() + except RuntimeError: # wrapped C/C++ object has been deleted + return None @wraps(BasePlotter.enable) def enable(self) -> None: From a63451795ade7468510c8a638251789c1af9bcfa Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:36:36 -0500 Subject: [PATCH 34/35] FIX: Show --- tests/test_plotting.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index a333dd6e..35fccc50 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -655,7 +655,9 @@ def test_drop_event(tmpdir, qtbot): mesh = pyvista.Cone() mesh.save(filename) assert os.path.isfile(filename) - plotter = BackgroundPlotter(update_app_icon=False, show=True) + plotter = BackgroundPlotter(update_app_icon=False) + with qtbot.wait_exposed(plotter.app_window, timeout=10000): + plotter.app_window.show() point = QPointF(0, 0) data = QMimeData() data.setUrls([QUrl(filename)]) @@ -692,7 +694,9 @@ def test_drag_event(tmpdir): def test_gesture_event(qtbot): - plotter = BackgroundPlotter() + plotter = BackgroundPlotter(update_app_icon=False) + with qtbot.wait_exposed(plotter.app_window, timeout=10000): + plotter.app_window.show() gestures = [QPinchGesture()] event = QGestureEvent(gestures) plotter.gesture_event(event) From 0cd0112c6444ace6e3d39cca2690f8b734bc4ebc Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 6 Jan 2023 15:52:50 -0500 Subject: [PATCH 35/35] FIX: Skip --- pyvistaqt/editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyvistaqt/editor.py b/pyvistaqt/editor.py index 2b118ba2..d828a9e6 100644 --- a/pyvistaqt/editor.py +++ b/pyvistaqt/editor.py @@ -92,7 +92,7 @@ def _get_renderer_widget(renderer: Renderer) -> QWidget: # axes def _axes_callback(state: bool) -> None: renderer = renderer_ref() - if renderer is None: + if renderer is None: # pragma: no cover return if state: renderer.show_axes() @@ -115,7 +115,7 @@ def _get_actor_widget(actor: vtkActor) -> QWidget: # visibility set_vis_ref = weakref.ref(actor.SetVisibility) - def _set_vis(visibility: bool) -> None: + def _set_vis(visibility: bool) -> None: # pragma: no cover set_vis = set_vis_ref() if set_vis is not None: set_vis(visibility) @@ -133,7 +133,7 @@ def _set_vis(visibility: bool) -> None: opacity.setValue(prop.GetOpacity()) set_opacity_ref = weakref.ref(prop.SetOpacity) - def _set_opacity(opacity: float) -> None: + def _set_opacity(opacity: float) -> None: # pragma: no cover set_opacity = set_opacity_ref() if set_opacity is not None: set_opacity(opacity)