init
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
"""Utilities for using pyvista with sphinx-gallery."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
BUILDING_GALLERY_ERROR_MSG = (
|
||||
'pyvista.BUILDING_GALLERY must be set to True in your conf.py to capture '
|
||||
'images within sphinx_gallery or when building documentation using the '
|
||||
'pyvista-plot directive.'
|
||||
)
|
||||
|
||||
|
||||
def _get_sg_image_scraper():
|
||||
"""Return the callable scraper to be used by Sphinx-Gallery.
|
||||
|
||||
It allows PyVista users to just use strings as they already can for
|
||||
'matplotlib' and 'mayavi'. Details on this implementation can be found in
|
||||
`sphinx-gallery/sphinx-gallery/494`_
|
||||
|
||||
This must be imported into the top level namespace of PyVista.
|
||||
|
||||
.. _sphinx-gallery/sphinx-gallery/494: https://github.com/sphinx-gallery/sphinx-gallery/pull/494
|
||||
"""
|
||||
return Scraper()
|
||||
|
||||
|
||||
def html_rst(
|
||||
figure_list,
|
||||
sources_dir,
|
||||
srcsetpaths=None,
|
||||
): # pragma: no cover # numpydoc ignore=PR01,RT01
|
||||
"""Generate reST for viewer with exported scene."""
|
||||
from sphinx_gallery.scrapers import _get_srcset_st # noqa: PLC0415
|
||||
from sphinx_gallery.scrapers import figure_rst # noqa: PLC0415
|
||||
|
||||
if srcsetpaths is None:
|
||||
# this should never happen, but figure_rst is public, so
|
||||
# this has to be a kwarg...
|
||||
srcsetpaths = [{0: fl} for fl in figure_list]
|
||||
|
||||
images_rst = ''
|
||||
for i, hinnames in enumerate(srcsetpaths):
|
||||
srcset = _get_srcset_st(sources_dir, hinnames)
|
||||
if srcset[-5:] == 'vtksz':
|
||||
png_file = figure_list[i][:-5] + 'png'
|
||||
|
||||
indented_firgure_rst = '\n'.join(
|
||||
' ' * 5 + line for line in figure_rst([png_file], sources_dir).split('\n')
|
||||
)
|
||||
images_rst += f"""
|
||||
\n
|
||||
\n
|
||||
.. tab-set::\n
|
||||
\n
|
||||
.. tab-item:: Static Scene\n
|
||||
\n
|
||||
{indented_firgure_rst}
|
||||
\n
|
||||
.. tab-item:: Interactive Scene\n
|
||||
\n
|
||||
.. offlineviewer:: {figure_list[i]}\n\n"""
|
||||
|
||||
else:
|
||||
images_rst += '\n' + figure_rst([figure_list[i]], sources_dir) + '\n\n'
|
||||
|
||||
return images_rst
|
||||
|
||||
|
||||
def _process_events_before_scraping(plotter):
|
||||
"""Process events such as changing the camera or an object before scraping."""
|
||||
if plotter.iren is not None and plotter.iren.initialized:
|
||||
# check for pyvistaqt app which can be specifically bound to pyvista plotter
|
||||
# objects in order to interact with qt, then process the events from qt
|
||||
if hasattr(plotter, 'app') and plotter.app is not None:
|
||||
plotter.app.processEvents()
|
||||
plotter.update()
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['image_path_iterator'])
|
||||
def generate_images(image_path_iterator: Iterator[str], dynamic: bool = False) -> list[str]: # noqa: FBT001, FBT002
|
||||
"""Generate images from the current plotters.
|
||||
|
||||
The file names are taken from the ``image_path_iterator`` iterator.
|
||||
|
||||
A gif will be created if a plotter has a ``_gif_filename`` attribute.
|
||||
Otherwise, depending on the value of ``dynamic``, either a ``.png`` static image
|
||||
or a ``.vtksz`` file will be created.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_path_iterator : Iterator[str]
|
||||
An iterator that yields the path to the next image to be saved.
|
||||
|
||||
dynamic : bool, default: False
|
||||
Whether to save a static ``.png`` image or a ``.vtksz`` (interactive)
|
||||
file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[str]
|
||||
A list of the names of the images that were created.
|
||||
|
||||
"""
|
||||
image_names = []
|
||||
figures = pyvista.plotting.plotter._ALL_PLOTTERS
|
||||
for plotter in figures.values():
|
||||
_process_events_before_scraping(plotter)
|
||||
fname = next(image_path_iterator)
|
||||
# Make sure the extension is "png"
|
||||
path = Path(fname)
|
||||
fname_withoutextension = str(path.parent / path.stem)
|
||||
fname = fname_withoutextension + '.png'
|
||||
|
||||
if (gif_filename := plotter._gif_filename) is not None:
|
||||
# move gif to fname
|
||||
fname = fname[:-3] + 'gif'
|
||||
shutil.move(gif_filename, fname)
|
||||
image_names.append(fname)
|
||||
else:
|
||||
plotter.screenshot(fname)
|
||||
if not dynamic or plotter.last_vtksz is None:
|
||||
image_names.append(fname)
|
||||
else: # pragma: no cover
|
||||
fname = fname[:-3] + 'vtksz'
|
||||
with Path(fname).open('wb') as f:
|
||||
f.write(plotter.last_vtksz) # type: ignore[arg-type]
|
||||
image_names.append(fname)
|
||||
|
||||
pyvista.close_all() # close and clear all plotters
|
||||
return image_names
|
||||
|
||||
|
||||
class Scraper:
|
||||
"""Save ``pyvista.Plotter`` objects.
|
||||
|
||||
Used by sphinx-gallery to generate the plots from the code in the examples.
|
||||
|
||||
Pass an instance of this class to ``sphinx_gallery_conf`` in your
|
||||
``conf.py`` as the ``"image_scrapers"`` argument.
|
||||
|
||||
Be sure to set ``pyvista.BUILDING_GALLERY = True`` in your ``conf.py``.
|
||||
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return a stable representation of the class instance."""
|
||||
return f'<{type(self).__name__} object>'
|
||||
|
||||
def __call__(self, block, block_vars, gallery_conf): # noqa: ARG002
|
||||
"""Save the figures generated after running example code.
|
||||
|
||||
Called by sphinx-gallery.
|
||||
|
||||
"""
|
||||
from sphinx_gallery.scrapers import figure_rst # noqa: PLC0415
|
||||
|
||||
if not pyvista.BUILDING_GALLERY:
|
||||
raise RuntimeError(BUILDING_GALLERY_ERROR_MSG)
|
||||
|
||||
image_path_iterator = block_vars['image_path_iterator']
|
||||
image_names = generate_images(image_path_iterator, dynamic=False)
|
||||
return figure_rst(image_names, gallery_conf['src_dir'])
|
||||
|
||||
|
||||
class DynamicScraper: # pragma: no cover
|
||||
"""Save ``pyvista.Plotter`` objects dynamically.
|
||||
|
||||
Used by sphinx-gallery to generate the plots from the code in the examples.
|
||||
|
||||
Pass an instance of this class to ``sphinx_gallery_conf`` in your
|
||||
``conf.py`` as the ``"image_scrapers"`` argument.
|
||||
|
||||
Be sure to set ``pyvista.BUILDING_GALLERY = True`` in your ``conf.py``.
|
||||
|
||||
If the boolean variable ``PYVISTA_GALLERY_FORCE_STATIC_IN_DOCUMENT = True/False``
|
||||
is set as a global variable in the document then its value will be used as default for the
|
||||
force_static argument of the pyvista-plot command. see also the notes at :func:plot_directive
|
||||
|
||||
To alter the global value behavior just for some plots you may set the
|
||||
boolean variable ``PYVISTA_GALLERY_FORCE_STATIC = True``/
|
||||
``PYVISTA_GALLERY_FORCE_STATIC = False`` just before the appropriate ``plot`` command.
|
||||
|
||||
The default behavior of this scraper is to create interactive plots.
|
||||
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return a stable representation of the class instance."""
|
||||
return f'<{type(self).__name__} object>'
|
||||
|
||||
def __call__(self, block, block_vars, gallery_conf): # pragma: no cover
|
||||
"""Save the figures generated after running example code.
|
||||
|
||||
Called by sphinx-gallery.
|
||||
|
||||
"""
|
||||
if not pyvista.BUILDING_GALLERY:
|
||||
raise RuntimeError(BUILDING_GALLERY_ERROR_MSG)
|
||||
|
||||
# read global option if it exists
|
||||
force_static = block_vars['example_globals'].get(
|
||||
'PYVISTA_GALLERY_FORCE_STATIC_IN_DOCUMENT',
|
||||
False,
|
||||
)
|
||||
# override with block specific value if it exists
|
||||
if 'PYVISTA_GALLERY_FORCE_STATIC = True' in block[1].split('\n'):
|
||||
force_static = True
|
||||
elif 'PYVISTA_GALLERY_FORCE_STATIC = False' in block[1].split('\n'):
|
||||
force_static = False
|
||||
|
||||
if force_static is None:
|
||||
# Just in case force_static is None at this point
|
||||
force_static = False
|
||||
|
||||
dynamic = not force_static
|
||||
|
||||
image_path_iterator = block_vars['image_path_iterator']
|
||||
image_names = generate_images(image_path_iterator, dynamic=dynamic)
|
||||
|
||||
return html_rst(image_names, gallery_conf['src_dir'])
|
||||
Reference in New Issue
Block a user