init
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
"""PyVista package for 3D plotting and mesh analysis."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
import warnings
|
||||
|
||||
from pyvista._plot import plot as plot
|
||||
from pyvista._version import __version__ as __version__
|
||||
from pyvista._version import version_info as version_info
|
||||
from pyvista.core import *
|
||||
from pyvista.core import _validation as _validation
|
||||
from pyvista.core._typing_core._dataset_types import _DataObjectType as _DataObjectType
|
||||
from pyvista.core._typing_core._dataset_types import (
|
||||
_DataSetOrMultiBlockType as _DataSetOrMultiBlockType,
|
||||
)
|
||||
from pyvista.core._typing_core._dataset_types import _DataSetType as _DataSetType
|
||||
from pyvista.core._typing_core._dataset_types import _GridType as _GridType
|
||||
from pyvista.core._typing_core._dataset_types import _PointGridType as _PointGridType
|
||||
from pyvista.core._typing_core._dataset_types import _PointSetType as _PointSetType
|
||||
from pyvista.core._vtk_core import vtk_version_info as vtk_version_info
|
||||
from pyvista.core.cell import _get_vtk_id_type
|
||||
from pyvista.core.utilities.observers import send_errors_to_logging
|
||||
from pyvista.core.wrappers import _wrappers as _wrappers
|
||||
from pyvista.jupyter import set_jupyter_backend as set_jupyter_backend
|
||||
from pyvista.report import GPUInfo as GPUInfo
|
||||
from pyvista.report import Report as Report
|
||||
from pyvista.report import get_gpu_info as get_gpu_info
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import numpy as np
|
||||
|
||||
# get the int type from vtk
|
||||
ID_TYPE: type[np.int32 | np.int64] = _get_vtk_id_type()
|
||||
|
||||
# determine if using at least vtk 9.0.0
|
||||
if vtk_version_info.major < 9: # pragma: no cover
|
||||
from pyvista.core.errors import VTKVersionError
|
||||
|
||||
msg = 'VTK version must be 9.0.0 or greater.'
|
||||
raise VTKVersionError(msg)
|
||||
|
||||
# catch annoying numpy/vtk future warning:
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
|
||||
# A simple flag to set when generating the documentation
|
||||
OFF_SCREEN = os.environ.get('PYVISTA_OFF_SCREEN', 'false').lower() == 'true'
|
||||
|
||||
# flag for when building the sphinx_gallery
|
||||
BUILDING_GALLERY = os.environ.get('PYVISTA_BUILDING_GALLERY', 'false').lower() == 'true'
|
||||
|
||||
# A threshold for the max cells to compute a volume for when repr-ing
|
||||
REPR_VOLUME_MAX_CELLS = 1e6
|
||||
|
||||
# Set where figures are saved
|
||||
FIGURE_PATH = os.environ.get('PYVISTA_FIGURE_PATH', None)
|
||||
|
||||
ON_SCREENSHOT = os.environ.get('PYVISTA_ON_SCREENSHOT', 'false').lower() == 'true'
|
||||
|
||||
# Send VTK messages to the logging module:
|
||||
send_errors_to_logging()
|
||||
|
||||
# theme to use by default for the plot directive
|
||||
PLOT_DIRECTIVE_THEME = None
|
||||
|
||||
# Set a parameter to control default print format for floats outside
|
||||
# of the plotter
|
||||
FLOAT_FORMAT = '{:.3e}'
|
||||
|
||||
# Serialization format to be used when pickling `DataObject`
|
||||
PICKLE_FORMAT: Literal['vtk', 'xml', 'legacy'] = 'vtk' if vtk_version_info >= (9, 3) else 'xml'
|
||||
|
||||
# Name used for unnamed scalars
|
||||
DEFAULT_SCALARS_NAME = 'Data'
|
||||
|
||||
MAX_N_COLOR_BARS = 10
|
||||
|
||||
_VTK_SNAKE_CASE_STATE: Literal['allow', 'warning', 'error'] = 'error'
|
||||
|
||||
|
||||
# Import all modules for type checkers and linters
|
||||
if TYPE_CHECKING:
|
||||
from pyvista import demos as demos
|
||||
from pyvista import examples as examples
|
||||
from pyvista import ext as ext
|
||||
from pyvista import trame as trame
|
||||
from pyvista import utilities as utilities
|
||||
from pyvista.plotting import *
|
||||
|
||||
|
||||
# Lazily import/access the plotting module
|
||||
def __getattr__(name):
|
||||
"""Fetch an attribute ``name`` from ``globals()`` or the ``pyvista.plotting`` module.
|
||||
|
||||
This override is implemented to prevent importing all of the plotting module
|
||||
and GL-dependent VTK modules when importing PyVista.
|
||||
|
||||
Raises
|
||||
------
|
||||
AttributeError
|
||||
If the attribute is not found.
|
||||
|
||||
"""
|
||||
import importlib # noqa: PLC0415
|
||||
import inspect # noqa: PLC0415
|
||||
|
||||
allow = {
|
||||
'demos',
|
||||
'examples',
|
||||
'ext',
|
||||
'trame',
|
||||
'utilities',
|
||||
}
|
||||
if name in allow:
|
||||
return importlib.import_module(f'pyvista.{name}')
|
||||
|
||||
# avoid recursive import
|
||||
if 'pyvista.plotting' not in sys.modules:
|
||||
import pyvista.plotting # noqa: F401, PLC0415
|
||||
|
||||
try:
|
||||
feature = inspect.getattr_static(sys.modules['pyvista.plotting'], name)
|
||||
except AttributeError:
|
||||
msg = f"module 'pyvista' has no attribute '{name}'"
|
||||
raise AttributeError(msg) from None
|
||||
|
||||
return feature
|
||||
@@ -0,0 +1,251 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import wraps
|
||||
import inspect
|
||||
from inspect import Parameter
|
||||
from inspect import Signature
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
from typing import TypeVar
|
||||
from typing import overload
|
||||
import warnings
|
||||
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
from pyvista._version import version_info
|
||||
|
||||
_MAX_POSITIONAL_ARGS = 3 # Should match value in pyproject.toml
|
||||
|
||||
|
||||
P = ParamSpec('P')
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
@overload
|
||||
def _deprecate_positional_args(
|
||||
func: Callable[P, T],
|
||||
*,
|
||||
version: tuple[int, int] = ...,
|
||||
allowed: list[str] | None = ...,
|
||||
n_allowed: int = ...,
|
||||
) -> Callable[P, T]: ...
|
||||
@overload
|
||||
def _deprecate_positional_args(
|
||||
*, version: tuple[int, int] = ..., allowed: list[str] | None = ..., n_allowed: int = ...
|
||||
) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
|
||||
def _deprecate_positional_args(
|
||||
func: Callable[..., T] | None = None,
|
||||
*,
|
||||
version: tuple[int, int] = (0, 50),
|
||||
allowed: list[str] | None = None,
|
||||
n_allowed: int | None = None,
|
||||
) -> Callable[..., T] | Callable[[Callable[P, T]], Callable[P, T]]:
|
||||
"""Use a decorator to deprecate positional arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable, default=None
|
||||
Function to check arguments on.
|
||||
|
||||
version : tuple[int, int], default: (0, 50)
|
||||
The version (major, minor) when positional arguments will result in RuntimeError.
|
||||
|
||||
allowed : list[str], optional
|
||||
List of argument names which are allowed to be positional. This value is limited
|
||||
based on rule PLR0917.
|
||||
|
||||
n_allowed : int, optional
|
||||
Override the number of allowed positional arguments to this value.
|
||||
|
||||
"""
|
||||
|
||||
def _inner_deprecate_positional_args(f: Callable[P, T]) -> Callable[P, T]:
|
||||
def qualified_name() -> str:
|
||||
return f.__qualname__ if hasattr(f, '__qualname__') else f.__name__
|
||||
|
||||
decorator_name = _deprecate_positional_args.__name__
|
||||
sig = inspect.signature(f)
|
||||
param_names = list(sig.parameters)
|
||||
|
||||
# Validate n_allowed itself
|
||||
if n_allowed:
|
||||
if n_allowed <= _MAX_POSITIONAL_ARGS:
|
||||
msg = (
|
||||
f'In decorator {decorator_name!r} for function {qualified_name()!r}:\n'
|
||||
f'`n_allowed` must be greater than {_MAX_POSITIONAL_ARGS} for it to be useful.'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
n_allowed_ = n_allowed
|
||||
else:
|
||||
n_allowed_ = _MAX_POSITIONAL_ARGS
|
||||
|
||||
if allowed is not None:
|
||||
# Validate input type
|
||||
if not isinstance(allowed, list):
|
||||
msg = ( # type: ignore[unreachable]
|
||||
f'In decorator {decorator_name!r} for function {qualified_name()!r}:\n'
|
||||
f'Allowed arguments must be a list, got {type(allowed)}.'
|
||||
)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Validate number of allowed args
|
||||
if len(allowed) > n_allowed_:
|
||||
msg = (
|
||||
f'In decorator {decorator_name!r} for function {qualified_name()!r}:\n'
|
||||
f'A maximum of {n_allowed_} positional arguments are allowed.\n'
|
||||
f'Got {len(allowed)}: {allowed}'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
# Validate allowed against actual parameter names
|
||||
for name in allowed:
|
||||
if name not in param_names:
|
||||
msg = (
|
||||
f'Allowed positional argument {name!r} in decorator '
|
||||
f'{decorator_name!r}\n'
|
||||
f'is not a parameter of function {qualified_name()!r}.'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
# Check that allowed args appears in the same order as in the signature
|
||||
sig_allowed = [name for name in param_names if name in allowed]
|
||||
if sig_allowed != allowed:
|
||||
msg = (
|
||||
f'The `allowed` list {allowed} in decorator {decorator_name!r} is not in the\n'
|
||||
f'same order as the parameters in {qualified_name()!r}.\n'
|
||||
f'Expected order: {sig_allowed}.'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
# Check that allowed args are not already kwonly
|
||||
for name in allowed:
|
||||
if sig.parameters[name].kind == Parameter.KEYWORD_ONLY:
|
||||
msg = (
|
||||
f'Parameter {name!r} in decorator {decorator_name!r} is already '
|
||||
f'keyword-only\nand should be removed from the allowed list.'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
# Check if the decorator is even needed at all
|
||||
n_positional = 0
|
||||
for name in param_names:
|
||||
if name not in ['cls', 'self'] and sig.parameters[name].kind in [
|
||||
Parameter.POSITIONAL_ONLY,
|
||||
Parameter.POSITIONAL_OR_KEYWORD,
|
||||
]:
|
||||
n_positional += 1
|
||||
actual_n_allowed = len(allowed) if allowed else 0
|
||||
if n_positional <= actual_n_allowed:
|
||||
msg = (
|
||||
f'Function {qualified_name()!r} has {actual_n_allowed} positional arguments, '
|
||||
f'which is less than or equal to the\nmaximum number of allowed positional '
|
||||
f'arguments ({n_allowed_}).\nThis decorator is not necessary and can be removed.'
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# Raise error post-deprecation
|
||||
if version_info >= version:
|
||||
# Construct expected positional args and signature
|
||||
new_parameters = []
|
||||
max_args_to_print = actual_n_allowed + 2
|
||||
cls_or_self = 'cls' in param_names or 'self' in param_names
|
||||
max_args_to_print = (max_args_to_print + 1) if cls_or_self else max_args_to_print
|
||||
has_too_many_to_print = False
|
||||
for i, name in enumerate(param_names):
|
||||
if i > max_args_to_print:
|
||||
has_too_many_to_print = True
|
||||
break
|
||||
if name in ['cls', 'self', *(allowed if allowed else [])]:
|
||||
current_kind = sig.parameters[name].kind
|
||||
new_kind = (
|
||||
current_kind
|
||||
if current_kind != Parameter.KEYWORD_ONLY
|
||||
else Parameter.KEYWORD_ONLY
|
||||
)
|
||||
new_parameters.append(Parameter(name, kind=new_kind))
|
||||
else:
|
||||
new_parameters.append(Parameter(name, kind=Parameter.KEYWORD_ONLY))
|
||||
|
||||
signature_string = f'{qualified_name()}{Signature(new_parameters)}'
|
||||
if has_too_many_to_print:
|
||||
# Replace ending bracket with ellipses
|
||||
signature_string = f'{signature_string[:-1]}, ...)'
|
||||
|
||||
# Get source file and line number
|
||||
file = Path(
|
||||
os.path.relpath(inspect.getfile(f), start=os.getcwd()) # noqa: PTH109 # https://github.com/pyvista/pyvista/pull/7732
|
||||
).as_posix()
|
||||
lineno = inspect.getsourcelines(f)[1]
|
||||
location = f'{file}:{lineno}'
|
||||
|
||||
msg = (
|
||||
f'Positional arguments are no longer allowed in {qualified_name()!r}.\n'
|
||||
f'Update the function signature at:\n'
|
||||
f'{location} to enforce keyword-only args:\n'
|
||||
f' {signature_string}\n'
|
||||
f'and remove the {decorator_name!r} decorator.'
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
@wraps(f)
|
||||
def inner_f(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
passed_positional_names = param_names[: len(args)]
|
||||
|
||||
# Exclude allowed ones
|
||||
if allowed:
|
||||
offending_args = [name for name in passed_positional_names if name not in allowed]
|
||||
else:
|
||||
offending_args = passed_positional_names
|
||||
|
||||
if 'self' in offending_args:
|
||||
offending_args.remove('self')
|
||||
if 'cls' in offending_args:
|
||||
offending_args.remove('cls')
|
||||
|
||||
if offending_args:
|
||||
# Craft a message to print a warning or raise an error
|
||||
if len(offending_args) == 1:
|
||||
a = ' a '
|
||||
s = ''
|
||||
this = 'this'
|
||||
else:
|
||||
a = ' '
|
||||
s = 's'
|
||||
this = 'these'
|
||||
|
||||
if version_info < version:
|
||||
# Print warning
|
||||
version_str = '.'.join(map(str, version))
|
||||
arg_list = ', '.join(f'{a!r}' for a in offending_args)
|
||||
stack_level = 3
|
||||
|
||||
def call_site() -> str:
|
||||
# Get location where the function is called
|
||||
frame = inspect.stack()[stack_level]
|
||||
file = Path(
|
||||
os.path.relpath(frame.filename, start=os.getcwd()) # noqa: PTH109 # https://github.com/pyvista/pyvista/pull/7732
|
||||
).as_posix()
|
||||
return f'{file}:{frame.lineno}'
|
||||
|
||||
def warn_positional_args() -> None:
|
||||
from pyvista.core.errors import PyVistaDeprecationWarning # noqa: PLC0415
|
||||
|
||||
msg = (
|
||||
f'\n{call_site()}: '
|
||||
f'Argument{s} {arg_list} must be passed as{a}keyword argument{s} '
|
||||
f'to function {qualified_name()!r}.\n'
|
||||
f'From version {version_str}, passing {this} as{a}positional '
|
||||
f'argument{s} will result in a TypeError.'
|
||||
)
|
||||
warnings.warn(msg, PyVistaDeprecationWarning, stacklevel=stack_level)
|
||||
|
||||
warn_positional_args()
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return inner_f
|
||||
|
||||
if func is not None:
|
||||
return _inner_deprecate_positional_args(func)
|
||||
return _inner_deprecate_positional_args
|
||||
@@ -0,0 +1,324 @@
|
||||
"""PyVista's famous ``plot()`` helper method.
|
||||
|
||||
This method is placed at the top-level to allow us to easily bind
|
||||
the method to all of the core datatypes before importing the
|
||||
``pyvista.plotting`` module and libGL dependent VTK modules.
|
||||
This is necessary for future versions of PyVista that will fully
|
||||
decouple the ``core`` and ``plotting`` APIs.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['var_item'])
|
||||
def plot( # noqa: PLR0917
|
||||
var_item,
|
||||
off_screen=None,
|
||||
full_screen=None,
|
||||
screenshot=None,
|
||||
interactive=True, # noqa: FBT002
|
||||
cpos=None,
|
||||
window_size=None,
|
||||
show_bounds=False, # noqa: FBT002
|
||||
show_axes=None,
|
||||
notebook=None,
|
||||
background=None,
|
||||
text='',
|
||||
return_img=False, # noqa: FBT002
|
||||
eye_dome_lighting=False, # noqa: FBT002
|
||||
volume=False, # noqa: FBT002
|
||||
parallel_projection=False, # noqa: FBT002
|
||||
jupyter_backend=None,
|
||||
return_viewer=False, # noqa: FBT002
|
||||
return_cpos=False, # noqa: FBT002
|
||||
jupyter_kwargs=None,
|
||||
theme=None,
|
||||
anti_aliasing=None,
|
||||
zoom=None,
|
||||
border=False, # noqa: FBT002
|
||||
border_color='k',
|
||||
border_width=2.0,
|
||||
ssao=False, # noqa: FBT002
|
||||
**kwargs,
|
||||
):
|
||||
"""Plot a PyVista, numpy, or vtk object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
var_item : pyvista.DataSet
|
||||
See :func:`Plotter.add_mesh <pyvista.Plotter.add_mesh>` for all
|
||||
supported types.
|
||||
|
||||
off_screen : bool, optional
|
||||
Plots off screen when ``True``. Helpful for saving
|
||||
screenshots without a window popping up. Defaults to the
|
||||
global setting ``pyvista.OFF_SCREEN``.
|
||||
|
||||
full_screen : bool, default: :attr:`pyvista.plotting.themes.Theme.full_screen`
|
||||
Opens window in full screen. When enabled, ignores
|
||||
``window_size``.
|
||||
|
||||
screenshot : str or bool, optional
|
||||
Saves screenshot to file when enabled. See:
|
||||
:func:`Plotter.screenshot() <pyvista.Plotter.screenshot>`.
|
||||
Default ``False``.
|
||||
|
||||
When ``True``, takes screenshot and returns ``numpy`` array of
|
||||
image.
|
||||
|
||||
interactive : bool, default: :attr:`pyvista.plotting.themes.Theme.interactive`
|
||||
Allows user to pan and move figure.
|
||||
|
||||
cpos : list, optional
|
||||
List of camera position, focal point, and view up.
|
||||
|
||||
window_size : sequence, default: :attr:`pyvista.plotting.themes.Theme.window_size`
|
||||
Window size in pixels.
|
||||
|
||||
show_bounds : bool, default: False
|
||||
Shows mesh bounds when ``True``.
|
||||
|
||||
show_axes : bool, default: :attr:`pyvista.plotting.themes._AxesConfig.show`
|
||||
Shows a vtk axes widget.
|
||||
|
||||
notebook : bool, default: :attr:`pyvista.plotting.themes.Theme.notebook`
|
||||
When ``True``, the resulting plot is placed inline a jupyter
|
||||
notebook. Assumes a jupyter console is active.
|
||||
|
||||
background : ColorLike, default: :attr:`pyvista.plotting.themes.Theme.background`
|
||||
Color of the background.
|
||||
|
||||
text : str, optional
|
||||
Adds text at the bottom of the plot.
|
||||
|
||||
return_img : bool, default: False
|
||||
Returns numpy array of the last image rendered.
|
||||
|
||||
eye_dome_lighting : bool, optional
|
||||
Enables eye dome lighting.
|
||||
|
||||
volume : bool, default: False
|
||||
Use the :func:`Plotter.add_volume()
|
||||
<pyvista.Plotter.add_volume>` method for volume rendering.
|
||||
|
||||
parallel_projection : bool, default: False
|
||||
Enable parallel projection.
|
||||
|
||||
jupyter_backend : str, default: :attr:`pyvista.plotting.themes.Theme.jupyter_backend`
|
||||
Jupyter notebook plotting backend to use. One of the
|
||||
following:
|
||||
|
||||
* ``'none'`` : Do not display in the notebook.
|
||||
* ``'static'`` : Display a static figure.
|
||||
* ``'trame'`` : Display using ``trame``.
|
||||
|
||||
This can also be set globally with
|
||||
:func:`pyvista.set_jupyter_backend`.
|
||||
|
||||
return_viewer : bool, default: False
|
||||
Return the jupyterlab viewer, scene, or display object
|
||||
when plotting with jupyter notebook.
|
||||
|
||||
return_cpos : bool, default: False
|
||||
Return the last camera position from the render window
|
||||
when enabled. Defaults to value in theme settings.
|
||||
|
||||
jupyter_kwargs : dict, optional
|
||||
Keyword arguments for the Jupyter notebook plotting backend.
|
||||
See :ref:`customize_trame_toolbar_example` for an example
|
||||
using this keyword.
|
||||
|
||||
theme : pyvista.plotting.themes.Theme, optional
|
||||
Plot-specific theme.
|
||||
|
||||
anti_aliasing : str | bool, default: :attr:`pyvista.plotting.themes.Theme.anti_aliasing`
|
||||
Enable or disable anti-aliasing. If ``True``, uses ``"msaa"``. If False,
|
||||
disables anti_aliasing. If a string, should be either ``"fxaa"`` or
|
||||
``"ssaa"``.
|
||||
|
||||
zoom : float, str, optional
|
||||
Camera zoom. Either ``'tight'`` or a float. A value greater than 1
|
||||
is a zoom-in, a value less than 1 is a zoom-out. Must be greater
|
||||
than 0.
|
||||
|
||||
border : bool, default: False
|
||||
Draw a border around each render window.
|
||||
|
||||
border_color : ColorLike, default: "k"
|
||||
Either a string, rgb list, or hex color string. For example:
|
||||
|
||||
* ``color='white'``
|
||||
* ``color='w'``
|
||||
* ``color=[1.0, 1.0, 1.0]``
|
||||
* ``color='#FFFFFF'``
|
||||
|
||||
border_width : float, default: 2.0
|
||||
Width of the border in pixels when enabled.
|
||||
|
||||
ssao : bool, optional
|
||||
Enable surface space ambient occlusion (SSAO). See
|
||||
:func:`Plotter.enable_ssao` for more details.
|
||||
|
||||
**kwargs : dict, optional
|
||||
See :func:`pyvista.Plotter.add_mesh` for additional options.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cpos : list
|
||||
List of camera position, focal point, and view up.
|
||||
Returned only when ``return_cpos=True`` or set in the
|
||||
default global or plot theme. Not returned when in a
|
||||
jupyter notebook and ``return_viewer=True``.
|
||||
|
||||
image : np.ndarray
|
||||
Numpy array of the last image when either ``return_img=True``
|
||||
or ``screenshot=True`` is set. Not returned when in a
|
||||
jupyter notebook with ``return_viewer=True``. Optionally
|
||||
contains alpha values. Sized:
|
||||
|
||||
* [Window height x Window width x 3] if the theme sets
|
||||
``transparent_background=False``.
|
||||
* [Window height x Window width x 4] if the theme sets
|
||||
``transparent_background=True``.
|
||||
|
||||
widget : ipywidgets.Widget
|
||||
IPython widget when ``return_viewer=True``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Plot a simple sphere while showing its edges.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.plot(show_edges=True)
|
||||
|
||||
Plot a volume mesh. Color by distance from the center of the
|
||||
ImageData. Note ``volume=True`` is passed.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> grid = pv.ImageData(dimensions=(32, 32, 32), spacing=(0.5, 0.5, 0.5))
|
||||
>>> grid['data'] = np.linalg.norm(grid.center - grid.points, axis=1)
|
||||
>>> grid['data'] = np.abs(grid['data'] - grid['data'].max()) ** 3
|
||||
>>> grid.plot(volume=True)
|
||||
|
||||
"""
|
||||
if jupyter_kwargs is None:
|
||||
jupyter_kwargs = {}
|
||||
|
||||
# undocumented kwarg used within pytest to run a function before closing
|
||||
before_close_callback = kwargs.pop('before_close_callback', None)
|
||||
|
||||
# pop from kwargs here to avoid including them in add_mesh or add_volume
|
||||
eye_dome_lighting = kwargs.pop('edl', eye_dome_lighting)
|
||||
show_grid = kwargs.pop('show_grid', False)
|
||||
auto_close = kwargs.get('auto_close')
|
||||
|
||||
pl = pyvista.Plotter(
|
||||
window_size=window_size,
|
||||
off_screen=off_screen,
|
||||
notebook=notebook,
|
||||
theme=theme,
|
||||
border=border,
|
||||
border_color=border_color,
|
||||
border_width=border_width,
|
||||
)
|
||||
|
||||
if show_axes is None:
|
||||
show_axes = pl.theme.axes.show
|
||||
if show_axes:
|
||||
if pl.theme.axes.box:
|
||||
pl.add_box_axes()
|
||||
else:
|
||||
pl.add_axes()
|
||||
|
||||
if anti_aliasing:
|
||||
if anti_aliasing is True:
|
||||
pl.enable_anti_aliasing('msaa', multi_samples=pyvista.global_theme.multi_samples)
|
||||
else:
|
||||
pl.enable_anti_aliasing(anti_aliasing)
|
||||
elif anti_aliasing is False:
|
||||
pl.disable_anti_aliasing()
|
||||
|
||||
try:
|
||||
pl.set_background(background)
|
||||
except (ValueError, TypeError):
|
||||
if isinstance(background, (str, Path)):
|
||||
path = Path(background)
|
||||
if path.is_file():
|
||||
pl.add_background_image(path)
|
||||
else:
|
||||
msg = f'Background must be color-like or a file path. Got {background} instead.'
|
||||
raise TypeError(msg)
|
||||
|
||||
if isinstance(var_item, list):
|
||||
if len(var_item) == 2: # might be arrows
|
||||
isarr_0 = isinstance(var_item[0], np.ndarray)
|
||||
isarr_1 = isinstance(var_item[1], np.ndarray)
|
||||
if isarr_0 and isarr_1:
|
||||
pl.add_arrows(var_item[0], var_item[1])
|
||||
else:
|
||||
for item in var_item:
|
||||
if volume or (isinstance(item, np.ndarray) and item.ndim == 3):
|
||||
pl.add_volume(item, **kwargs)
|
||||
else:
|
||||
pl.add_mesh(item, **kwargs)
|
||||
else:
|
||||
for item in var_item:
|
||||
if volume or (isinstance(item, np.ndarray) and item.ndim == 3):
|
||||
pl.add_volume(item, **kwargs)
|
||||
else:
|
||||
pl.add_mesh(item, **kwargs)
|
||||
elif volume or (isinstance(var_item, np.ndarray) and var_item.ndim == 3):
|
||||
pl.add_volume(var_item, **kwargs)
|
||||
elif isinstance(var_item, pyvista.MultiBlock):
|
||||
pl.add_composite(var_item, **kwargs)
|
||||
else:
|
||||
pl.add_mesh(var_item, **kwargs)
|
||||
|
||||
if text:
|
||||
pl.add_text(text)
|
||||
|
||||
if show_grid:
|
||||
pl.show_grid()
|
||||
elif show_bounds:
|
||||
pl.show_bounds()
|
||||
|
||||
if cpos is None:
|
||||
cpos = pl.get_default_cam_pos()
|
||||
pl.camera_position = cpos
|
||||
pl.camera_set = False
|
||||
else:
|
||||
pl.camera_position = cpos
|
||||
|
||||
if eye_dome_lighting:
|
||||
pl.enable_eye_dome_lighting()
|
||||
|
||||
if parallel_projection:
|
||||
pl.enable_parallel_projection()
|
||||
|
||||
if ssao:
|
||||
pl.enable_ssao()
|
||||
|
||||
if zoom is not None:
|
||||
pl.camera.zoom(zoom)
|
||||
|
||||
return pl.show(
|
||||
auto_close=auto_close,
|
||||
interactive=interactive,
|
||||
full_screen=full_screen,
|
||||
screenshot=screenshot,
|
||||
return_img=return_img,
|
||||
jupyter_backend=jupyter_backend,
|
||||
before_close_callback=before_close_callback,
|
||||
jupyter_kwargs=jupyter_kwargs,
|
||||
return_viewer=return_viewer,
|
||||
return_cpos=return_cpos,
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
"""Version info for pyvista.
|
||||
|
||||
On the ``main`` branch, use 'dev0' to denote a development version.
|
||||
For example:
|
||||
|
||||
version_info = 0, 27, 'dev0'
|
||||
|
||||
When generating pre-release wheels, use '0rcN', for example:
|
||||
|
||||
version_info = 0, 28, '0rc1'
|
||||
|
||||
Denotes the first release candidate.
|
||||
|
||||
"""
|
||||
|
||||
# major, minor, patch
|
||||
from __future__ import annotations
|
||||
|
||||
version_info = 0, 46, 4
|
||||
|
||||
# Nice string for the version
|
||||
__version__ = '.'.join(map(str, version_info))
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Close all plotters to help control memory usage for our doctests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import pyvista
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def autoclose_plotters():
|
||||
"""Close all plotters."""
|
||||
yield
|
||||
pyvista.close_all()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_global_theme():
|
||||
"""Reset global_theme."""
|
||||
# this stops any doctest-module tests from overriding the global theme and
|
||||
# creating test side effects
|
||||
pyvista.set_plot_theme('document_build')
|
||||
yield
|
||||
pyvista.set_plot_theme('document_build')
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Core routines."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from . import _vtk_core as _vtk_core
|
||||
from ._typing_core import *
|
||||
from .cell import Cell as Cell
|
||||
from .cell import CellArray as CellArray
|
||||
from .celltype import CellType as CellType
|
||||
from .composite import MultiBlock as MultiBlock
|
||||
from .dataobject import DataObject as DataObject
|
||||
from .dataset import DataSet as DataSet
|
||||
from .datasetattributes import DataSetAttributes as DataSetAttributes
|
||||
from .errors import AmbiguousDataError as AmbiguousDataError
|
||||
from .errors import DeprecationError as DeprecationError
|
||||
from .errors import MissingDataError as MissingDataError
|
||||
from .errors import NotAllTrianglesError as NotAllTrianglesError
|
||||
from .errors import PointSetCellOperationError as PointSetCellOperationError
|
||||
from .errors import PointSetDimensionReductionError as PointSetDimensionReductionError
|
||||
from .errors import PointSetNotSupported as PointSetNotSupported
|
||||
from .errors import PyVistaAttributeError as PyVistaAttributeError
|
||||
from .errors import PyVistaDeprecationWarning as PyVistaDeprecationWarning
|
||||
from .errors import PyVistaEfficiencyWarning as PyVistaEfficiencyWarning
|
||||
from .errors import PyVistaFutureWarning as PyVistaFutureWarning
|
||||
from .errors import PyVistaPipelineError as PyVistaPipelineError
|
||||
from .errors import VTKVersionError as VTKVersionError
|
||||
from .filters import CompositeFilters as CompositeFilters
|
||||
from .filters import DataObjectFilters as DataObjectFilters
|
||||
from .filters import DataSetFilters as DataSetFilters
|
||||
from .filters import ImageDataFilters as ImageDataFilters
|
||||
from .filters import PolyDataFilters as PolyDataFilters
|
||||
from .filters import UnstructuredGridFilters as UnstructuredGridFilters
|
||||
from .grid import Grid as Grid
|
||||
from .grid import ImageData as ImageData
|
||||
from .grid import RectilinearGrid as RectilinearGrid
|
||||
from .objects import Table as Table
|
||||
from .partitioned import PartitionedDataSet as PartitionedDataSet
|
||||
from .pointset import ExplicitStructuredGrid as ExplicitStructuredGrid
|
||||
from .pointset import PointGrid as PointGrid
|
||||
from .pointset import PointSet as PointSet
|
||||
from .pointset import PolyData as PolyData
|
||||
from .pointset import StructuredGrid as StructuredGrid
|
||||
from .pointset import UnstructuredGrid as UnstructuredGrid
|
||||
from .pyvista_ndarray import pyvista_ndarray as pyvista_ndarray
|
||||
from .utilities import *
|
||||
from .wrappers import _wrappers as _wrappers
|
||||
@@ -0,0 +1,22 @@
|
||||
"""Type aliases for type hints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ._aliases import ArrayLike as ArrayLike
|
||||
from ._aliases import BoundsTuple as BoundsTuple
|
||||
from ._aliases import CellArrayLike as CellArrayLike
|
||||
from ._aliases import CellsLike as CellsLike
|
||||
from ._aliases import InteractionEventType as InteractionEventType
|
||||
from ._aliases import MatrixLike as MatrixLike
|
||||
from ._aliases import Number as Number
|
||||
from ._aliases import RotationLike as RotationLike
|
||||
from ._aliases import TransformLike as TransformLike
|
||||
from ._aliases import VectorLike as VectorLike
|
||||
from ._array_like import NumberType as NumberType
|
||||
from ._array_like import NumpyArray as NumpyArray
|
||||
from ._dataset_types import _DataObjectType as _DataObjectType
|
||||
from ._dataset_types import _DataSetOrMultiBlockType as _DataSetOrMultiBlockType
|
||||
from ._dataset_types import _DataSetType as _DataSetType
|
||||
from ._dataset_types import _GridType as _GridType
|
||||
from ._dataset_types import _PointGridType as _PointGridType
|
||||
from ._dataset_types import _PointSetType as _PointSetType
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Core type aliases."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
from typing import NamedTuple
|
||||
from typing import Union
|
||||
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
|
||||
from ._array_like import NumberType
|
||||
from ._array_like import _ArrayLike
|
||||
from ._array_like import _ArrayLike1D
|
||||
from ._array_like import _ArrayLike2D
|
||||
|
||||
if TYPE_CHECKING or os.environ.get(
|
||||
'PYVISTA_DOCUMENTATION_BULKY_IMPORTS_ALLOWED'
|
||||
): # pragma: no cover
|
||||
try:
|
||||
from scipy.spatial.transform import Rotation
|
||||
except ImportError:
|
||||
Rotation = None
|
||||
else:
|
||||
Rotation = None
|
||||
|
||||
# NOTE:
|
||||
# Type aliases are automatically expanded in the documentation.
|
||||
# To document an alias as-is without expansion, the alias should be:
|
||||
# (1) added to the "autodoc_type_aliases" dictionary in /doc/source/conf.py
|
||||
# (2) added to /doc/core/typing.rst
|
||||
# (3) added to the "numpydoc_validation" excludes in pyproject.toml
|
||||
#
|
||||
# Long or complex type aliases (e.g. a union of 4 or more base types) should
|
||||
# always be added to the dictionary and documented
|
||||
Number = Union[int, float]
|
||||
|
||||
VectorLike = _ArrayLike1D[NumberType]
|
||||
VectorLike.__doc__ = """One-dimensional array-like object with numerical values.
|
||||
|
||||
Includes sequences and numpy arrays.
|
||||
"""
|
||||
|
||||
MatrixLike = _ArrayLike2D[NumberType]
|
||||
MatrixLike.__doc__ = """Two-dimensional array-like object with numerical values.
|
||||
|
||||
Includes singly-nested sequences and numpy arrays.
|
||||
"""
|
||||
|
||||
ArrayLike = _ArrayLike[NumberType]
|
||||
ArrayLike.__doc__ = """Any-dimensional array-like object with numerical values.
|
||||
|
||||
Includes sequences, nested sequences, and numpy arrays. Scalar values are not included.
|
||||
"""
|
||||
if Rotation is not None:
|
||||
RotationLike = Union[MatrixLike[float], _vtk.vtkMatrix3x3, Rotation]
|
||||
else:
|
||||
RotationLike = Union[MatrixLike[float], _vtk.vtkMatrix3x3] # type: ignore[misc]
|
||||
RotationLike.__doc__ = """Array or object representing a spatial rotation.
|
||||
|
||||
Includes 3x3 arrays and SciPy Rotation objects.
|
||||
"""
|
||||
|
||||
TransformLike = Union[RotationLike, _vtk.vtkMatrix4x4, _vtk.vtkTransform]
|
||||
TransformLike.__doc__ = """Array or object representing a spatial transformation.
|
||||
|
||||
Includes 3x3 and 4x4 arrays as well as SciPy Rotation objects."""
|
||||
|
||||
|
||||
class BoundsTuple(NamedTuple):
|
||||
"""Tuple of six values representing 3D bounds.
|
||||
|
||||
Has the form ``(x_min, x_max, y_min, y_max, z_min, z_max)``.
|
||||
"""
|
||||
|
||||
x_min: float
|
||||
x_max: float
|
||||
y_min: float
|
||||
y_max: float
|
||||
z_min: float
|
||||
z_max: float
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# Split bounds at decimal and compute padding needed to the left of it
|
||||
dot = '.'
|
||||
strings = [str(float(val)) for val in self]
|
||||
has_dot = [dot in s for s in strings]
|
||||
split_strings = [s.split(dot) for s in strings]
|
||||
pad_left = max(len(parts[0]) for parts in split_strings)
|
||||
|
||||
# Iterate through fields and align values at the decimal
|
||||
lines = []
|
||||
fields = self._fields
|
||||
field_size = max(len(f) for f in fields)
|
||||
name = self.__class__.__name__
|
||||
whitespace = (len(name) + 1) * ' '
|
||||
for i, items in enumerate(zip(fields, split_strings)):
|
||||
field, parts = items
|
||||
if has_dot[i]:
|
||||
left, right = parts
|
||||
aligned = f'{left:>{pad_left}}{dot}{right}'
|
||||
else:
|
||||
left = parts[0]
|
||||
aligned = f'{left:>{pad_left}}'
|
||||
spacing = '' if i == 0 else whitespace
|
||||
comma = '' if i == len(fields) - 1 else ','
|
||||
lines.append(f'{spacing}{field:<{field_size}} = {aligned}{comma}')
|
||||
|
||||
joined_lines = '\n'.join(lines)
|
||||
return f'{name}({joined_lines})'
|
||||
|
||||
|
||||
CellsLike = Union[MatrixLike[int], VectorLike[int]]
|
||||
|
||||
CellArrayLike = Union[CellsLike, _vtk.vtkCellArray]
|
||||
|
||||
# Undocumented alias - should be expanded in docs
|
||||
_ArrayLikeOrScalar = Union[NumberType, ArrayLike[NumberType]]
|
||||
|
||||
InteractionEventType = Union[Literal['end', 'start', 'always'], _vtk.vtkCommand.EventIds]
|
||||
InteractionEventType.__doc__ = """Interaction event mostly used for widgets.
|
||||
|
||||
Includes both strings such as `end`, 'start' and `always` and `_vtk.vtkCommand.EventIds`.
|
||||
"""
|
||||
@@ -0,0 +1,87 @@
|
||||
"""Generic array-like type definitions.
|
||||
|
||||
Definitions here are loosely based on code in numpy._typing._array_like.
|
||||
Some key differences include:
|
||||
|
||||
- Some npt._array_like definitions explicitly support dual-types for
|
||||
handling python and numpy scalar data types separately.
|
||||
Here, only a single generic type is used for simplicity.
|
||||
|
||||
- The npt._array_like definitions use a recursive _NestedSequence protocol.
|
||||
Here, finite sequences are used instead.
|
||||
|
||||
- The npt._array_like definitions use a generic _SupportsArray protocol.
|
||||
Here, we use `ndarray` directly.
|
||||
|
||||
- The npt._array_like definitions include scalar types (e.g. float, int).
|
||||
Here they are excluded (i.e. scalars are not considered to be arrays).
|
||||
|
||||
- The npt._array_like TypeVar is bound to np.generic. Here, the
|
||||
TypeVar is bound to a subset of numeric types only.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
# Define numeric types
|
||||
NumberType = TypeVar(
|
||||
'NumberType',
|
||||
bound=Union[np.floating, np.integer, np.bool_, float, int, bool],
|
||||
)
|
||||
NumberType.__doc__ = """Type variable for numeric data types."""
|
||||
|
||||
# Create a copy of the typevar which can be used for annotating a second variable.
|
||||
# Its definition should be identical to `NumberType`
|
||||
_NumberType = TypeVar( # noqa: PYI018
|
||||
'_NumberType',
|
||||
bound=Union[np.floating, np.integer, np.bool_, float, int, bool],
|
||||
)
|
||||
|
||||
NumpyArray = npt.NDArray[NumberType]
|
||||
|
||||
_FiniteNestedList = Union[
|
||||
list[NumberType],
|
||||
list[list[NumberType]],
|
||||
list[list[list[NumberType]]],
|
||||
list[list[list[list[NumberType]]]],
|
||||
]
|
||||
_FiniteNestedTuple = Union[
|
||||
tuple[NumberType],
|
||||
tuple[tuple[NumberType]],
|
||||
tuple[tuple[tuple[NumberType]]],
|
||||
tuple[tuple[tuple[tuple[NumberType]]]],
|
||||
]
|
||||
|
||||
_ArrayLike1D = Union[
|
||||
NumpyArray[NumberType],
|
||||
Sequence[NumberType],
|
||||
Sequence[NumpyArray[NumberType]],
|
||||
]
|
||||
_ArrayLike2D = Union[
|
||||
NumpyArray[NumberType],
|
||||
Sequence[Sequence[NumberType]],
|
||||
Sequence[Sequence[NumpyArray[NumberType]]],
|
||||
]
|
||||
_ArrayLike3D = Union[
|
||||
NumpyArray[NumberType],
|
||||
Sequence[Sequence[Sequence[NumberType]]],
|
||||
Sequence[Sequence[Sequence[NumpyArray[NumberType]]]],
|
||||
]
|
||||
_ArrayLike4D = Union[
|
||||
NumpyArray[NumberType],
|
||||
Sequence[Sequence[Sequence[Sequence[NumberType]]]],
|
||||
Sequence[Sequence[Sequence[Sequence[NumpyArray[NumberType]]]]],
|
||||
]
|
||||
_ArrayLike = Union[
|
||||
_ArrayLike1D[NumberType],
|
||||
_ArrayLike2D[NumberType],
|
||||
_ArrayLike3D[NumberType],
|
||||
_ArrayLike4D[NumberType],
|
||||
]
|
||||
@@ -0,0 +1,40 @@
|
||||
"""PyVista dataset types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from pyvista.core.composite import MultiBlock
|
||||
from pyvista.core.dataobject import DataObject
|
||||
from pyvista.core.dataset import DataSet
|
||||
from pyvista.core.grid import Grid
|
||||
from pyvista.core.pointset import PointGrid
|
||||
from pyvista.core.pointset import PolyData
|
||||
from pyvista.core.pointset import UnstructuredGrid
|
||||
from pyvista.core.pointset import _PointSet
|
||||
|
||||
_GridType = TypeVar('_GridType', bound=Grid)
|
||||
_GridType.__doc__ = """Type variable for PyVista ``Grid`` classes."""
|
||||
|
||||
_PointGridType = TypeVar('_PointGridType', bound=PointGrid)
|
||||
_PointGridType.__doc__ = """Type variable for PyVista ``PointGrid`` classes."""
|
||||
|
||||
_PointSetType = TypeVar('_PointSetType', bound=_PointSet)
|
||||
_PointSetType.__doc__ = """Type variable for PyVista ``PointSet`` classes."""
|
||||
|
||||
_DataSetType = TypeVar('_DataSetType', bound=DataSet)
|
||||
_DataSetType.__doc__ = """Type variable for :class:`~pyvista.DataSet` classes."""
|
||||
|
||||
_DataSetOrMultiBlockType = TypeVar('_DataSetOrMultiBlockType', bound=Union[DataSet, MultiBlock])
|
||||
_DataSetOrMultiBlockType.__doc__ = (
|
||||
"""Type variable for :class:`~pyvista.DataSet` or :class:`~pyvista.MultiBlock` classes."""
|
||||
)
|
||||
|
||||
_DataObjectType = TypeVar('_DataObjectType', bound=DataObject)
|
||||
_DataObjectType.__doc__ = """Type variable for :class:`~pyvista.DataObject` classes."""
|
||||
|
||||
|
||||
# Undocumented
|
||||
_PolyDataType = TypeVar('_PolyDataType', bound=PolyData) # noqa: PYI018
|
||||
_UnstructuredGridType = TypeVar('_UnstructuredGridType', bound=UnstructuredGrid) # noqa: PYI018
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Input validation functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .check import check_contains as check_contains
|
||||
from .check import check_finite as check_finite
|
||||
from .check import check_greater_than as check_greater_than
|
||||
from .check import check_instance as check_instance
|
||||
from .check import check_integer as check_integer
|
||||
from .check import check_iterable as check_iterable
|
||||
from .check import check_iterable_items as check_iterable_items
|
||||
from .check import check_length as check_length
|
||||
from .check import check_less_than as check_less_than
|
||||
from .check import check_ndim as check_ndim
|
||||
from .check import check_nonnegative as check_nonnegative
|
||||
from .check import check_number as check_number
|
||||
from .check import check_range as check_range
|
||||
from .check import check_real as check_real
|
||||
from .check import check_sequence as check_sequence
|
||||
from .check import check_shape as check_shape
|
||||
from .check import check_sorted as check_sorted
|
||||
from .check import check_string as check_string
|
||||
from .check import check_subdtype as check_subdtype
|
||||
from .check import check_type as check_type
|
||||
from .validate import validate_array as validate_array
|
||||
from .validate import validate_array3 as validate_array3
|
||||
from .validate import validate_arrayN as validate_arrayN
|
||||
from .validate import validate_arrayN_unsigned as validate_arrayN_unsigned
|
||||
from .validate import validate_arrayNx3 as validate_arrayNx3
|
||||
from .validate import validate_axes as validate_axes
|
||||
from .validate import validate_data_range as validate_data_range
|
||||
from .validate import validate_dimensionality as validate_dimensionality
|
||||
from .validate import validate_number as validate_number
|
||||
from .validate import validate_rotation as validate_rotation
|
||||
from .validate import validate_transform3x3 as validate_transform3x3
|
||||
from .validate import validate_transform4x4 as validate_transform4x4
|
||||
@@ -0,0 +1,146 @@
|
||||
"""Array casting functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista.core._typing_core import ArrayLike
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
from pyvista.core._typing_core._aliases import _ArrayLikeOrScalar
|
||||
from pyvista.core._typing_core._array_like import NumberType
|
||||
from pyvista.core._typing_core._array_like import _FiniteNestedList
|
||||
from pyvista.core._typing_core._array_like import _FiniteNestedTuple
|
||||
|
||||
|
||||
def _cast_to_list(
|
||||
arr: _ArrayLikeOrScalar[NumberType],
|
||||
) -> Union[NumberType, _FiniteNestedList[NumberType]]:
|
||||
"""Cast an array to a nested list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arr : float | ArrayLike[float]
|
||||
Array to cast.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List or nested list array.
|
||||
|
||||
"""
|
||||
return _cast_to_numpy(arr).tolist()
|
||||
|
||||
|
||||
def _cast_to_tuple(
|
||||
arr: ArrayLike[NumberType],
|
||||
) -> Union[NumberType, _FiniteNestedTuple[NumberType]]:
|
||||
"""Cast an array to a nested tuple.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arr : float | ArrayLike[float]
|
||||
Array to cast.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
Tuple or nested tuple array.
|
||||
|
||||
"""
|
||||
arr = _cast_to_numpy(arr).tolist()
|
||||
|
||||
def _to_tuple(s):
|
||||
return tuple(_to_tuple(i) for i in s) if isinstance(s, list) else s
|
||||
|
||||
return _to_tuple(arr)
|
||||
|
||||
|
||||
def _cast_to_numpy(
|
||||
arr: _ArrayLikeOrScalar[NumberType],
|
||||
/,
|
||||
*,
|
||||
as_any: bool = True,
|
||||
dtype: Optional[npt.DTypeLike] = None,
|
||||
copy: bool = False,
|
||||
must_be_real: bool = False,
|
||||
) -> NumpyArray[NumberType]:
|
||||
"""Cast array to a NumPy ndarray.
|
||||
|
||||
Object arrays are not allowed but the dtype is otherwise unchecked by default.
|
||||
String arrays and complex numbers are therefore allowed.
|
||||
|
||||
.. warning::
|
||||
|
||||
Arrays intended for use with vtk should set ``must_be_real=True``
|
||||
since ``numpy_to_vtk`` uses the array values directly without
|
||||
checking for complex arrays.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arr : float | ArrayLike[float]
|
||||
Array to cast.
|
||||
|
||||
as_any : bool, default: True
|
||||
Allow subclasses of ``np.ndarray`` to pass through without
|
||||
making a copy.
|
||||
|
||||
dtype : npt.typing.DTypeLike, optional
|
||||
The data-type of the returned array.
|
||||
|
||||
copy : bool, default: False
|
||||
If ``True``, a copy of the array is returned. A copy is always
|
||||
returned if the array:
|
||||
|
||||
* is a nested sequence
|
||||
* is a subclass of ``np.ndarray`` and ``as_any`` is ``False``.
|
||||
|
||||
must_be_real : bool, default: True
|
||||
Raise a ``TypeError`` if the array does not have real numbers, i.e.
|
||||
its data type is not integer or floating.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If input cannot be cast as a NumPy ndarray.
|
||||
TypeError
|
||||
If an object array is created or if the data is not real numbers
|
||||
and ``must_be_real`` is ``True``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
NumPy ndarray.
|
||||
|
||||
"""
|
||||
# needed to support numpy <1.25
|
||||
# needed to support vtk 9.0.3
|
||||
# check for removal when support for vtk 9.0.3 is removed
|
||||
try:
|
||||
VisibleDeprecationWarning = np.exceptions.VisibleDeprecationWarning
|
||||
except AttributeError:
|
||||
# we only type for newer numpy, and this branch only touched in older numpy
|
||||
if not TYPE_CHECKING:
|
||||
VisibleDeprecationWarning = np.VisibleDeprecationWarning
|
||||
|
||||
try:
|
||||
out = np.asanyarray(arr, dtype=dtype) if as_any else np.asarray(arr, dtype=dtype)
|
||||
|
||||
if copy and out is arr:
|
||||
# we requested a copy but didn't end up with one
|
||||
out = out.copy()
|
||||
except (ValueError, VisibleDeprecationWarning) as e:
|
||||
msg = f'Input cannot be cast as {np.ndarray}.'
|
||||
raise ValueError(msg) from e
|
||||
if must_be_real and not issubclass(out.dtype.type, (np.floating, np.integer)):
|
||||
msg = f'Array must have real numbers. Got dtype {out.dtype.type}'
|
||||
raise TypeError(msg)
|
||||
elif out.dtype.name == 'object':
|
||||
msg = f'Object arrays are not supported. Got {arr} when casting to a NumPy array.'
|
||||
raise TypeError(msg)
|
||||
return out
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,752 @@
|
||||
"""Limited imports from VTK (excludes any GL-dependent).
|
||||
|
||||
These are the modules within VTK that must be loaded across pyvista's
|
||||
core API. Here, we attempt to import modules using the ``vtkmodules``
|
||||
package, which lets us only have to import from select modules and not
|
||||
the entire library.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import sys
|
||||
from typing import NamedTuple
|
||||
import warnings
|
||||
|
||||
from vtkmodules.vtkCommonCore import vtkInformation as vtkInformation
|
||||
from vtkmodules.vtkCommonCore import vtkVersion as vtkVersion
|
||||
from vtkmodules.vtkImagingSources import vtkImageEllipsoidSource as vtkImageEllipsoidSource
|
||||
from vtkmodules.vtkImagingSources import vtkImageGaussianSource as vtkImageGaussianSource
|
||||
from vtkmodules.vtkImagingSources import vtkImageGridSource as vtkImageGridSource
|
||||
from vtkmodules.vtkImagingSources import vtkImageMandelbrotSource as vtkImageMandelbrotSource
|
||||
from vtkmodules.vtkImagingSources import vtkImageNoiseSource as vtkImageNoiseSource
|
||||
from vtkmodules.vtkImagingSources import vtkImageSinusoidSource as vtkImageSinusoidSource
|
||||
|
||||
# vtkExtractEdges moved from vtkFiltersExtraction to vtkFiltersCore in
|
||||
# VTK commit d9981b9aeb93b42d1371c6e295d76bfdc18430bd
|
||||
try:
|
||||
from vtkmodules.vtkFiltersCore import vtkExtractEdges as vtkExtractEdges
|
||||
except ImportError:
|
||||
from vtkmodules.vtkFiltersExtraction import ( # type: ignore[attr-defined, no-redef]
|
||||
vtkExtractEdges as vtkExtractEdges,
|
||||
)
|
||||
|
||||
# vtkCellTreeLocator moved from vtkFiltersGeneral to vtkCommonDataModel in
|
||||
# VTK commit 4a29e6f7dd9acb460644fe487d2e80aac65f7be9
|
||||
try:
|
||||
from vtkmodules.vtkCommonDataModel import vtkCellTreeLocator as vtkCellTreeLocator
|
||||
except ImportError:
|
||||
from vtkmodules.vtkFiltersGeneral import ( # type: ignore[attr-defined, no-redef]
|
||||
vtkCellTreeLocator as vtkCellTreeLocator,
|
||||
)
|
||||
|
||||
from vtkmodules.numpy_interface.dataset_adapter import VTKArray as VTKArray
|
||||
from vtkmodules.numpy_interface.dataset_adapter import VTKObjectWrapper as VTKObjectWrapper
|
||||
from vtkmodules.numpy_interface.dataset_adapter import numpyTovtkDataArray as numpyTovtkDataArray
|
||||
from vtkmodules.util.numpy_support import get_vtk_array_type as get_vtk_array_type
|
||||
from vtkmodules.util.numpy_support import numpy_to_vtk as numpy_to_vtk
|
||||
from vtkmodules.util.numpy_support import numpy_to_vtkIdTypeArray as numpy_to_vtkIdTypeArray
|
||||
from vtkmodules.util.numpy_support import vtk_to_numpy as vtk_to_numpy
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.util.pickle_support import (
|
||||
serialize_VTK_data_object as serialize_VTK_data_object,
|
||||
)
|
||||
|
||||
from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase as VTKPythonAlgorithmBase
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkKochanekSpline as vtkKochanekSpline
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricBohemianDome as vtkParametricBohemianDome,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricBour as vtkParametricBour
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricBoy as vtkParametricBoy
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricCatalanMinimal as vtkParametricCatalanMinimal,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricConicSpiral as vtkParametricConicSpiral,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricCrossCap as vtkParametricCrossCap,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricDini as vtkParametricDini
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricEllipsoid as vtkParametricEllipsoid,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricEnneper as vtkParametricEnneper
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricFigure8Klein as vtkParametricFigure8Klein,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricFunction as vtkParametricFunction,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricHenneberg as vtkParametricHenneberg,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricKlein as vtkParametricKlein
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricKuen as vtkParametricKuen
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricMobius as vtkParametricMobius
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricPluckerConoid as vtkParametricPluckerConoid,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricPseudosphere as vtkParametricPseudosphere,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricRandomHills as vtkParametricRandomHills,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricRoman as vtkParametricRoman
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricSpline as vtkParametricSpline
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricSuperEllipsoid as vtkParametricSuperEllipsoid,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import (
|
||||
vtkParametricSuperToroid as vtkParametricSuperToroid,
|
||||
)
|
||||
from vtkmodules.vtkCommonComputationalGeometry import vtkParametricTorus as vtkParametricTorus
|
||||
from vtkmodules.vtkCommonCore import VTK_ARIAL as VTK_ARIAL
|
||||
from vtkmodules.vtkCommonCore import VTK_BIT as VTK_BIT
|
||||
from vtkmodules.vtkCommonCore import VTK_CHAR as VTK_CHAR
|
||||
from vtkmodules.vtkCommonCore import VTK_COURIER as VTK_COURIER
|
||||
from vtkmodules.vtkCommonCore import VTK_DOUBLE as VTK_DOUBLE
|
||||
from vtkmodules.vtkCommonCore import VTK_FLOAT as VTK_FLOAT
|
||||
from vtkmodules.vtkCommonCore import VTK_FONT_FILE as VTK_FONT_FILE
|
||||
from vtkmodules.vtkCommonCore import VTK_ID_TYPE as VTK_ID_TYPE
|
||||
from vtkmodules.vtkCommonCore import VTK_INT as VTK_INT
|
||||
from vtkmodules.vtkCommonCore import VTK_LONG as VTK_LONG
|
||||
from vtkmodules.vtkCommonCore import VTK_LONG_LONG as VTK_LONG_LONG
|
||||
from vtkmodules.vtkCommonCore import VTK_SHORT as VTK_SHORT
|
||||
from vtkmodules.vtkCommonCore import VTK_SIGNED_CHAR as VTK_SIGNED_CHAR
|
||||
from vtkmodules.vtkCommonCore import VTK_STRING as VTK_STRING
|
||||
from vtkmodules.vtkCommonCore import VTK_TIMES as VTK_TIMES
|
||||
from vtkmodules.vtkCommonCore import VTK_UNSIGNED_CHAR as VTK_UNSIGNED_CHAR
|
||||
from vtkmodules.vtkCommonCore import VTK_UNSIGNED_INT as VTK_UNSIGNED_INT
|
||||
from vtkmodules.vtkCommonCore import VTK_UNSIGNED_LONG as VTK_UNSIGNED_LONG
|
||||
from vtkmodules.vtkCommonCore import VTK_UNSIGNED_LONG_LONG as VTK_UNSIGNED_LONG_LONG
|
||||
from vtkmodules.vtkCommonCore import VTK_UNSIGNED_SHORT as VTK_UNSIGNED_SHORT
|
||||
from vtkmodules.vtkCommonCore import buffer_shared as buffer_shared # type: ignore[attr-defined]
|
||||
from vtkmodules.vtkCommonCore import mutable as mutable
|
||||
from vtkmodules.vtkCommonCore import reference as reference
|
||||
from vtkmodules.vtkCommonCore import vtkAbstractArray as vtkAbstractArray
|
||||
from vtkmodules.vtkCommonCore import vtkBitArray as vtkBitArray
|
||||
from vtkmodules.vtkCommonCore import vtkCharArray as vtkCharArray
|
||||
from vtkmodules.vtkCommonCore import vtkCommand as vtkCommand
|
||||
from vtkmodules.vtkCommonCore import vtkDataArray as vtkDataArray
|
||||
from vtkmodules.vtkCommonCore import vtkDoubleArray as vtkDoubleArray
|
||||
from vtkmodules.vtkCommonCore import vtkFileOutputWindow as vtkFileOutputWindow
|
||||
from vtkmodules.vtkCommonCore import vtkFloatArray as vtkFloatArray
|
||||
from vtkmodules.vtkCommonCore import vtkIdList as vtkIdList
|
||||
from vtkmodules.vtkCommonCore import vtkIdTypeArray as vtkIdTypeArray
|
||||
from vtkmodules.vtkCommonCore import vtkIntArray as vtkIntArray
|
||||
from vtkmodules.vtkCommonCore import vtkLogger as vtkLogger
|
||||
from vtkmodules.vtkCommonCore import vtkLongArray as vtkLongArray
|
||||
from vtkmodules.vtkCommonCore import vtkLongLongArray as vtkLongLongArray
|
||||
from vtkmodules.vtkCommonCore import vtkLookupTable as vtkLookupTable
|
||||
from vtkmodules.vtkCommonCore import vtkMath as vtkMath
|
||||
from vtkmodules.vtkCommonCore import vtkOutputWindow as vtkOutputWindow
|
||||
from vtkmodules.vtkCommonCore import vtkPoints as vtkPoints
|
||||
from vtkmodules.vtkCommonCore import vtkShortArray as vtkShortArray
|
||||
from vtkmodules.vtkCommonCore import vtkSignedCharArray as vtkSignedCharArray
|
||||
from vtkmodules.vtkCommonCore import vtkStringArray as vtkStringArray
|
||||
from vtkmodules.vtkCommonCore import vtkStringOutputWindow as vtkStringOutputWindow
|
||||
from vtkmodules.vtkCommonCore import vtkTypeInt32Array as vtkTypeInt32Array
|
||||
from vtkmodules.vtkCommonCore import vtkTypeInt64Array as vtkTypeInt64Array
|
||||
from vtkmodules.vtkCommonCore import vtkTypeUInt32Array as vtkTypeUInt32Array
|
||||
from vtkmodules.vtkCommonCore import vtkUnsignedCharArray as vtkUnsignedCharArray
|
||||
from vtkmodules.vtkCommonCore import vtkUnsignedIntArray as vtkUnsignedIntArray
|
||||
from vtkmodules.vtkCommonCore import vtkUnsignedLongArray as vtkUnsignedLongArray
|
||||
from vtkmodules.vtkCommonCore import vtkUnsignedLongLongArray as vtkUnsignedLongLongArray
|
||||
from vtkmodules.vtkCommonCore import vtkUnsignedShortArray as vtkUnsignedShortArray
|
||||
from vtkmodules.vtkCommonCore import vtkWeakReference as vtkWeakReference
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BEZIER_CURVE as VTK_BEZIER_CURVE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BEZIER_HEXAHEDRON as VTK_BEZIER_HEXAHEDRON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BEZIER_PYRAMID as VTK_BEZIER_PYRAMID
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BEZIER_QUADRILATERAL as VTK_BEZIER_QUADRILATERAL
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BEZIER_TETRAHEDRON as VTK_BEZIER_TETRAHEDRON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BEZIER_TRIANGLE as VTK_BEZIER_TRIANGLE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BEZIER_WEDGE as VTK_BEZIER_WEDGE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BIQUADRATIC_QUAD as VTK_BIQUADRATIC_QUAD
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON as VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
VTK_BIQUADRATIC_QUADRATIC_WEDGE as VTK_BIQUADRATIC_QUADRATIC_WEDGE,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import VTK_BIQUADRATIC_TRIANGLE as VTK_BIQUADRATIC_TRIANGLE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_CONVEX_POINT_SET as VTK_CONVEX_POINT_SET
|
||||
from vtkmodules.vtkCommonDataModel import VTK_CUBIC_LINE as VTK_CUBIC_LINE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_EMPTY_CELL as VTK_EMPTY_CELL
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HEXAGONAL_PRISM as VTK_HEXAGONAL_PRISM
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HEXAHEDRON as VTK_HEXAHEDRON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HIGHER_ORDER_EDGE as VTK_HIGHER_ORDER_EDGE
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
VTK_HIGHER_ORDER_HEXAHEDRON as VTK_HIGHER_ORDER_HEXAHEDRON,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HIGHER_ORDER_POLYGON as VTK_HIGHER_ORDER_POLYGON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HIGHER_ORDER_PYRAMID as VTK_HIGHER_ORDER_PYRAMID
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HIGHER_ORDER_QUAD as VTK_HIGHER_ORDER_QUAD
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
VTK_HIGHER_ORDER_TETRAHEDRON as VTK_HIGHER_ORDER_TETRAHEDRON,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HIGHER_ORDER_TRIANGLE as VTK_HIGHER_ORDER_TRIANGLE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_HIGHER_ORDER_WEDGE as VTK_HIGHER_ORDER_WEDGE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LAGRANGE_CURVE as VTK_LAGRANGE_CURVE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LAGRANGE_HEXAHEDRON as VTK_LAGRANGE_HEXAHEDRON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LAGRANGE_PYRAMID as VTK_LAGRANGE_PYRAMID
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LAGRANGE_QUADRILATERAL as VTK_LAGRANGE_QUADRILATERAL
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LAGRANGE_TETRAHEDRON as VTK_LAGRANGE_TETRAHEDRON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LAGRANGE_TRIANGLE as VTK_LAGRANGE_TRIANGLE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LAGRANGE_WEDGE as VTK_LAGRANGE_WEDGE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_LINE as VTK_LINE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_PARAMETRIC_CURVE as VTK_PARAMETRIC_CURVE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_PARAMETRIC_HEX_REGION as VTK_PARAMETRIC_HEX_REGION
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
VTK_PARAMETRIC_QUAD_SURFACE as VTK_PARAMETRIC_QUAD_SURFACE,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import VTK_PARAMETRIC_SURFACE as VTK_PARAMETRIC_SURFACE
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
VTK_PARAMETRIC_TETRA_REGION as VTK_PARAMETRIC_TETRA_REGION,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import VTK_PARAMETRIC_TRI_SURFACE as VTK_PARAMETRIC_TRI_SURFACE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_PENTAGONAL_PRISM as VTK_PENTAGONAL_PRISM
|
||||
from vtkmodules.vtkCommonDataModel import VTK_PIXEL as VTK_PIXEL
|
||||
from vtkmodules.vtkCommonDataModel import VTK_POLY_LINE as VTK_POLY_LINE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_POLY_VERTEX as VTK_POLY_VERTEX
|
||||
from vtkmodules.vtkCommonDataModel import VTK_POLYGON as VTK_POLYGON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_POLYHEDRON as VTK_POLYHEDRON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_PYRAMID as VTK_PYRAMID
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUAD as VTK_QUAD
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_EDGE as VTK_QUADRATIC_EDGE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_HEXAHEDRON as VTK_QUADRATIC_HEXAHEDRON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_LINEAR_QUAD as VTK_QUADRATIC_LINEAR_QUAD
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_LINEAR_WEDGE as VTK_QUADRATIC_LINEAR_WEDGE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_POLYGON as VTK_QUADRATIC_POLYGON
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_PYRAMID as VTK_QUADRATIC_PYRAMID
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_QUAD as VTK_QUADRATIC_QUAD
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_TETRA as VTK_QUADRATIC_TETRA
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_TRIANGLE as VTK_QUADRATIC_TRIANGLE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_QUADRATIC_WEDGE as VTK_QUADRATIC_WEDGE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_TETRA as VTK_TETRA
|
||||
from vtkmodules.vtkCommonDataModel import VTK_TRIANGLE as VTK_TRIANGLE
|
||||
from vtkmodules.vtkCommonDataModel import VTK_TRIANGLE_STRIP as VTK_TRIANGLE_STRIP
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
VTK_TRIQUADRATIC_HEXAHEDRON as VTK_TRIQUADRATIC_HEXAHEDRON,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import VTK_VERTEX as VTK_VERTEX
|
||||
from vtkmodules.vtkCommonDataModel import VTK_VOXEL as VTK_VOXEL
|
||||
from vtkmodules.vtkCommonDataModel import VTK_WEDGE as VTK_WEDGE
|
||||
from vtkmodules.vtkCommonDataModel import vtkAbstractCellLocator as vtkAbstractCellLocator
|
||||
from vtkmodules.vtkCommonDataModel import vtkBezierCurve as vtkBezierCurve
|
||||
from vtkmodules.vtkCommonDataModel import vtkBezierHexahedron as vtkBezierHexahedron
|
||||
from vtkmodules.vtkCommonDataModel import vtkBezierQuadrilateral as vtkBezierQuadrilateral
|
||||
from vtkmodules.vtkCommonDataModel import vtkBezierTetra as vtkBezierTetra
|
||||
from vtkmodules.vtkCommonDataModel import vtkBezierTriangle as vtkBezierTriangle
|
||||
from vtkmodules.vtkCommonDataModel import vtkBezierWedge as vtkBezierWedge
|
||||
from vtkmodules.vtkCommonDataModel import vtkBiQuadraticQuad as vtkBiQuadraticQuad
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
vtkBiQuadraticQuadraticHexahedron as vtkBiQuadraticQuadraticHexahedron,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
vtkBiQuadraticQuadraticWedge as vtkBiQuadraticQuadraticWedge,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import vtkBiQuadraticTriangle as vtkBiQuadraticTriangle
|
||||
from vtkmodules.vtkCommonDataModel import vtkCell as vtkCell
|
||||
from vtkmodules.vtkCommonDataModel import vtkCellArray as vtkCellArray
|
||||
from vtkmodules.vtkCommonDataModel import vtkCellLocator as vtkCellLocator
|
||||
from vtkmodules.vtkCommonDataModel import vtkColor3ub as vtkColor3ub
|
||||
from vtkmodules.vtkCommonDataModel import vtkCompositeDataSet as vtkCompositeDataSet
|
||||
from vtkmodules.vtkCommonDataModel import vtkConvexPointSet as vtkConvexPointSet
|
||||
from vtkmodules.vtkCommonDataModel import vtkCubicLine as vtkCubicLine
|
||||
from vtkmodules.vtkCommonDataModel import vtkDataObject as vtkDataObject
|
||||
from vtkmodules.vtkCommonDataModel import vtkDataSet as vtkDataSet
|
||||
from vtkmodules.vtkCommonDataModel import vtkDataSetAttributes as vtkDataSetAttributes
|
||||
from vtkmodules.vtkCommonDataModel import vtkEmptyCell as vtkEmptyCell
|
||||
from vtkmodules.vtkCommonDataModel import vtkExplicitStructuredGrid as vtkExplicitStructuredGrid
|
||||
from vtkmodules.vtkCommonDataModel import vtkFieldData as vtkFieldData
|
||||
from vtkmodules.vtkCommonDataModel import vtkGenericCell as vtkGenericCell
|
||||
from vtkmodules.vtkCommonDataModel import vtkHexagonalPrism as vtkHexagonalPrism
|
||||
from vtkmodules.vtkCommonDataModel import vtkHexahedron as vtkHexahedron
|
||||
from vtkmodules.vtkCommonDataModel import vtkImageData as vtkImageData
|
||||
from vtkmodules.vtkCommonDataModel import vtkImplicitFunction as vtkImplicitFunction
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
vtkIterativeClosestPointTransform as vtkIterativeClosestPointTransform,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import vtkLagrangeCurve as vtkLagrangeCurve
|
||||
from vtkmodules.vtkCommonDataModel import vtkLagrangeHexahedron as vtkLagrangeHexahedron
|
||||
from vtkmodules.vtkCommonDataModel import vtkLagrangeQuadrilateral as vtkLagrangeQuadrilateral
|
||||
from vtkmodules.vtkCommonDataModel import vtkLagrangeTriangle as vtkLagrangeTriangle
|
||||
from vtkmodules.vtkCommonDataModel import vtkLagrangeWedge as vtkLagrangeWedge
|
||||
from vtkmodules.vtkCommonDataModel import vtkLine as vtkLine
|
||||
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet as vtkMultiBlockDataSet
|
||||
from vtkmodules.vtkCommonDataModel import vtkNonMergingPointLocator as vtkNonMergingPointLocator
|
||||
from vtkmodules.vtkCommonDataModel import vtkPartitionedDataSet as vtkPartitionedDataSet
|
||||
from vtkmodules.vtkCommonDataModel import vtkPentagonalPrism as vtkPentagonalPrism
|
||||
from vtkmodules.vtkCommonDataModel import vtkPerlinNoise as vtkPerlinNoise
|
||||
from vtkmodules.vtkCommonDataModel import vtkPiecewiseFunction as vtkPiecewiseFunction
|
||||
from vtkmodules.vtkCommonDataModel import vtkPixel as vtkPixel
|
||||
from vtkmodules.vtkCommonDataModel import vtkPlane as vtkPlane
|
||||
from vtkmodules.vtkCommonDataModel import vtkPlaneCollection as vtkPlaneCollection
|
||||
from vtkmodules.vtkCommonDataModel import vtkPlanes as vtkPlanes
|
||||
from vtkmodules.vtkCommonDataModel import vtkPointLocator as vtkPointLocator
|
||||
from vtkmodules.vtkCommonDataModel import vtkPointSet as vtkPointSet
|
||||
from vtkmodules.vtkCommonDataModel import vtkPolyData as vtkPolyData
|
||||
from vtkmodules.vtkCommonDataModel import vtkPolygon as vtkPolygon
|
||||
from vtkmodules.vtkCommonDataModel import vtkPolyhedron as vtkPolyhedron
|
||||
from vtkmodules.vtkCommonDataModel import vtkPolyLine as vtkPolyLine
|
||||
from vtkmodules.vtkCommonDataModel import vtkPolyPlane as vtkPolyPlane
|
||||
from vtkmodules.vtkCommonDataModel import vtkPolyVertex as vtkPolyVertex
|
||||
from vtkmodules.vtkCommonDataModel import vtkPyramid as vtkPyramid
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuad as vtkQuad
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticEdge as vtkQuadraticEdge
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticHexahedron as vtkQuadraticHexahedron
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearQuad as vtkQuadraticLinearQuad
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticLinearWedge as vtkQuadraticLinearWedge
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticPolygon as vtkQuadraticPolygon
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticPyramid as vtkQuadraticPyramid
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticQuad as vtkQuadraticQuad
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticTetra as vtkQuadraticTetra
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticTriangle as vtkQuadraticTriangle
|
||||
from vtkmodules.vtkCommonDataModel import vtkQuadraticWedge as vtkQuadraticWedge
|
||||
from vtkmodules.vtkCommonDataModel import vtkRectf as vtkRectf
|
||||
from vtkmodules.vtkCommonDataModel import vtkRectilinearGrid as vtkRectilinearGrid
|
||||
from vtkmodules.vtkCommonDataModel import vtkSelection as vtkSelection
|
||||
from vtkmodules.vtkCommonDataModel import vtkSelectionNode as vtkSelectionNode
|
||||
from vtkmodules.vtkCommonDataModel import vtkStaticCellLocator as vtkStaticCellLocator
|
||||
from vtkmodules.vtkCommonDataModel import vtkStaticPointLocator as vtkStaticPointLocator
|
||||
from vtkmodules.vtkCommonDataModel import vtkStructuredGrid as vtkStructuredGrid
|
||||
from vtkmodules.vtkCommonDataModel import vtkStructuredPoints as vtkStructuredPoints
|
||||
from vtkmodules.vtkCommonDataModel import vtkTable as vtkTable
|
||||
from vtkmodules.vtkCommonDataModel import vtkTetra as vtkTetra
|
||||
from vtkmodules.vtkCommonDataModel import vtkTriangle as vtkTriangle
|
||||
from vtkmodules.vtkCommonDataModel import vtkTriangleStrip as vtkTriangleStrip
|
||||
from vtkmodules.vtkCommonDataModel import vtkTriQuadraticHexahedron as vtkTriQuadraticHexahedron
|
||||
from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid as vtkUnstructuredGrid
|
||||
from vtkmodules.vtkCommonDataModel import vtkVertex as vtkVertex
|
||||
from vtkmodules.vtkCommonDataModel import vtkVoxel as vtkVoxel
|
||||
from vtkmodules.vtkCommonDataModel import vtkWedge as vtkWedge
|
||||
|
||||
with contextlib.suppress(ImportError): # Introduced prior to VTK 9.2
|
||||
from vtkmodules.vtkCommonDataModel import VTK_TRIQUADRATIC_PYRAMID as VTK_TRIQUADRATIC_PYRAMID
|
||||
from vtkmodules.vtkCommonDataModel import vtkTriQuadraticPyramid as vtkTriQuadraticPyramid
|
||||
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkAlgorithm as vtkAlgorithm
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkAlgorithmOutput as vtkAlgorithmOutput
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkCompositeDataPipeline as vtkCompositeDataPipeline
|
||||
from vtkmodules.vtkCommonExecutionModel import vtkImageToStructuredGrid as vtkImageToStructuredGrid
|
||||
from vtkmodules.vtkCommonMath import vtkMatrix3x3 as vtkMatrix3x3
|
||||
from vtkmodules.vtkCommonMath import vtkMatrix4x4 as vtkMatrix4x4
|
||||
from vtkmodules.vtkCommonTransforms import vtkTransform as vtkTransform
|
||||
from vtkmodules.vtkDomainsChemistry import vtkProteinRibbonFilter as vtkProteinRibbonFilter
|
||||
from vtkmodules.vtkFiltersCore import VTK_BEST_FITTING_PLANE as VTK_BEST_FITTING_PLANE
|
||||
from vtkmodules.vtkFiltersCore import vtkAppendArcLength as vtkAppendArcLength
|
||||
from vtkmodules.vtkFiltersCore import vtkAppendFilter as vtkAppendFilter
|
||||
from vtkmodules.vtkFiltersCore import vtkAppendPolyData as vtkAppendPolyData
|
||||
from vtkmodules.vtkFiltersCore import vtkCellCenters as vtkCellCenters
|
||||
from vtkmodules.vtkFiltersCore import vtkCellDataToPointData as vtkCellDataToPointData
|
||||
from vtkmodules.vtkFiltersCore import vtkCenterOfMass as vtkCenterOfMass
|
||||
from vtkmodules.vtkFiltersCore import vtkCleanPolyData as vtkCleanPolyData
|
||||
from vtkmodules.vtkFiltersCore import vtkClipPolyData as vtkClipPolyData
|
||||
from vtkmodules.vtkFiltersCore import vtkConnectivityFilter as vtkConnectivityFilter
|
||||
from vtkmodules.vtkFiltersCore import vtkContourFilter as vtkContourFilter
|
||||
from vtkmodules.vtkFiltersCore import vtkCutter as vtkCutter
|
||||
from vtkmodules.vtkFiltersCore import vtkDecimatePolylineFilter as vtkDecimatePolylineFilter
|
||||
from vtkmodules.vtkFiltersCore import vtkDecimatePro as vtkDecimatePro
|
||||
from vtkmodules.vtkFiltersCore import vtkDelaunay2D as vtkDelaunay2D
|
||||
from vtkmodules.vtkFiltersCore import vtkDelaunay3D as vtkDelaunay3D
|
||||
from vtkmodules.vtkFiltersCore import vtkElevationFilter as vtkElevationFilter
|
||||
from vtkmodules.vtkFiltersCore import (
|
||||
vtkExplicitStructuredGridToUnstructuredGrid as vtkExplicitStructuredGridToUnstructuredGrid,
|
||||
)
|
||||
from vtkmodules.vtkFiltersCore import vtkFeatureEdges as vtkFeatureEdges
|
||||
from vtkmodules.vtkFiltersCore import vtkFlyingEdges3D as vtkFlyingEdges3D
|
||||
from vtkmodules.vtkFiltersCore import vtkGlyph3D as vtkGlyph3D
|
||||
from vtkmodules.vtkFiltersCore import vtkImplicitPolyDataDistance as vtkImplicitPolyDataDistance
|
||||
from vtkmodules.vtkFiltersCore import vtkMarchingCubes as vtkMarchingCubes
|
||||
from vtkmodules.vtkFiltersCore import vtkMassProperties as vtkMassProperties
|
||||
|
||||
with contextlib.suppress(ImportError): # Introduced VTK 9.4
|
||||
from vtkmodules.vtkFiltersCore import vtkOrientPolyData as vtkOrientPolyData
|
||||
from vtkmodules.vtkFiltersCore import vtkPointDataToCellData as vtkPointDataToCellData
|
||||
from vtkmodules.vtkFiltersCore import vtkPolyDataNormals as vtkPolyDataNormals
|
||||
from vtkmodules.vtkFiltersCore import vtkQuadricDecimation as vtkQuadricDecimation
|
||||
from vtkmodules.vtkFiltersCore import vtkResampleWithDataSet as vtkResampleWithDataSet
|
||||
from vtkmodules.vtkFiltersCore import vtkReverseSense as vtkReverseSense
|
||||
from vtkmodules.vtkFiltersCore import vtkSmoothPolyDataFilter as vtkSmoothPolyDataFilter
|
||||
from vtkmodules.vtkFiltersCore import vtkStripper as vtkStripper
|
||||
from vtkmodules.vtkFiltersCore import vtkThreshold as vtkThreshold
|
||||
from vtkmodules.vtkFiltersCore import vtkTriangleFilter as vtkTriangleFilter
|
||||
from vtkmodules.vtkFiltersCore import vtkTubeFilter as vtkTubeFilter
|
||||
from vtkmodules.vtkFiltersCore import (
|
||||
vtkUnstructuredGridToExplicitStructuredGrid as vtkUnstructuredGridToExplicitStructuredGrid,
|
||||
)
|
||||
from vtkmodules.vtkFiltersCore import (
|
||||
vtkWindowedSincPolyDataFilter as vtkWindowedSincPolyDataFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersExtraction import vtkExtractCellsByType as vtkExtractCellsByType
|
||||
from vtkmodules.vtkFiltersExtraction import vtkExtractGeometry as vtkExtractGeometry
|
||||
from vtkmodules.vtkFiltersExtraction import vtkExtractGrid as vtkExtractGrid
|
||||
from vtkmodules.vtkFiltersExtraction import vtkExtractSelection as vtkExtractSelection
|
||||
from vtkmodules.vtkFiltersFlowPaths import (
|
||||
vtkEvenlySpacedStreamlines2D as vtkEvenlySpacedStreamlines2D,
|
||||
)
|
||||
from vtkmodules.vtkFiltersFlowPaths import vtkStreamTracer as vtkStreamTracer
|
||||
|
||||
with contextlib.suppress(ImportError): # Introduced VTK v9.1.0
|
||||
from vtkmodules.vtkFiltersGeneral import vtkRemovePolyData as vtkRemovePolyData
|
||||
|
||||
from vtkmodules.vtkFiltersGeneral import vtkAxes as vtkAxes
|
||||
from vtkmodules.vtkFiltersGeneral import (
|
||||
vtkBooleanOperationPolyDataFilter as vtkBooleanOperationPolyDataFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersGeneral import vtkBoxClipDataSet as vtkBoxClipDataSet
|
||||
from vtkmodules.vtkFiltersGeneral import vtkClipClosedSurface as vtkClipClosedSurface
|
||||
from vtkmodules.vtkFiltersGeneral import vtkContourTriangulator as vtkContourTriangulator
|
||||
from vtkmodules.vtkFiltersGeneral import vtkCursor3D as vtkCursor3D
|
||||
from vtkmodules.vtkFiltersGeneral import vtkCurvatures as vtkCurvatures
|
||||
from vtkmodules.vtkFiltersGeneral import vtkDataSetTriangleFilter as vtkDataSetTriangleFilter
|
||||
from vtkmodules.vtkFiltersGeneral import vtkGradientFilter as vtkGradientFilter
|
||||
from vtkmodules.vtkFiltersGeneral import (
|
||||
vtkIntersectionPolyDataFilter as vtkIntersectionPolyDataFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersGeneral import vtkOBBTree as vtkOBBTree
|
||||
from vtkmodules.vtkFiltersGeneral import (
|
||||
vtkRectilinearGridToPointSet as vtkRectilinearGridToPointSet,
|
||||
)
|
||||
from vtkmodules.vtkFiltersGeneral import (
|
||||
vtkRectilinearGridToTetrahedra as vtkRectilinearGridToTetrahedra,
|
||||
)
|
||||
from vtkmodules.vtkFiltersGeneral import vtkShrinkFilter as vtkShrinkFilter
|
||||
from vtkmodules.vtkFiltersGeneral import vtkTableBasedClipDataSet as vtkTableBasedClipDataSet
|
||||
from vtkmodules.vtkFiltersGeneral import vtkTableToPolyData as vtkTableToPolyData
|
||||
from vtkmodules.vtkFiltersGeneral import vtkTessellatorFilter as vtkTessellatorFilter
|
||||
from vtkmodules.vtkFiltersGeneral import vtkTransformFilter as vtkTransformFilter
|
||||
from vtkmodules.vtkFiltersGeneral import vtkWarpScalar as vtkWarpScalar
|
||||
from vtkmodules.vtkFiltersGeneral import vtkWarpVector as vtkWarpVector
|
||||
from vtkmodules.vtkFiltersGeometry import (
|
||||
vtkCompositeDataGeometryFilter as vtkCompositeDataGeometryFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter as vtkDataSetSurfaceFilter
|
||||
from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter as vtkGeometryFilter
|
||||
from vtkmodules.vtkFiltersGeometry import (
|
||||
vtkStructuredGridGeometryFilter as vtkStructuredGridGeometryFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersHybrid import vtkPolyDataSilhouette as vtkPolyDataSilhouette
|
||||
from vtkmodules.vtkFiltersModeling import (
|
||||
vtkAdaptiveSubdivisionFilter as vtkAdaptiveSubdivisionFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersModeling import (
|
||||
vtkBandedPolyDataContourFilter as vtkBandedPolyDataContourFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersModeling import (
|
||||
vtkButterflySubdivisionFilter as vtkButterflySubdivisionFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersModeling import (
|
||||
vtkCollisionDetectionFilter as vtkCollisionDetectionFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersModeling import (
|
||||
vtkDijkstraGraphGeodesicPath as vtkDijkstraGraphGeodesicPath,
|
||||
)
|
||||
from vtkmodules.vtkFiltersModeling import vtkFillHolesFilter as vtkFillHolesFilter
|
||||
from vtkmodules.vtkFiltersModeling import vtkLinearExtrusionFilter as vtkLinearExtrusionFilter
|
||||
from vtkmodules.vtkFiltersModeling import vtkLinearSubdivisionFilter as vtkLinearSubdivisionFilter
|
||||
from vtkmodules.vtkFiltersModeling import vtkLoopSubdivisionFilter as vtkLoopSubdivisionFilter
|
||||
from vtkmodules.vtkFiltersModeling import vtkOutlineFilter as vtkOutlineFilter
|
||||
from vtkmodules.vtkFiltersModeling import vtkRibbonFilter as vtkRibbonFilter
|
||||
from vtkmodules.vtkFiltersModeling import (
|
||||
vtkRotationalExtrusionFilter as vtkRotationalExtrusionFilter,
|
||||
)
|
||||
from vtkmodules.vtkFiltersModeling import vtkRuledSurfaceFilter as vtkRuledSurfaceFilter
|
||||
from vtkmodules.vtkFiltersModeling import vtkSelectEnclosedPoints as vtkSelectEnclosedPoints
|
||||
from vtkmodules.vtkFiltersModeling import vtkSubdivideTetra as vtkSubdivideTetra
|
||||
from vtkmodules.vtkFiltersModeling import vtkTrimmedExtrusionFilter as vtkTrimmedExtrusionFilter
|
||||
from vtkmodules.vtkFiltersParallel import vtkIntegrateAttributes as vtkIntegrateAttributes
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
# `vtkmodules.vtkFiltersParallelDIY2` is unavailable in some versions of `vtk` from conda-forge
|
||||
from vtkmodules.vtkFiltersParallelDIY2 import (
|
||||
vtkRedistributeDataSetFilter as vtkRedistributeDataSetFilter,
|
||||
)
|
||||
|
||||
from vtkmodules.vtkFiltersPoints import vtkGaussianKernel as vtkGaussianKernel
|
||||
from vtkmodules.vtkFiltersPoints import vtkPointInterpolator as vtkPointInterpolator
|
||||
from vtkmodules.vtkFiltersSources import vtkArcSource as vtkArcSource
|
||||
from vtkmodules.vtkFiltersSources import vtkArrowSource as vtkArrowSource
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
# Deprecated in 9.3
|
||||
from vtkmodules.vtkFiltersSources import ( # type: ignore[attr-defined]
|
||||
vtkCapsuleSource as vtkCapsuleSource,
|
||||
)
|
||||
|
||||
from vtkmodules.vtkFiltersSources import vtkConeSource as vtkConeSource
|
||||
from vtkmodules.vtkFiltersSources import vtkCubeSource as vtkCubeSource
|
||||
from vtkmodules.vtkFiltersSources import vtkCylinderSource as vtkCylinderSource
|
||||
from vtkmodules.vtkFiltersSources import vtkDiskSource as vtkDiskSource
|
||||
from vtkmodules.vtkFiltersSources import vtkFrustumSource as vtkFrustumSource
|
||||
from vtkmodules.vtkFiltersSources import vtkLineSource as vtkLineSource
|
||||
from vtkmodules.vtkFiltersSources import vtkOutlineCornerFilter as vtkOutlineCornerFilter
|
||||
from vtkmodules.vtkFiltersSources import vtkOutlineCornerSource as vtkOutlineCornerSource
|
||||
from vtkmodules.vtkFiltersSources import vtkParametricFunctionSource as vtkParametricFunctionSource
|
||||
from vtkmodules.vtkFiltersSources import vtkPlaneSource as vtkPlaneSource
|
||||
from vtkmodules.vtkFiltersSources import vtkPlatonicSolidSource as vtkPlatonicSolidSource
|
||||
from vtkmodules.vtkFiltersSources import vtkPointSource as vtkPointSource
|
||||
from vtkmodules.vtkFiltersSources import vtkRegularPolygonSource as vtkRegularPolygonSource
|
||||
from vtkmodules.vtkFiltersSources import vtkSphereSource as vtkSphereSource
|
||||
from vtkmodules.vtkFiltersSources import vtkSuperquadricSource as vtkSuperquadricSource
|
||||
from vtkmodules.vtkFiltersSources import vtkTessellatedBoxSource as vtkTessellatedBoxSource
|
||||
from vtkmodules.vtkFiltersStatistics import vtkComputeQuartiles as vtkComputeQuartiles
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.vtkFiltersStatistics import vtkLengthDistribution as vtkLengthDistribution
|
||||
from vtkmodules.vtkFiltersTexture import vtkTextureMapToPlane as vtkTextureMapToPlane
|
||||
from vtkmodules.vtkFiltersTexture import vtkTextureMapToSphere as vtkTextureMapToSphere
|
||||
from vtkmodules.vtkFiltersVerdict import vtkCellQuality as vtkCellQuality
|
||||
from vtkmodules.vtkFiltersVerdict import vtkCellSizeFilter as vtkCellSizeFilter
|
||||
from vtkmodules.vtkFiltersVerdict import vtkMeshQuality as vtkMeshQuality
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.vtkFiltersVerdict import vtkBoundaryMeshQuality as vtkBoundaryMeshQuality
|
||||
|
||||
from vtkmodules.vtkImagingCore import vtkAbstractImageInterpolator as vtkAbstractImageInterpolator
|
||||
from vtkmodules.vtkImagingCore import vtkExtractVOI as vtkExtractVOI
|
||||
from vtkmodules.vtkImagingCore import vtkImageConstantPad as vtkImageConstantPad
|
||||
from vtkmodules.vtkImagingCore import vtkImageDifference as vtkImageDifference
|
||||
from vtkmodules.vtkImagingCore import vtkImageExtractComponents as vtkImageExtractComponents
|
||||
from vtkmodules.vtkImagingCore import vtkImageFlip as vtkImageFlip
|
||||
from vtkmodules.vtkImagingCore import vtkImageInterpolator as vtkImageInterpolator
|
||||
from vtkmodules.vtkImagingCore import vtkImageMirrorPad as vtkImageMirrorPad
|
||||
from vtkmodules.vtkImagingCore import vtkImageResize as vtkImageResize
|
||||
from vtkmodules.vtkImagingCore import vtkImageSincInterpolator as vtkImageSincInterpolator
|
||||
from vtkmodules.vtkImagingCore import vtkImageThreshold as vtkImageThreshold
|
||||
from vtkmodules.vtkImagingCore import vtkImageWrapPad as vtkImageWrapPad
|
||||
from vtkmodules.vtkImagingCore import vtkRTAnalyticSource as vtkRTAnalyticSource
|
||||
from vtkmodules.vtkImagingGeneral import vtkImageGaussianSmooth as vtkImageGaussianSmooth
|
||||
from vtkmodules.vtkImagingGeneral import vtkImageMedian3D as vtkImageMedian3D
|
||||
from vtkmodules.vtkImagingHybrid import vtkGaussianSplatter as vtkGaussianSplatter
|
||||
from vtkmodules.vtkImagingHybrid import vtkSampleFunction as vtkSampleFunction
|
||||
from vtkmodules.vtkImagingHybrid import (
|
||||
vtkSurfaceReconstructionFilter as vtkSurfaceReconstructionFilter,
|
||||
)
|
||||
from vtkmodules.vtkImagingMorphological import (
|
||||
vtkImageConnectivityFilter as vtkImageConnectivityFilter,
|
||||
)
|
||||
from vtkmodules.vtkImagingStencil import vtkImageStencil as vtkImageStencil
|
||||
from vtkmodules.vtkImagingStencil import vtkPolyDataToImageStencil as vtkPolyDataToImageStencil
|
||||
from vtkmodules.vtkIOGeometry import vtkHoudiniPolyDataWriter as vtkHoudiniPolyDataWriter
|
||||
from vtkmodules.vtkIOGeometry import vtkIVWriter as vtkIVWriter
|
||||
from vtkmodules.vtkIOGeometry import vtkOBJWriter as vtkOBJWriter
|
||||
from vtkmodules.vtkIOGeometry import vtkProStarReader as vtkProStarReader
|
||||
from vtkmodules.vtkIOGeometry import vtkSTLWriter as vtkSTLWriter
|
||||
|
||||
with contextlib.suppress(ImportError): # Introduced VTK v9.4.0
|
||||
from vtkmodules.vtkIOHDF import vtkHDFWriter as vtkHDFWriter
|
||||
|
||||
from vtkmodules.vtkIOInfovis import vtkDelimitedTextReader as vtkDelimitedTextReader
|
||||
from vtkmodules.vtkIOLegacy import vtkDataReader as vtkDataReader
|
||||
from vtkmodules.vtkIOLegacy import vtkDataSetReader as vtkDataSetReader
|
||||
from vtkmodules.vtkIOLegacy import vtkDataSetWriter as vtkDataSetWriter
|
||||
from vtkmodules.vtkIOLegacy import vtkDataWriter as vtkDataWriter
|
||||
from vtkmodules.vtkIOLegacy import vtkPolyDataReader as vtkPolyDataReader
|
||||
from vtkmodules.vtkIOLegacy import vtkPolyDataWriter as vtkPolyDataWriter
|
||||
from vtkmodules.vtkIOLegacy import vtkRectilinearGridReader as vtkRectilinearGridReader
|
||||
from vtkmodules.vtkIOLegacy import vtkRectilinearGridWriter as vtkRectilinearGridWriter
|
||||
from vtkmodules.vtkIOLegacy import vtkSimplePointsWriter as vtkSimplePointsWriter
|
||||
from vtkmodules.vtkIOLegacy import vtkStructuredGridReader as vtkStructuredGridReader
|
||||
from vtkmodules.vtkIOLegacy import vtkStructuredGridWriter as vtkStructuredGridWriter
|
||||
from vtkmodules.vtkIOLegacy import vtkUnstructuredGridReader as vtkUnstructuredGridReader
|
||||
from vtkmodules.vtkIOLegacy import vtkUnstructuredGridWriter as vtkUnstructuredGridWriter
|
||||
from vtkmodules.vtkIOPLY import vtkPLYReader as vtkPLYReader
|
||||
from vtkmodules.vtkIOPLY import vtkPLYWriter as vtkPLYWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLImageDataReader as vtkXMLImageDataReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLImageDataWriter as vtkXMLImageDataWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLMultiBlockDataReader as vtkXMLMultiBlockDataReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLMultiBlockDataWriter as vtkXMLMultiBlockDataWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLPartitionedDataSetReader as vtkXMLPartitionedDataSetReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLPImageDataReader as vtkXMLPImageDataReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLPolyDataReader as vtkXMLPolyDataReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter as vtkXMLPolyDataWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLPRectilinearGridReader as vtkXMLPRectilinearGridReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLPUnstructuredGridReader as vtkXMLPUnstructuredGridReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLReader as vtkXMLReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLRectilinearGridReader as vtkXMLRectilinearGridReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLRectilinearGridWriter as vtkXMLRectilinearGridWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLStructuredGridReader as vtkXMLStructuredGridReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLStructuredGridWriter as vtkXMLStructuredGridWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLTableReader as vtkXMLTableReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLTableWriter as vtkXMLTableWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLUnstructuredGridReader as vtkXMLUnstructuredGridReader
|
||||
from vtkmodules.vtkIOXML import vtkXMLUnstructuredGridWriter as vtkXMLUnstructuredGridWriter
|
||||
from vtkmodules.vtkIOXML import vtkXMLWriter as vtkXMLWriter
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.vtkImagingMorphological import vtkImageDilateErode3D as vtkImageDilateErode3D
|
||||
|
||||
try:
|
||||
from vtkmodules.vtkPythonContext2D import vtkPythonItem as vtkPythonItem
|
||||
except ImportError: # pragma: no cover
|
||||
# `vtkmodules.vtkPythonContext2D` is unavailable in some versions of `vtk` (see #3224)
|
||||
|
||||
class vtkPythonItem: # type: ignore[no-redef] # noqa: N801
|
||||
"""Empty placeholder."""
|
||||
|
||||
def __init__(self): # pragma: no cover
|
||||
"""Raise version error on init."""
|
||||
from pyvista.core.errors import VTKVersionError # noqa: PLC0415
|
||||
|
||||
msg = 'Chart backgrounds require the vtkPythonContext2D module'
|
||||
raise VTKVersionError(msg)
|
||||
|
||||
|
||||
from vtkmodules.vtkImagingFourier import vtkImageButterworthHighPass as vtkImageButterworthHighPass
|
||||
from vtkmodules.vtkImagingFourier import vtkImageButterworthLowPass as vtkImageButterworthLowPass
|
||||
from vtkmodules.vtkImagingFourier import vtkImageFFT as vtkImageFFT
|
||||
from vtkmodules.vtkImagingFourier import vtkImageRFFT as vtkImageRFFT
|
||||
|
||||
# 9.1+ imports
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.vtkFiltersPoints import vtkConvertToPointCloud as vtkConvertToPointCloud
|
||||
|
||||
with contextlib.suppress(ImportError): # Introduced prior to VTK 9.3
|
||||
from vtkmodules.vtkRenderingCore import vtkViewport as vtkViewport
|
||||
|
||||
# 9.3+ imports
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.vtkFiltersCore import vtkPackLabels as vtkPackLabels
|
||||
from vtkmodules.vtkFiltersCore import vtkSurfaceNets3D as vtkSurfaceNets3D
|
||||
|
||||
# 9.1+ imports
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.vtkIOParallelXML import (
|
||||
vtkXMLPartitionedDataSetWriter as vtkXMLPartitionedDataSetWriter,
|
||||
)
|
||||
|
||||
|
||||
class VersionInfo(NamedTuple):
|
||||
"""Version information as a named tuple."""
|
||||
|
||||
major: int
|
||||
minor: int
|
||||
micro: int
|
||||
|
||||
def __str__(self):
|
||||
return str((self.major, self.minor, self.micro))
|
||||
|
||||
|
||||
def VTKVersionInfo(): # noqa: N802
|
||||
"""Return the vtk version as a namedtuple.
|
||||
|
||||
Returns
|
||||
-------
|
||||
VersionInfo
|
||||
Version information as a named tuple.
|
||||
|
||||
"""
|
||||
try:
|
||||
ver = vtkVersion()
|
||||
major = ver.GetVTKMajorVersion()
|
||||
minor = ver.GetVTKMinorVersion()
|
||||
micro = ver.GetVTKBuildVersion()
|
||||
except AttributeError: # pragma: no cover
|
||||
warnings.warn('Unable to detect VTK version. Defaulting to v4.0.0')
|
||||
major, minor, micro = (4, 0, 0)
|
||||
|
||||
return VersionInfo(major, minor, micro)
|
||||
|
||||
|
||||
vtk_version_info = VTKVersionInfo()
|
||||
|
||||
|
||||
class vtkPyVistaOverride: # noqa: N801
|
||||
"""Base class to automatically override VTK classes with PyVista classes."""
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
if vtk_version_info >= (9, 4):
|
||||
# Check for VTK base classes and call the override method
|
||||
for base in cls.__bases__:
|
||||
if (
|
||||
hasattr(base, '__module__')
|
||||
and base.__module__.startswith('vtkmodules.')
|
||||
and hasattr(base, 'override')
|
||||
):
|
||||
# For now, just remove any overrides for these classes
|
||||
# There are clear issues with the current implementation
|
||||
# of overriding these classes upstream and until they are
|
||||
# resolved, we will entirely remove the overrides.
|
||||
# See https://gitlab.kitware.com/vtk/vtk/-/merge_requests/11698
|
||||
# See https://gitlab.kitware.com/vtk/vtk/-/issues/19550#note_1598883
|
||||
base.override(None)
|
||||
break
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
class DisableVtkSnakeCase:
|
||||
"""Base class to raise error if using VTK's `snake_case` API."""
|
||||
|
||||
@staticmethod
|
||||
def check_attribute(target, attr):
|
||||
# Check sys.meta_path to avoid dynamic imports when Python is shutting down
|
||||
if vtk_version_info >= (9, 4) and sys.meta_path is not None:
|
||||
# Raise error if accessing attributes from VTK's pythonic snake_case API
|
||||
|
||||
import pyvista as pv # noqa: PLC0415
|
||||
|
||||
state = pv._VTK_SNAKE_CASE_STATE
|
||||
if state != 'allow':
|
||||
if (
|
||||
attr not in ['__class__', '__init__']
|
||||
and attr[0].islower()
|
||||
and is_vtk_attribute(target, attr)
|
||||
):
|
||||
msg = (
|
||||
f'The attribute {attr!r} is defined by VTK and is not part of the '
|
||||
f'PyVista API'
|
||||
)
|
||||
if state == 'error':
|
||||
raise pv.PyVistaAttributeError(msg)
|
||||
else:
|
||||
warnings.warn(msg, RuntimeWarning)
|
||||
|
||||
def __getattribute__(self, item):
|
||||
DisableVtkSnakeCase.check_attribute(self, item)
|
||||
return object.__getattribute__(self, item)
|
||||
|
||||
|
||||
def is_vtk_attribute(obj: object, attr: str): # numpydoc ignore=RT01
|
||||
"""Return True if the attribute is defined by a vtk class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj : object
|
||||
Class or instance to check.
|
||||
|
||||
attr : str
|
||||
Name of the attribute to check.
|
||||
|
||||
"""
|
||||
|
||||
def _find_defining_class(cls, attr):
|
||||
"""Find the class that defines a given attribute."""
|
||||
for base in cls.__mro__:
|
||||
if attr in base.__dict__:
|
||||
return base
|
||||
return None
|
||||
|
||||
cls = _find_defining_class(obj if isinstance(obj, type) else obj.__class__, attr)
|
||||
return cls is not None and cls.__module__.startswith('vtkmodules')
|
||||
|
||||
|
||||
class VTKObjectWrapperCheckSnakeCase(VTKObjectWrapper):
|
||||
"""Superclass for classes that wrap VTK objects with Python objects.
|
||||
|
||||
This class overrides __getattr__ to disable the VTK snake case API.
|
||||
"""
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""Forward unknown attribute requests to VTKArray's __getattr__."""
|
||||
if self.VTKObject is not None:
|
||||
# Check if forwarding snake_case attributes
|
||||
DisableVtkSnakeCase.check_attribute(self.VTKObject, name)
|
||||
return getattr(self.VTKObject, name)
|
||||
raise AttributeError
|
||||
@@ -0,0 +1,904 @@
|
||||
"""Contains the pyvista.Cell class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import cast
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
from . import _vtk_core as _vtk
|
||||
from ._typing_core import BoundsTuple
|
||||
from .celltype import CellType
|
||||
from .dataobject import DataObject
|
||||
from .errors import CellSizeError
|
||||
from .errors import PyVistaDeprecationWarning
|
||||
from .utilities.cells import numpy_to_idarr
|
||||
from .utilities.misc import _BoundsSizeMixin
|
||||
from .utilities.misc import _NoNewAttrMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from pyvista import UnstructuredGrid
|
||||
|
||||
from ._typing_core import CellsLike
|
||||
from ._typing_core import MatrixLike
|
||||
from ._typing_core import NumpyArray
|
||||
|
||||
|
||||
def _get_vtk_id_type() -> type[np.int32 | np.int64]:
|
||||
"""Return the numpy datatype responding to :vtk:`vtkIdTypeArray`."""
|
||||
VTK_ID_TYPE_SIZE = _vtk.vtkIdTypeArray().GetDataTypeSize()
|
||||
if VTK_ID_TYPE_SIZE == 4:
|
||||
return np.int32
|
||||
elif VTK_ID_TYPE_SIZE == 8:
|
||||
return np.int64
|
||||
return np.int32
|
||||
|
||||
|
||||
class Cell(_BoundsSizeMixin, DataObject, _vtk.vtkGenericCell):
|
||||
"""Wrapping of :vtk:`vtkCell`.
|
||||
|
||||
This class provides the capability to access a given cell topology and can
|
||||
be useful when walking through a cell's individual faces or investigating
|
||||
cell properties.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vtk_cell : :vtk:`vtkCell`, optional
|
||||
The vtk object to wrap as Cell, that must be of :vtk:`vtkCell` type.
|
||||
|
||||
cell_type : int, optional
|
||||
VTK cell type. Determined from ``vtk_cell`` if not input.
|
||||
|
||||
deep : bool, default: False
|
||||
Perform a deep copy of the original cell.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Accessing individual cells from a :class:`pyvista.DataSet` using this class
|
||||
will be much slower than accessing bulk data from the
|
||||
:attr:`pyvista.PolyData.faces` or :attr:`pyvista.UnstructuredGrid.cells` attributes.
|
||||
|
||||
Also note that the cell object is a deep copy of the original cell and
|
||||
is unassociated with the original cell. Changing any data of
|
||||
that cell (for example, :attr:`pyvista.Cell.points`) will not change the original dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Get the 0-th cell from a :class:`pyvista.PolyData`.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> cell # doctest: +SKIP
|
||||
Cell (0x7fa760075a10)
|
||||
Type: <CellType.TRIANGLE: 5>
|
||||
Linear: True
|
||||
Dimension: 2
|
||||
N Points: 3
|
||||
N Faces: 0
|
||||
N Edges: 3
|
||||
X Bounds: -5.406e-02, -5.551e-17
|
||||
Y Bounds: 0.000e+00, 1.124e-02
|
||||
Z Bounds: -5.000e-01, -4.971e-01
|
||||
|
||||
Get the 0-th cell from a :class:`pyvista.UnstructuredGrid`.
|
||||
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_hexbeam()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> cell # doctest: +SKIP
|
||||
Cell (0x7fdc71a3c210)
|
||||
Type: <CellType.HEXAHEDRON: 12>
|
||||
Linear: True
|
||||
Dimension: 3
|
||||
N Points: 8
|
||||
N Faces: 6
|
||||
N Edges: 12
|
||||
X Bounds: 0.000e+00, 5.000e-01
|
||||
Y Bounds: 0.000e+00, 5.000e-01
|
||||
Z Bounds: 0.000e+00, 5.000e-01
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args(allowed=['vtk_cell', 'cell_type'])
|
||||
def __init__(
|
||||
self: Self,
|
||||
vtk_cell: _vtk.vtkCell | None = None,
|
||||
cell_type: CellType | None = None,
|
||||
deep: bool = False, # noqa: FBT001, FBT002
|
||||
) -> None:
|
||||
"""Initialize the cell."""
|
||||
super().__init__()
|
||||
if vtk_cell is not None:
|
||||
if not isinstance(vtk_cell, _vtk.vtkCell):
|
||||
msg = f'`vtk_cell` must be a vtkCell, not {type(vtk_cell)}' # type: ignore[unreachable]
|
||||
raise TypeError(msg)
|
||||
# cell type must be set first before deep or shallow copy
|
||||
if cell_type is None:
|
||||
self.SetCellType(vtk_cell.GetCellType())
|
||||
else:
|
||||
self.SetCellType(cell_type)
|
||||
|
||||
if deep:
|
||||
self.DeepCopy(vtk_cell)
|
||||
else:
|
||||
self.ShallowCopy(vtk_cell)
|
||||
|
||||
@property
|
||||
def type(self: Self) -> CellType:
|
||||
"""Get the cell type from the enum :class:`pyvista.CellType`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.CellType
|
||||
Type of cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).type
|
||||
<CellType.TRIANGLE: 5>
|
||||
|
||||
"""
|
||||
return CellType(self.GetCellType())
|
||||
|
||||
@property
|
||||
def is_linear(self: Self) -> bool:
|
||||
"""Return if the cell is linear.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
If the cell is linear.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).is_linear
|
||||
True
|
||||
|
||||
"""
|
||||
return bool(self.IsLinear())
|
||||
|
||||
def plot(self: Self, **kwargs) -> None:
|
||||
"""Plot this cell.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
**kwargs : dict, optional
|
||||
See :func:`pyvista.plot` for a description of the optional keyword
|
||||
arguments.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_hexbeam()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> cell.plot()
|
||||
|
||||
"""
|
||||
self.cast_to_unstructured_grid().plot(**kwargs)
|
||||
|
||||
def cast_to_polydata(self: Self) -> pyvista.PolyData:
|
||||
"""Cast this cell to PolyData.
|
||||
|
||||
Can only be used for 0D, 1D, or 2D cells.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
This cell cast to a :class:`pyvista.PolyData`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_sphere()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> grid = cell.cast_to_polydata()
|
||||
>>> grid # doctest: +SKIP
|
||||
PolyData (0x7f09ae437b80)
|
||||
N Cells: 1
|
||||
N Points: 3
|
||||
N Strips: 0
|
||||
X Bounds: 0.000e+00, 1.000e+01
|
||||
Y Bounds: 0.000e+00, 2.500e+01
|
||||
Z Bounds: -1.270e+02, -1.250e+02
|
||||
N Arrays: 0
|
||||
|
||||
"""
|
||||
cells = [len(self.point_ids), *list(range(len(self.point_ids)))]
|
||||
if self.dimension == 0:
|
||||
return pyvista.PolyData(self.points.copy(), verts=cells)
|
||||
if self.dimension == 1:
|
||||
return pyvista.PolyData(self.points.copy(), lines=cells)
|
||||
if self.dimension == 2:
|
||||
if self.type == CellType.TRIANGLE_STRIP:
|
||||
return pyvista.PolyData(self.points.copy(), strips=cells)
|
||||
else:
|
||||
return pyvista.PolyData(self.points.copy(), faces=cells)
|
||||
else:
|
||||
msg = f'3D cells cannot be cast to PolyData: got cell type {self.type}'
|
||||
raise ValueError(msg)
|
||||
|
||||
def cast_to_unstructured_grid(self: Self) -> UnstructuredGrid:
|
||||
"""Cast this cell to an unstructured grid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
This cell cast to a :class:`pyvista.UnstructuredGrid`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_hexbeam()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> grid = cell.cast_to_unstructured_grid()
|
||||
>>> grid # doctest: +SKIP
|
||||
UnstructuredGrid (0x7f9383619540)
|
||||
N Cells: 1
|
||||
N Points: 8
|
||||
X Bounds: 0.000e+00, 5.000e-01
|
||||
Y Bounds: 0.000e+00, 5.000e-01
|
||||
Z Bounds: 0.000e+00, 5.000e-01
|
||||
N Arrays: 0
|
||||
|
||||
"""
|
||||
if self.type == CellType.POLYHEDRON:
|
||||
# construct from faces
|
||||
cell_ids = [self.n_faces]
|
||||
for face in self.faces:
|
||||
cell_ids.append(len(face.point_ids))
|
||||
cell_ids.extend(self.point_ids.index(i) for i in face.point_ids)
|
||||
cell_ids.insert(0, len(cell_ids))
|
||||
else:
|
||||
cell_ids = [len(self.point_ids), *list(range(len(self.point_ids)))]
|
||||
return pyvista.UnstructuredGrid(
|
||||
cell_ids,
|
||||
[int(self.type)],
|
||||
self.points.copy(),
|
||||
)
|
||||
|
||||
@property
|
||||
def dimension(self: Self) -> int:
|
||||
"""Return the cell dimension.
|
||||
|
||||
This returns the dimensionality of the cell. For example, 1 for an edge,
|
||||
2 for a triangle, and 3 for a tetrahedron.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The cell dimension.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).dimension
|
||||
2
|
||||
|
||||
"""
|
||||
return self.GetCellDimension()
|
||||
|
||||
@property
|
||||
def n_points(self: Self) -> int:
|
||||
"""Get the number of points composing the cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of points.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).n_points
|
||||
3
|
||||
|
||||
"""
|
||||
return self.GetNumberOfPoints()
|
||||
|
||||
@property
|
||||
def n_faces(self: Self) -> int:
|
||||
"""Get the number of faces composing the cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of faces.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista.examples.cells import Tetrahedron
|
||||
>>> mesh = Tetrahedron()
|
||||
>>> mesh.get_cell(0).n_faces
|
||||
4
|
||||
|
||||
"""
|
||||
return self.GetNumberOfFaces()
|
||||
|
||||
@property
|
||||
def n_edges(self: Self) -> int:
|
||||
"""Get the number of edges composing the cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of edges composing the cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).n_edges
|
||||
3
|
||||
|
||||
"""
|
||||
return self.GetNumberOfEdges()
|
||||
|
||||
@property
|
||||
def point_ids(self: Self) -> list[int]:
|
||||
"""Get the point IDs composing the cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[int]
|
||||
The point IDs composing the cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).point_ids
|
||||
[2, 30, 0]
|
||||
|
||||
"""
|
||||
point_ids = self.GetPointIds()
|
||||
return [point_ids.GetId(i) for i in range(point_ids.GetNumberOfIds())]
|
||||
|
||||
@property
|
||||
def points(self: Self) -> NumpyArray[float]:
|
||||
"""Get the point coordinates of the cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
The point coordinates of the cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).points
|
||||
array([[0.05405951, 0. , 0.49706897],
|
||||
[0.05287818, 0.0112396 , 0.49706897],
|
||||
[0. , 0. , 0.5 ]])
|
||||
|
||||
"""
|
||||
return _vtk.vtk_to_numpy(self.GetPoints().GetData())
|
||||
|
||||
def get_edge(self: Self, index: int) -> Cell:
|
||||
"""Get the i-th edge composing the cell.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : int
|
||||
Edge ID.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Cell
|
||||
Edge given by ``index``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Extract a single edge from a face and output the IDs of the edge
|
||||
points.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> edge = cell.get_edge(0)
|
||||
>>> edge.point_ids
|
||||
[2, 30]
|
||||
|
||||
"""
|
||||
if index + 1 > self.n_edges:
|
||||
msg = f'Invalid index {index} for a cell with {self.n_edges} edges.'
|
||||
raise IndexError(msg)
|
||||
|
||||
# must deep copy here as multiple sequental calls to GetEdge overwrite
|
||||
# the underlying pointer
|
||||
return Cell(self.GetEdge(index), deep=True) # type: ignore[abstract]
|
||||
|
||||
@property
|
||||
def edges(self: Self) -> list[Cell]:
|
||||
"""Return a list of edges composing the cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[Cell]
|
||||
A list of edges composing the cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista.examples.cells import Hexahedron
|
||||
>>> mesh = Hexahedron()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> edges = cell.edges
|
||||
>>> len(edges)
|
||||
12
|
||||
|
||||
"""
|
||||
return [self.get_edge(i) for i in range(self.n_edges)]
|
||||
|
||||
@property
|
||||
def faces(self: Self) -> list[Cell]:
|
||||
"""Return a list of faces composing the cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[Cell]
|
||||
A list of faces composing the cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista.examples.cells import Tetrahedron
|
||||
>>> mesh = Tetrahedron()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> faces = cell.faces
|
||||
>>> len(faces)
|
||||
4
|
||||
|
||||
"""
|
||||
return [self.get_face(i) for i in range(self.n_faces)]
|
||||
|
||||
def get_face(self: Self, index: int) -> Cell:
|
||||
"""Get the i-th face composing the cell.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : int
|
||||
Face ID.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Cell
|
||||
Face given by ``index``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Return the face IDs composing the first face of an example tetrahedron.
|
||||
|
||||
>>> from pyvista.examples.cells import Tetrahedron
|
||||
>>> mesh = Tetrahedron()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> face = cell.get_face(0)
|
||||
>>> face.point_ids
|
||||
[0, 1, 3]
|
||||
|
||||
"""
|
||||
# must deep copy here as sequental calls overwrite the underlying pointer
|
||||
if index + 1 > self.n_faces:
|
||||
msg = f'Invalid index {index} for a cell with {self.n_faces} faces.'
|
||||
raise IndexError(msg)
|
||||
|
||||
# must deep copy here as multiple sequental calls to GetFace overwrite
|
||||
# the underlying pointer
|
||||
cell = self.GetFace(index)
|
||||
return Cell(cell, deep=True, cell_type=cast('CellType', cell.GetCellType())) # type: ignore[abstract]
|
||||
|
||||
@property
|
||||
def bounds(self: Self) -> BoundsTuple:
|
||||
"""Get the cell bounds in ``(x_min, x_max, y_min, y_max, z_min, z_max)``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
BoundsTuple
|
||||
The cell bounds in ``(x_min, x_max, y_min, y_max, z_min, z_max)``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).bounds
|
||||
BoundsTuple(x_min = 0.0,
|
||||
x_max = 0.05405950918793678,
|
||||
y_min = 0.0,
|
||||
y_max = 0.011239604093134403,
|
||||
z_min = 0.49706897139549255,
|
||||
z_max = 0.5)
|
||||
|
||||
"""
|
||||
return BoundsTuple(*self.GetBounds())
|
||||
|
||||
@property
|
||||
def center(self: Self) -> tuple[float, float, float]:
|
||||
"""Get the center of the cell.
|
||||
|
||||
Uses parametric coordinate center to determine x-y-z center.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[float, float, float]
|
||||
The center of the cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.get_cell(0).center
|
||||
(0.03564589594801267, 0.0037465346977114677, 0.49804598093032837)
|
||||
|
||||
"""
|
||||
para_center = [0.0, 0.0, 0.0]
|
||||
sub_id = self.GetParametricCenter(para_center)
|
||||
# EvaluateLocation requires mutable sub_id
|
||||
sub_id = _vtk.mutable(sub_id) # type: ignore[assignment]
|
||||
# center and weights are returned from EvaluateLocation
|
||||
center = [0.0, 0.0, 0.0]
|
||||
weights = [0.0] * self.n_points
|
||||
self.EvaluateLocation(sub_id, para_center, center, weights)
|
||||
return cast('tuple[float, float, float]', tuple(center))
|
||||
|
||||
def _get_attrs(self: Self) -> list[tuple[str, Any, str]]:
|
||||
"""Return the representation methods (internal helper)."""
|
||||
attrs = []
|
||||
attrs.append(('Type', repr(self.type), '{}' * len(repr(self.type))))
|
||||
attrs.append(('Linear', self.is_linear, '{}')) # type: ignore[arg-type]
|
||||
attrs.append(('Dimension', self.dimension, '{}')) # type: ignore[arg-type]
|
||||
attrs.append(('N Points', self.n_points, '{}')) # type: ignore[arg-type]
|
||||
attrs.append(('N Faces', self.n_faces, '{}')) # type: ignore[arg-type]
|
||||
attrs.append(('N Edges', self.n_edges, '{}')) # type: ignore[arg-type]
|
||||
bds = self.bounds
|
||||
fmt = f'{pyvista.FLOAT_FORMAT}, {pyvista.FLOAT_FORMAT}'
|
||||
attrs.append(('X Bounds', (bds[0], bds[1]), fmt)) # type: ignore[arg-type]
|
||||
attrs.append(('Y Bounds', (bds[2], bds[3]), fmt)) # type: ignore[arg-type]
|
||||
attrs.append(('Z Bounds', (bds[4], bds[5]), fmt)) # type: ignore[arg-type]
|
||||
|
||||
return attrs
|
||||
|
||||
def __repr__(self: Self) -> str:
|
||||
"""Return the object representation."""
|
||||
return self.head(display=False, html=False)
|
||||
|
||||
def __str__(self: Self) -> str:
|
||||
"""Return the object string representation."""
|
||||
return self.head(display=False, html=False)
|
||||
|
||||
@_deprecate_positional_args
|
||||
def copy(self: Self, deep: bool = True) -> Self: # noqa: FBT001, FBT002
|
||||
"""Return a copy of the cell.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
deep : bool, optional
|
||||
When ``True`` makes a full copy of the cell. When ``False``,
|
||||
performs a shallow copy where the new cell still references the
|
||||
original cell.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Cell
|
||||
Deep or shallow copy of the cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a deep copy of the cell and demonstrate it is deep.
|
||||
|
||||
>>> from pyvista.examples.cells import Tetrahedron
|
||||
>>> mesh = Tetrahedron()
|
||||
>>> cell = mesh.get_cell(0)
|
||||
>>> deep_cell = cell.copy(deep=True)
|
||||
>>> deep_cell.points[:] = 0
|
||||
>>> cell != deep_cell
|
||||
True
|
||||
|
||||
Create a shallow copy of the cell and demonstrate it is shallow.
|
||||
|
||||
>>> shallow_cell = cell.copy(deep=False)
|
||||
>>> shallow_cell.points[:] = 0
|
||||
>>> cell == shallow_cell
|
||||
True
|
||||
|
||||
"""
|
||||
return type(self)(self, deep=deep)
|
||||
|
||||
|
||||
class CellArray(
|
||||
_NoNewAttrMixin,
|
||||
_vtk.DisableVtkSnakeCase,
|
||||
_vtk.vtkPyVistaOverride,
|
||||
_vtk.vtkCellArray,
|
||||
):
|
||||
"""PyVista wrapping of :vtk:`vtkCellArray`.
|
||||
|
||||
Provides convenience functions to simplify creating a CellArray from
|
||||
a numpy array or list.
|
||||
|
||||
.. deprecated:: 0.44.0
|
||||
The parameters ``n_cells`` and ``deep`` are deprecated and no longer used.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cells : np.ndarray or list, optional
|
||||
Import an array of data with the legacy :vtk:`vtkCellArray` layout, e.g.
|
||||
``{ n0, p0_0, p0_1, ..., p0_n, n1, p1_0, p1_1, ..., p1_n, ... }``
|
||||
Where n0 is the number of points in cell 0, and pX_Y is the Y'th
|
||||
point in cell X.
|
||||
|
||||
n_cells : int, optional
|
||||
The number of cells.
|
||||
|
||||
deep : bool, default: False
|
||||
Perform a deep copy of the original cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a cell array containing two triangles from the traditional interleaved format
|
||||
|
||||
>>> from pyvista.core.cell import CellArray
|
||||
>>> cellarr = CellArray([3, 0, 1, 2, 3, 3, 4, 5])
|
||||
|
||||
Create a cell array containing two triangles from separate offsets and connectivity arrays
|
||||
|
||||
>>> from pyvista.core.cell import CellArray
|
||||
>>> offsets = [0, 3, 6]
|
||||
>>> connectivity = [0, 1, 2, 3, 4, 5]
|
||||
>>> cellarr = CellArray.from_arrays(offsets, connectivity)
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args(allowed=['cells'])
|
||||
def __init__(
|
||||
self: Self,
|
||||
cells: CellsLike | None = None,
|
||||
n_cells: int | None = None,
|
||||
deep: bool | None = None, # noqa: FBT001
|
||||
) -> None:
|
||||
"""Initialize a :vtk:`vtkCellArray`."""
|
||||
super().__init__()
|
||||
self.__offsets: _vtk.vtkIdTypeArray | None = None
|
||||
self.__connectivity: _vtk.vtkIdTypeArray | None = None
|
||||
if cells is not None:
|
||||
self.cells = cells
|
||||
|
||||
# deprecated 0.44.0, convert to error in 0.47.0, remove 0.48.0
|
||||
for k, v in (('n_cells', n_cells), ('deep', deep)):
|
||||
if v is not None:
|
||||
warnings.warn(
|
||||
f'`CellArray parameter `{k}` is deprecated and no longer used.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
|
||||
@property
|
||||
def cells(self: Self) -> NumpyArray[int]:
|
||||
"""Return a numpy array of the cells.
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
A numpy array of the cells.
|
||||
|
||||
"""
|
||||
cells = _vtk.vtkIdTypeArray()
|
||||
self.ExportLegacyFormat(cells)
|
||||
return _vtk.vtk_to_numpy(cells)
|
||||
|
||||
@cells.setter
|
||||
def cells(self: Self, cells: CellsLike) -> None:
|
||||
cells = np.asarray(cells)
|
||||
vtk_idarr = numpy_to_idarr(cells, deep=False, return_ind=False)
|
||||
self.ImportLegacyFormat(vtk_idarr)
|
||||
imported_size = self.GetNumberOfConnectivityEntries()
|
||||
|
||||
# https://github.com/pyvista/pyvista/pull/5404
|
||||
if imported_size != cells.size:
|
||||
msg = (
|
||||
f'Cell array size is invalid. Size ({cells.size}) does not'
|
||||
f' match expected size ({imported_size}). This is likely'
|
||||
' due to invalid connectivity array.'
|
||||
)
|
||||
raise CellSizeError(msg)
|
||||
self.__offsets = self.__connectivity = None
|
||||
|
||||
@property
|
||||
def n_cells(self: Self) -> int:
|
||||
"""Return the number of cells.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of cells.
|
||||
|
||||
"""
|
||||
return self.GetNumberOfCells()
|
||||
|
||||
@property
|
||||
def connectivity_array(self: Self) -> NumpyArray[int]:
|
||||
"""Return the array with the point ids that define the cells' connectivity.
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
Array with the point ids that define the cells' connectivity.
|
||||
|
||||
"""
|
||||
return _get_connectivity_array(self)
|
||||
|
||||
@property
|
||||
def offset_array(self: Self) -> NumpyArray[int]:
|
||||
"""Return the array used to store cell offsets.
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
Array used to store cell offsets.
|
||||
|
||||
"""
|
||||
return _get_offset_array(self)
|
||||
|
||||
def _set_data(
|
||||
self: Self,
|
||||
offsets: MatrixLike[int],
|
||||
connectivity: MatrixLike[int],
|
||||
*,
|
||||
deep: bool = False,
|
||||
) -> None:
|
||||
"""Set the offsets and connectivity arrays."""
|
||||
vtk_offsets = numpy_to_idarr(offsets, deep=deep)
|
||||
vtk_connectivity = numpy_to_idarr(connectivity, deep=deep)
|
||||
self.SetData(vtk_offsets, vtk_connectivity)
|
||||
|
||||
# Because vtkCellArray doesn't take ownership of the arrays, it's possible for them to get
|
||||
# garbage collected. Keep a reference to them for safety
|
||||
self.__offsets = vtk_offsets
|
||||
self.__connectivity = vtk_connectivity
|
||||
|
||||
@staticmethod
|
||||
@_deprecate_positional_args(allowed=['offsets', 'connectivity'])
|
||||
def from_arrays(
|
||||
offsets: MatrixLike[int],
|
||||
connectivity: MatrixLike[int],
|
||||
deep: bool = False, # noqa: FBT001, FBT002
|
||||
) -> CellArray:
|
||||
"""Construct a CellArray from offsets and connectivity arrays.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
offsets : MatrixLike[int]
|
||||
Offsets array of length `n_cells + 1`.
|
||||
|
||||
connectivity : MatrixLike[int]
|
||||
Connectivity array.
|
||||
|
||||
deep : bool, default: False
|
||||
Whether to deep copy the array data into the vtk arrays.
|
||||
|
||||
Returns
|
||||
-------
|
||||
CellArray
|
||||
Constructed CellArray.
|
||||
|
||||
"""
|
||||
cellarr = CellArray()
|
||||
cellarr._set_data(offsets, connectivity, deep=deep)
|
||||
return cellarr
|
||||
|
||||
@property
|
||||
def regular_cells(self: Self) -> NumpyArray[int]:
|
||||
"""Return a (n_cells, cell_size)-shaped array of point indices for equal-sized faces.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Array of face indices of shape (n_cells, cell_size).
|
||||
|
||||
Notes
|
||||
-----
|
||||
This property does not validate that the cells are all
|
||||
actually the same size. If they're not, this property may either
|
||||
raise a `ValueError` or silently return an incorrect array.
|
||||
|
||||
"""
|
||||
return _get_regular_cells(self)
|
||||
|
||||
@classmethod
|
||||
@_deprecate_positional_args(allowed=['cells'])
|
||||
def from_regular_cells(
|
||||
cls: type[CellArray],
|
||||
cells: MatrixLike[int],
|
||||
deep: bool = False, # noqa: FBT001, FBT002
|
||||
) -> pyvista.CellArray:
|
||||
"""Construct a ``CellArray`` from a (n_cells, cell_size) array of cell indices.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cells : numpy.ndarray or list[list[int]]
|
||||
Cell array of shape (n_cells, cell_size) where all cells have the same `cell_size`.
|
||||
|
||||
deep : bool, default: False
|
||||
Whether to deep copy the cell array data into the vtk connectivity array.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.CellArray
|
||||
Constructed ``CellArray``.
|
||||
|
||||
"""
|
||||
cells = np.asarray(cells, dtype=pyvista.ID_TYPE)
|
||||
n_cells, cell_size = cells.shape
|
||||
offsets = cell_size * np.arange(n_cells + 1, dtype=pyvista.ID_TYPE)
|
||||
cellarr = cls()
|
||||
cellarr._set_data(offsets, cells, deep=deep)
|
||||
return cellarr
|
||||
|
||||
@classmethod
|
||||
def from_irregular_cells(cls: type[CellArray], cells: MatrixLike[int]) -> pyvista.CellArray:
|
||||
"""Construct a ``CellArray`` from a (n_cells, cell_size) array of cell indices.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cells : numpy.ndarray or list[list[int]]
|
||||
Cell array of shape (n_cells, cell_size) where all cells have the same `cell_size`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.CellArray
|
||||
Constructed ``CellArray``.
|
||||
|
||||
"""
|
||||
offsets = np.cumsum([len(c) for c in cells])
|
||||
offsets = np.concatenate([[0], offsets], dtype=pyvista.ID_TYPE)
|
||||
connectivity = np.concatenate(cells, dtype=pyvista.ID_TYPE)
|
||||
return cls.from_arrays(offsets, connectivity) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# The following methods would be much nicer bound to CellArray,
|
||||
# but then they wouldn't be available on bare vtkCellArrays. In the future,
|
||||
# consider using vtkCellArray.override decorator, so they're all automatically
|
||||
# returned as CellArrays
|
||||
|
||||
|
||||
def _get_connectivity_array(cellarr: _vtk.vtkCellArray) -> NumpyArray[int]:
|
||||
"""Return the array with the point ids that define the cells' connectivity."""
|
||||
return _vtk.vtk_to_numpy(cellarr.GetConnectivityArray())
|
||||
|
||||
|
||||
def _get_offset_array(cellarr: _vtk.vtkCellArray) -> NumpyArray[int]:
|
||||
"""Return the array used to store cell offsets."""
|
||||
return _vtk.vtk_to_numpy(cellarr.GetOffsetsArray())
|
||||
|
||||
|
||||
def _get_regular_cells(cellarr: _vtk.vtkCellArray) -> NumpyArray[int]:
|
||||
"""Return a (n_cells, cell_size)-shaped array of point indices for equal-sized faces."""
|
||||
cells = _get_connectivity_array(cellarr)
|
||||
if len(cells) == 0:
|
||||
return cells
|
||||
|
||||
offsets = _get_offset_array(cellarr)
|
||||
cell_size = offsets[1] - offsets[0]
|
||||
return cells.reshape(-1, cell_size)
|
||||
|
||||
|
||||
def _get_irregular_cells(cellarr: _vtk.vtkCellArray) -> tuple[NumpyArray[int], ...]:
|
||||
"""Return a tuple of length n_cells of each cell's point indices."""
|
||||
cells = _get_connectivity_array(cellarr)
|
||||
if len(cells) == 0:
|
||||
return ()
|
||||
|
||||
offsets = _get_offset_array(cellarr)
|
||||
return tuple(np.split(cells, offsets[1:-1]))
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,215 @@
|
||||
"""PyVista specific errors."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class NotAllTrianglesError(ValueError):
|
||||
"""Exception when a mesh does not contain all triangles.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, message='Mesh must consist of only triangles') -> None:
|
||||
"""Empty init."""
|
||||
ValueError.__init__(self, message)
|
||||
|
||||
|
||||
class DeprecationError(RuntimeError):
|
||||
"""Used for deprecated methods and functions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, message='This feature has been deprecated') -> None:
|
||||
"""Empty init."""
|
||||
RuntimeError.__init__(self, message)
|
||||
|
||||
|
||||
class VTKVersionError(RuntimeError):
|
||||
"""Requested feature is not supported by the installed VTK version.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message='The requested feature is not supported by the installed VTK version.',
|
||||
) -> None: # numpydoc ignore=PR01,RT01
|
||||
"""Empty init."""
|
||||
RuntimeError.__init__(self, message)
|
||||
|
||||
|
||||
class PointSetNotSupported(TypeError): # noqa: N818
|
||||
"""Requested filter or property is not supported by the PointSet class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message='The requested operation is not supported for PointSets.',
|
||||
) -> None: # numpydoc ignore=PR01,RT01
|
||||
"""Empty init."""
|
||||
TypeError.__init__(self, message)
|
||||
|
||||
|
||||
class PointSetCellOperationError(PointSetNotSupported):
|
||||
"""Requested filter or property is not supported by the PointSet class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message='Cell operations are not supported. PointSets contain no cells.',
|
||||
) -> None: # numpydoc ignore=PR01,RT01
|
||||
"""Empty init."""
|
||||
PointSetNotSupported.__init__(self, message)
|
||||
|
||||
|
||||
class PointSetDimensionReductionError(PointSetNotSupported):
|
||||
"""Requested filter or property is not supported by the PointSet class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message='Slice and other dimension reducing filters are not supported on PointSets.',
|
||||
) -> None: # numpydoc ignore=PR01,RT01
|
||||
"""Empty init."""
|
||||
PointSetNotSupported.__init__(self, message)
|
||||
|
||||
|
||||
class PartitionedDataSetsNotSupported(TypeError): # noqa: N818
|
||||
"""Requested filter or property is not supported by the PartitionedDataSets class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message='The requested operation is not supported for PartitionedDataSetss.',
|
||||
) -> None: # numpydoc ignore=PR01,RT01
|
||||
"""Empty init."""
|
||||
TypeError.__init__(self, message)
|
||||
|
||||
|
||||
class MissingDataError(ValueError):
|
||||
"""Exception when data is missing, e.g. no active scalars can be set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, message='No data available.') -> None:
|
||||
"""Call the base class constructor with the custom message."""
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class AmbiguousDataError(ValueError):
|
||||
"""Exception when data is ambiguous, e.g. multiple active scalars can be set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, message='Multiple data available.') -> None:
|
||||
"""Call the base class constructor with the custom message."""
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class CellSizeError(ValueError):
|
||||
"""Exception when a cell array size is invalid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, message='Cell array size is invalid.') -> None:
|
||||
"""Call the base class constructor with the custom message."""
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class PyVistaPipelineError(RuntimeError):
|
||||
"""Exception when a VTK pipeline runs into an issue.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message='VTK pipeline issue detected by PyVista.',
|
||||
) -> None: # numpydoc ignore=PR01,RT01
|
||||
"""Call the base class constructor with the custom message."""
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class PyVistaAttributeError(AttributeError):
|
||||
"""Exception when accessing an attribute that is not part of the PyVista API.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
Error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message='The attribute is not part of the PyVista API',
|
||||
) -> None: # numpydoc ignore=PR01,RT01
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class PyVistaDeprecationWarning(Warning):
|
||||
"""Non-supressed Deprecation Warning."""
|
||||
|
||||
|
||||
class PyVistaFutureWarning(Warning):
|
||||
"""Non-supressed Future Warning."""
|
||||
|
||||
|
||||
class PyVistaEfficiencyWarning(Warning):
|
||||
"""Efficiency warning."""
|
||||
@@ -0,0 +1,95 @@
|
||||
"""These classes hold methods to apply general filters to any data type.
|
||||
|
||||
By inheriting these classes into the wrapped VTK data structures, a user
|
||||
can easily apply common filters in an intuitive manner.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_uniform()
|
||||
|
||||
>>> # Threshold
|
||||
>>> thresh = dataset.threshold([100, 500])
|
||||
|
||||
>>> # Slice
|
||||
>>> slc = dataset.slice()
|
||||
|
||||
>>> # Clip
|
||||
>>> clp = dataset.clip(invert=True)
|
||||
|
||||
>>> # Contour
|
||||
>>> iso = dataset.contour()
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import cast
|
||||
|
||||
import pyvista
|
||||
from pyvista.core.utilities.helpers import wrap
|
||||
from pyvista.core.utilities.observers import ProgressMonitor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
|
||||
|
||||
def _update_alg(alg, *, progress_bar: bool = False, message='') -> None:
|
||||
"""Update an algorithm with or without a progress bar."""
|
||||
if progress_bar:
|
||||
with ProgressMonitor(alg, message=message):
|
||||
alg.Update()
|
||||
else:
|
||||
alg.Update()
|
||||
|
||||
|
||||
def _get_output(
|
||||
algorithm: _vtk.vtkAlgorithm,
|
||||
*,
|
||||
iport=0,
|
||||
iconnection=0,
|
||||
oport=0,
|
||||
active_scalars=None,
|
||||
active_scalars_field='point',
|
||||
):
|
||||
"""Get the algorithm's output and copy input's pyvista meta info."""
|
||||
ido = cast('pyvista.DataObject', wrap(algorithm.GetInputDataObject(iport, iconnection)))
|
||||
data = cast('pyvista.DataObject', wrap(algorithm.GetOutputDataObject(oport)))
|
||||
if not isinstance(data, pyvista.MultiBlock):
|
||||
data.copy_meta_from(ido, deep=True)
|
||||
if not data.field_data and ido.field_data:
|
||||
data.field_data.update(ido.field_data)
|
||||
if active_scalars is not None:
|
||||
data.set_active_scalars(active_scalars, preference=active_scalars_field)
|
||||
# return a PointSet if input is a pointset
|
||||
if isinstance(ido, pyvista.PointSet):
|
||||
return data.cast_to_pointset()
|
||||
return data
|
||||
|
||||
|
||||
from .composite import CompositeFilters
|
||||
from .data_object import DataObjectFilters
|
||||
|
||||
# Re-export submodules to maintain the same import paths
|
||||
# before filters.py was split into submodules
|
||||
from .data_set import DataSetFilters
|
||||
from .image_data import ImageDataFilters
|
||||
from .poly_data import PolyDataFilters
|
||||
from .rectilinear_grid import RectilinearGridFilters
|
||||
from .structured_grid import StructuredGridFilters
|
||||
from .unstructured_grid import UnstructuredGridFilters
|
||||
|
||||
__all__ = [
|
||||
'CompositeFilters',
|
||||
'DataObjectFilters',
|
||||
'DataSetFilters',
|
||||
'ImageDataFilters',
|
||||
'PolyDataFilters',
|
||||
'RectilinearGridFilters',
|
||||
'StructuredGridFilters',
|
||||
'UnstructuredGridFilters',
|
||||
'_get_output',
|
||||
'_update_alg',
|
||||
]
|
||||
@@ -0,0 +1,408 @@
|
||||
"""Filters module with a class to manage filters/algorithms for composite datasets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.filters import _get_output
|
||||
from pyvista.core.filters import _update_alg
|
||||
from pyvista.core.filters.data_object import DataObjectFilters
|
||||
from pyvista.core.filters.data_set import DataSetFilters
|
||||
from pyvista.core.utilities.helpers import wrap
|
||||
from pyvista.core.utilities.misc import abstract_class
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable
|
||||
|
||||
from pyvista import MultiBlock
|
||||
from pyvista.core.composite import _TypeMultiBlockLeaf
|
||||
|
||||
|
||||
@abstract_class
|
||||
class CompositeFilters(DataObjectFilters):
|
||||
"""An internal class to manage filters/algorithms for composite datasets."""
|
||||
|
||||
def generic_filter( # type:ignore[misc]
|
||||
self: MultiBlock,
|
||||
function: str | Callable[..., _TypeMultiBlockLeaf],
|
||||
/,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> MultiBlock:
|
||||
"""Apply any filter to all nested blocks recursively.
|
||||
|
||||
This filter applies a user-specified function or method to all blocks in
|
||||
this :class:`~pyvista.MultiBlock`.
|
||||
|
||||
.. note::
|
||||
|
||||
If an ``inplace`` keyword is used, this ``MultiBlock`` is modified
|
||||
in-place along with all blocks.
|
||||
|
||||
.. note::
|
||||
|
||||
By default, the specified ``function`` is not applied to any ``None``
|
||||
blocks. These are simply skipped and passed through to the output.
|
||||
|
||||
For advanced use, it is possible to apply the filter to ``None`` blocks
|
||||
by using the undocumented keyword ``_skip_none=False``.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
Parameters
|
||||
----------
|
||||
function : Callable | str
|
||||
Callable function or name of the method to apply to each block. The function
|
||||
should accept a :class:`~pyvista.DataSet` as input and return either a
|
||||
:class:`~pyvista.DataSet` or :class:`~pyvista.MultiBlock` as output.
|
||||
|
||||
*args : Any, optional
|
||||
Arguments to use with the specified ``function``.
|
||||
|
||||
**kwargs : Any, optional
|
||||
Keyword arguments to use with the specified ``function``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
MultiBlock
|
||||
Filtered dataset.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
Raised if the filter cannot be applied to any block for any reason. This
|
||||
overrides ``TypeError``, ``ValueError``, ``AttributeError`` errors when
|
||||
filtering.
|
||||
|
||||
See Also
|
||||
--------
|
||||
pyvista.MultiBlock.flatten
|
||||
pyvista.MultiBlock.recursive_iterator
|
||||
pyvista.MultiBlock.clean
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a :class:`~pyvista.MultiBlock` with various mesh types.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> import numpy as np
|
||||
>>> volume = examples.load_uniform()
|
||||
>>> poly = examples.load_ant()
|
||||
>>> unstructured = examples.load_tetbeam()
|
||||
>>> multi = pv.MultiBlock([volume, poly, unstructured])
|
||||
|
||||
>>> [type(block) for block in multi] # doctest: +NORMALIZE_WHITESPACE
|
||||
[<class 'pyvista.core.grid.ImageData'>,
|
||||
<class 'pyvista.core.pointset.PolyData'>,
|
||||
<class 'pyvista.core.pointset.UnstructuredGrid'>]
|
||||
|
||||
Use the generic filter to apply :meth:`~pyvista.DataSet.cast_to_unstructured_grid`
|
||||
to all blocks.
|
||||
|
||||
>>> filtered = multi.generic_filter('cast_to_unstructured_grid')
|
||||
|
||||
>>> [type(block) for block in filtered] # doctest: +NORMALIZE_WHITESPACE
|
||||
[<class 'pyvista.core.pointset.UnstructuredGrid'>,
|
||||
<class 'pyvista.core.pointset.UnstructuredGrid'>,
|
||||
<class 'pyvista.core.pointset.UnstructuredGrid'>]
|
||||
|
||||
Use the :meth:`~pyvista.DataSetFilters.partition` filter on all blocks.
|
||||
Any arguments can be specified as though the filter is being used directly.
|
||||
|
||||
>>> filtered = multi.generic_filter('partition', 4, as_composite=True)
|
||||
|
||||
Any function can be used as long as it returns a :class:`~pyvista.DataSet` or
|
||||
:class:`~pyvista.MultiBlock`. For example, we can normalize each block
|
||||
independently to have bounds between ``-0.5`` and ``0.5``.
|
||||
|
||||
>>> def normalize_bounds(dataset):
|
||||
... # Center the dataset
|
||||
... dataset = dataset.translate(-np.array(dataset.center))
|
||||
... # Scale the dataset
|
||||
... factor = 1 / np.array(dataset.bounds_size)
|
||||
... return dataset.scale(factor)
|
||||
|
||||
>>> filtered = multi.generic_filter(normalize_bounds)
|
||||
>>> filtered
|
||||
MultiBlock (...)
|
||||
N Blocks: 3
|
||||
X Bounds: -5.000e-01, 5.000e-01
|
||||
Y Bounds: -5.000e-01, 5.000e-01
|
||||
Z Bounds: -5.000e-01, 5.000e-01
|
||||
|
||||
The generic filter will fail if the filter can only be applied to some blocks
|
||||
but not others. For example, it is not possible to use the
|
||||
:meth:`~pyvista.ImageDataFilters.resample` filter generically since the
|
||||
``MultiBlock`` above is heterogeneous and contains some blocks which are not
|
||||
:class:`~pyvista.ImageData`.
|
||||
|
||||
>>> multi.generic_filter('resample', 0.5) # doctest:+SKIP
|
||||
RuntimeError: The filter 'resample' could not be applied to the block at index 1 with
|
||||
name 'Block-01' and type PolyData.
|
||||
|
||||
Use a custom function instead to apply the generic filter conditionally. Here we
|
||||
filter the image blocks but simply pass-through a copy of any other blocks.
|
||||
|
||||
>>> def conditional_resample(dataset, *args, **kwargs):
|
||||
... if isinstance(dataset, pv.ImageData):
|
||||
... return dataset.resample(*args, **kwargs)
|
||||
... return dataset.copy()
|
||||
|
||||
>>> filtered = multi.generic_filter(conditional_resample, 0.5)
|
||||
|
||||
"""
|
||||
# Set default undocumented kwargs. A function is used here to prevent IDEs from
|
||||
# suggesting these keywords to users.
|
||||
|
||||
def get_iterator_kwargs(kwargs_) -> tuple[bool, bool]:
|
||||
# Skip None blocks by default
|
||||
skip_none_: bool = kwargs_.pop('_skip_none', True)
|
||||
# Do not skip empty blocks by default
|
||||
skip_empty_: bool = kwargs_.pop('_skip_empty', False)
|
||||
return skip_none_, skip_empty_
|
||||
|
||||
skip_none, skip_empty = get_iterator_kwargs(kwargs)
|
||||
|
||||
def apply_filter(function_, ids_, name_, block_): # noqa: PLR0917
|
||||
try:
|
||||
function_ = (
|
||||
getattr(block_, function_)
|
||||
if isinstance(function_, str)
|
||||
else functools.partial(function_, block_)
|
||||
)
|
||||
output_ = function_(**kwargs) if len(args) == 0 else function_(*args, **kwargs)
|
||||
except (AttributeError, ValueError, TypeError, RuntimeError) as e:
|
||||
# Construct a helpful error message
|
||||
func_name = (
|
||||
function_.func if isinstance(function_, functools.partial) else function_
|
||||
)
|
||||
obj_name = type(block).__name__
|
||||
if len(ids_) == 1:
|
||||
index = ids_[0]
|
||||
nested = ' '
|
||||
else:
|
||||
nested = ' nested '
|
||||
index = _format_nested_index(ids)
|
||||
msg = (
|
||||
f"The filter '{func_name}'\n"
|
||||
f'could not be applied to the{nested}block at index {index} with '
|
||||
f"name '{name_}' and type {obj_name}."
|
||||
)
|
||||
raise RuntimeError(msg) from e
|
||||
return output_
|
||||
|
||||
def get_iterator(multi, skip_none_, skip_empty_):
|
||||
return multi.recursive_iterator(
|
||||
'all', skip_none=skip_none_, skip_empty=skip_empty_, nested_ids=True
|
||||
)
|
||||
|
||||
# Apply filter in-place
|
||||
inplace = kwargs.get('inplace')
|
||||
if inplace:
|
||||
for ids, name, block in get_iterator(self, skip_none, skip_empty):
|
||||
apply_filter(function, ids, name, block)
|
||||
return self
|
||||
|
||||
# Create a copy and replace all the blocks
|
||||
output = pyvista.MultiBlock()
|
||||
output.shallow_copy(self, recursive=True)
|
||||
for ids, name, block in get_iterator(output, skip_none, skip_empty):
|
||||
filtered = apply_filter(function, ids, name, block)
|
||||
# Only replace if necessary
|
||||
if filtered is not block:
|
||||
output.replace(ids, filtered)
|
||||
return output
|
||||
|
||||
def extract_geometry(self):
|
||||
"""Extract the surface the geometry of all blocks.
|
||||
|
||||
Place this filter at the end of a pipeline before a polydata
|
||||
consumer such as a polydata mapper to extract geometry from
|
||||
all blocks and append them to one polydata object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Surface of the composite dataset.
|
||||
|
||||
"""
|
||||
gf = _vtk.vtkCompositeDataGeometryFilter()
|
||||
gf.SetInputData(self)
|
||||
gf.Update()
|
||||
return wrap(gf.GetOutputDataObject(0))
|
||||
|
||||
@_deprecate_positional_args
|
||||
def combine(self, merge_points: bool = False, tolerance=0.0): # noqa: FBT001, FBT002
|
||||
"""Combine all blocks into a single unstructured grid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
merge_points : bool, default: False
|
||||
Merge coincidental points.
|
||||
|
||||
tolerance : float, default: 0.0
|
||||
The absolute tolerance to use to find coincident points when
|
||||
``merge_points=True``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
Combined blocks.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Combine blocks within a multiblock without merging points.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> block = pv.MultiBlock(
|
||||
... [
|
||||
... pv.Cube(clean=False),
|
||||
... pv.Cube(center=(1, 0, 0), clean=False),
|
||||
... ]
|
||||
... )
|
||||
>>> merged = block.combine()
|
||||
>>> merged.n_points
|
||||
48
|
||||
|
||||
Combine blocks and merge points
|
||||
|
||||
>>> merged = block.combine(merge_points=True)
|
||||
>>> merged.n_points
|
||||
12
|
||||
|
||||
"""
|
||||
alg = _vtk.vtkAppendFilter()
|
||||
for block in self: # type: ignore[attr-defined]
|
||||
single_block = (
|
||||
CompositeFilters.combine(
|
||||
block, # type: ignore[arg-type]
|
||||
merge_points=merge_points,
|
||||
tolerance=tolerance,
|
||||
)
|
||||
if isinstance(block, _vtk.vtkMultiBlockDataSet)
|
||||
else block
|
||||
)
|
||||
alg.AddInputData(single_block)
|
||||
alg.SetMergePoints(merge_points)
|
||||
alg.SetTolerance(tolerance)
|
||||
alg.Update()
|
||||
return wrap(alg.GetOutputDataObject(0))
|
||||
|
||||
@_deprecate_positional_args
|
||||
def outline( # type: ignore[misc]
|
||||
self: MultiBlock,
|
||||
generate_faces: bool = False, # noqa: FBT001, FBT002
|
||||
nested: bool = False, # noqa: FBT001, FBT002
|
||||
progress_bar: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Produce an outline of the full extent for the all blocks in this composite dataset.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
generate_faces : bool, default: False
|
||||
Generate solid faces for the box.
|
||||
|
||||
nested : bool, default: False
|
||||
If ``True``, these creates individual outlines for each nested dataset.
|
||||
|
||||
progress_bar : bool, default: False
|
||||
Display a progress bar to indicate progress.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Mesh containing the outline.
|
||||
|
||||
"""
|
||||
if nested:
|
||||
return DataSetFilters.outline(
|
||||
self,
|
||||
generate_faces=generate_faces,
|
||||
progress_bar=progress_bar,
|
||||
)
|
||||
box = pyvista.Box(bounds=self.bounds)
|
||||
return box.outline(generate_faces=generate_faces, progress_bar=progress_bar)
|
||||
|
||||
@_deprecate_positional_args
|
||||
def outline_corners( # type: ignore[misc]
|
||||
self: MultiBlock,
|
||||
factor=0.2,
|
||||
nested: bool = False, # noqa: FBT001, FBT002
|
||||
progress_bar: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Produce an outline of the corners for the all blocks in this composite dataset.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
factor : float, default: 0.2
|
||||
Controls the relative size of the corners to the length of
|
||||
the corresponding bounds.
|
||||
|
||||
nested : bool, default: False
|
||||
If ``True``, these creates individual outlines for each nested dataset.
|
||||
|
||||
progress_bar : bool, default: False
|
||||
Display a progress bar to indicate progress.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Mesh containing outlined corners.
|
||||
|
||||
"""
|
||||
if nested:
|
||||
return DataSetFilters.outline_corners(self, factor=factor, progress_bar=progress_bar)
|
||||
box = pyvista.Box(bounds=self.bounds)
|
||||
return box.outline_corners(factor=factor, progress_bar=progress_bar)
|
||||
|
||||
@_deprecate_positional_args
|
||||
def _compute_normals( # noqa: PLR0917
|
||||
self,
|
||||
cell_normals: bool = True, # noqa: FBT001, FBT002
|
||||
point_normals: bool = True, # noqa: FBT001, FBT002
|
||||
split_vertices: bool = False, # noqa: FBT001, FBT002
|
||||
flip_normals: bool = False, # noqa: FBT001, FBT002
|
||||
consistent_normals: bool = True, # noqa: FBT001, FBT002
|
||||
auto_orient_normals: bool = False, # noqa: FBT001, FBT002
|
||||
non_manifold_traversal: bool = True, # noqa: FBT001, FBT002
|
||||
feature_angle=30.0,
|
||||
track_vertices: bool = False, # noqa: FBT001, FBT002
|
||||
progress_bar: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Compute point and/or cell normals for a multi-block dataset."""
|
||||
if not self.is_all_polydata: # type: ignore[attr-defined]
|
||||
msg = (
|
||||
'This multiblock contains non-PolyData datasets. Convert all the '
|
||||
'datasets to PolyData with `as_polydata`'
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# track original point indices
|
||||
if split_vertices and track_vertices:
|
||||
for block in self: # type: ignore[attr-defined]
|
||||
ids = np.arange(block.n_points, dtype=pyvista.ID_TYPE)
|
||||
block.point_data.set_array(ids, 'pyvistaOriginalPointIds')
|
||||
|
||||
alg = _vtk.vtkPolyDataNormals()
|
||||
alg.SetComputeCellNormals(cell_normals)
|
||||
alg.SetComputePointNormals(point_normals)
|
||||
alg.SetSplitting(split_vertices)
|
||||
alg.SetFlipNormals(flip_normals)
|
||||
alg.SetConsistency(consistent_normals)
|
||||
alg.SetAutoOrientNormals(auto_orient_normals)
|
||||
alg.SetNonManifoldTraversal(non_manifold_traversal)
|
||||
alg.SetFeatureAngle(feature_angle)
|
||||
alg.SetInputData(self)
|
||||
_update_alg(alg, progress_bar=progress_bar, message='Computing Normals')
|
||||
return _get_output(alg)
|
||||
|
||||
|
||||
def _format_nested_index(index: tuple[int, ...]) -> str:
|
||||
return ''.join([f'[{ind}]' for ind in index])
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,149 @@
|
||||
"""Filters module with the class to manage filters/algorithms for rectilinear grid datasets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.filters import _get_output
|
||||
from pyvista.core.filters import _update_alg
|
||||
from pyvista.core.utilities.misc import abstract_class
|
||||
|
||||
|
||||
@abstract_class
|
||||
class RectilinearGridFilters:
|
||||
"""An internal class to manage filters/algorithms for rectilinear grid datasets."""
|
||||
|
||||
@_deprecate_positional_args(allowed=['tetra_per_cell'])
|
||||
def to_tetrahedra( # noqa: PLR0917
|
||||
self,
|
||||
tetra_per_cell: int = 5,
|
||||
mixed: str | Sequence[int] | bool = False, # noqa: FBT001, FBT002
|
||||
pass_cell_ids: bool = True, # noqa: FBT001, FBT002
|
||||
pass_data: bool = True, # noqa: FBT001, FBT002
|
||||
progress_bar: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Create a tetrahedral mesh structured grid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tetra_per_cell : int, default: 5
|
||||
The number of tetrahedrons to divide each cell into. Can be
|
||||
either ``5``, ``6``, or ``12``. If ``mixed=True``, this value is
|
||||
overridden.
|
||||
|
||||
mixed : str, bool, sequence, default: False
|
||||
When set, subdivides some cells into 5 and some cells into 12. Set
|
||||
to ``True`` to use the active cell scalars of the
|
||||
:class:`pyvista.RectilinearGrid` to be either 5 or 12 to
|
||||
determining the number of tetrahedra to generate per cell.
|
||||
|
||||
When a sequence, uses these values to subdivide the cells. When a
|
||||
string uses a cell array rather than the active array to determine
|
||||
the number of tetrahedra to generate per cell.
|
||||
|
||||
pass_cell_ids : bool, default: True
|
||||
Set to ``True`` to make the tetrahedra have scalar data indicating
|
||||
which cell they came from in the original
|
||||
:class:`pyvista.RectilinearGrid`. The name of this array is
|
||||
``'vtkOriginalCellIds'`` within the ``cell_data``.
|
||||
|
||||
pass_data : bool, default: True
|
||||
Set to ``True`` to make the tetrahedra mesh have the cell data from
|
||||
the original :class:`pyvista.RectilinearGrid`. This uses
|
||||
``pass_cell_ids=True`` internally. If ``True``, ``pass_cell_ids``
|
||||
will also be set to ``True``.
|
||||
|
||||
progress_bar : bool, default: False
|
||||
Display a progress bar to indicate progress.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
UnstructuredGrid containing the tetrahedral cells.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Divide a rectangular grid into tetrahedrons. Each cell contains by
|
||||
default 5 tetrahedrons.
|
||||
|
||||
First, create and plot the grid.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> xrng = np.linspace(0, 1, 2)
|
||||
>>> yrng = np.linspace(0, 1, 2)
|
||||
>>> zrng = np.linspace(0, 2, 3)
|
||||
>>> grid = pv.RectilinearGrid(xrng, yrng, zrng)
|
||||
>>> grid.plot()
|
||||
|
||||
Now, generate the tetrahedra plot in the exploded view of the cell.
|
||||
|
||||
>>> tet_grid = grid.to_tetrahedra()
|
||||
>>> tet_grid.explode(factor=0.5).plot(show_edges=True)
|
||||
|
||||
Take the same grid but divide the first cell into 5 cells and the other
|
||||
cell into 12 tetrahedrons per cell.
|
||||
|
||||
>>> tet_grid = grid.to_tetrahedra(mixed=[5, 12])
|
||||
>>> tet_grid.explode(factor=0.5).plot(show_edges=True)
|
||||
|
||||
"""
|
||||
alg = _vtk.vtkRectilinearGridToTetrahedra()
|
||||
alg.SetRememberVoxelId(pass_cell_ids or pass_data)
|
||||
if mixed is not False:
|
||||
if isinstance(mixed, str):
|
||||
self.cell_data.active_scalars_name = mixed # type: ignore[attr-defined]
|
||||
elif isinstance(mixed, (np.ndarray, Sequence)):
|
||||
self.cell_data['_MIXED_CELLS_'] = mixed # type: ignore[attr-defined]
|
||||
elif not isinstance(mixed, bool):
|
||||
msg = '`mixed` must be either a sequence of ints or bool' # type: ignore[unreachable]
|
||||
raise TypeError(msg)
|
||||
alg.SetTetraPerCellTo5And12()
|
||||
else:
|
||||
if tetra_per_cell not in [5, 6, 12]:
|
||||
msg = f'`tetra_per_cell` should be either 5, 6, or 12, not {tetra_per_cell}'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Edge case causing a seg-fault where grid is flat in one dimension
|
||||
# See: https://gitlab.kitware.com/vtk/vtk/-/issues/18650
|
||||
if 1 in self.dimensions and tetra_per_cell == 12: # type: ignore[attr-defined]
|
||||
msg = (
|
||||
'Cannot split cells into 12 tetrahedrals when at least '
|
||||
f'one dimension is 1. Dimensions are {self.dimensions}.' # type: ignore[attr-defined]
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
alg.SetTetraPerCell(tetra_per_cell)
|
||||
|
||||
alg.SetInputData(self)
|
||||
_update_alg(alg, progress_bar=progress_bar, message='Converting to tetrahedra')
|
||||
out = _get_output(alg)
|
||||
|
||||
if pass_data:
|
||||
# algorithm stores original cell ids in active scalars
|
||||
# this does not preserve active scalars, but we need to
|
||||
# keep active scalars until they are renamed
|
||||
for name in self.cell_data: # type: ignore[attr-defined]
|
||||
if name != out.cell_data.active_scalars_name:
|
||||
out[name] = self.cell_data[name][out.cell_data.active_scalars] # type: ignore[attr-defined]
|
||||
|
||||
for name in self.point_data: # type: ignore[attr-defined]
|
||||
out[name] = self.point_data[name] # type: ignore[attr-defined]
|
||||
|
||||
if alg.GetRememberVoxelId():
|
||||
# original cell_ids are not named and are the active scalars
|
||||
out.cell_data.set_array(
|
||||
out.cell_data.pop(out.cell_data.active_scalars_name),
|
||||
'vtkOriginalCellIds',
|
||||
)
|
||||
|
||||
if pass_data:
|
||||
# Now reset active scalars in cast the original mesh had data with active scalars
|
||||
association, name = self.active_scalars_info # type: ignore[attr-defined]
|
||||
out.set_active_scalars(name, preference=association)
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,202 @@
|
||||
"""Filters module with class to manage filters/algorithms for structured grid datasets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.filters import _get_output
|
||||
from pyvista.core.filters.data_set import DataSetFilters
|
||||
from pyvista.core.utilities.misc import abstract_class
|
||||
|
||||
|
||||
@abstract_class
|
||||
class StructuredGridFilters(DataSetFilters):
|
||||
"""An internal class to manage filters/algorithms for structured grid datasets."""
|
||||
|
||||
@_deprecate_positional_args(allowed=['voi', 'rate'])
|
||||
def extract_subset(self, voi, rate=(1, 1, 1), boundary: bool = False): # noqa: FBT001, FBT002
|
||||
"""Select piece (e.g., volume of interest).
|
||||
|
||||
To use this filter set the VOI ivar which are i-j-k min/max
|
||||
indices that specify a rectangular region in the data. (Note
|
||||
that these are 0-offset.) You can also specify a sampling rate
|
||||
to subsample the data.
|
||||
|
||||
Typical applications of this filter are to extract a slice
|
||||
from a volume for image processing, subsampling large volumes
|
||||
to reduce data size, or extracting regions of a volume with
|
||||
interesting data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
voi : sequence[int]
|
||||
Length 6 iterable of ints: ``(x_min, x_max, y_min, y_max, z_min, z_max)``.
|
||||
These bounds specify the volume of interest in i-j-k min/max
|
||||
indices.
|
||||
|
||||
rate : sequence[int], default: (1, 1, 1)
|
||||
Length 3 iterable of ints: ``(xrate, yrate, zrate)``.
|
||||
|
||||
boundary : bool, default: False
|
||||
Control whether to enforce that the "boundary" of the grid
|
||||
is output in the subsampling process. (This only has
|
||||
effect when the rate in any direction is not equal to
|
||||
1). When this is on, the subsampling will always include
|
||||
the boundary of the grid even if the sample rate is
|
||||
not an even multiple of the grid dimensions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.StructuredGrid
|
||||
StructuredGrid with extracted subset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Split a grid in half.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> grid = examples.load_structured()
|
||||
>>> voi_1 = grid.extract_subset([0, 80, 0, 40, 0, 1], boundary=True)
|
||||
>>> voi_2 = grid.extract_subset([0, 80, 40, 80, 0, 1], boundary=True)
|
||||
|
||||
For fun, add the two grids back together and show they are
|
||||
identical to the original grid.
|
||||
|
||||
>>> joined = voi_1.concatenate(voi_2, axis=1)
|
||||
>>> assert np.allclose(grid.points, joined.points)
|
||||
|
||||
"""
|
||||
alg = _vtk.vtkExtractGrid()
|
||||
alg.SetVOI(voi)
|
||||
alg.SetInputDataObject(self)
|
||||
alg.SetSampleRate(rate)
|
||||
alg.SetIncludeBoundary(boundary)
|
||||
alg.Update()
|
||||
return _get_output(alg)
|
||||
|
||||
def concatenate(self, other, axis, tolerance=0.0):
|
||||
"""Concatenate a structured grid to this grid.
|
||||
|
||||
Joins structured grids into a single structured grid. Grids
|
||||
must be of compatible dimension, and must be coincident along
|
||||
the seam. Grids must have the same point and cell data. Field
|
||||
data is ignored.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : pyvista.StructuredGrid
|
||||
Structured grid to concatenate.
|
||||
|
||||
axis : int
|
||||
Axis along which to concatenate.
|
||||
|
||||
tolerance : float, default: 0.0
|
||||
Tolerance for point coincidence along joining seam.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.StructuredGrid
|
||||
Concatenated grid.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Split a grid in half and join them.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> grid = examples.load_structured()
|
||||
>>> voi_1 = grid.extract_subset([0, 80, 0, 40, 0, 1], boundary=True)
|
||||
>>> voi_2 = grid.extract_subset([0, 80, 40, 80, 0, 1], boundary=True)
|
||||
>>> joined = voi_1.concatenate(voi_2, axis=1)
|
||||
>>> f'{grid.dimensions} same as {joined.dimensions}'
|
||||
'(80, 80, 1) same as (80, 80, 1)'
|
||||
|
||||
"""
|
||||
if axis > 2:
|
||||
msg = 'Concatenation axis must be <= 2.'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# check dimensions are compatible
|
||||
for i, (dim1, dim2) in enumerate(zip(self.dimensions, other.dimensions)): # type: ignore[attr-defined]
|
||||
if i == axis:
|
||||
continue
|
||||
if dim1 != dim2:
|
||||
msg = (
|
||||
f'StructuredGrids with dimensions {self.dimensions} and {other.dimensions} ' # type: ignore[attr-defined]
|
||||
'are not compatible.'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
# check point/cell variables are the same
|
||||
if set(self.point_data.keys()) != set(other.point_data.keys()): # type: ignore[attr-defined]
|
||||
msg = 'Grid to concatenate has different point array names.'
|
||||
raise RuntimeError(msg)
|
||||
if set(self.cell_data.keys()) != set(other.cell_data.keys()): # type: ignore[attr-defined]
|
||||
msg = 'Grid to concatenate has different cell array names.'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# check that points are coincident (within tolerance) along seam
|
||||
if not np.allclose(
|
||||
np.take(self.points_matrix, indices=-1, axis=axis), # type: ignore[attr-defined]
|
||||
np.take(other.points_matrix, indices=0, axis=axis),
|
||||
atol=tolerance,
|
||||
):
|
||||
msg = (
|
||||
f'Grids cannot be joined along axis {axis}, as points '
|
||||
'are not coincident within tolerance of {tolerance}.'
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# slice to cut off the repeated grid face
|
||||
slice_spec = [slice(None, None, None)] * 3
|
||||
slice_spec[axis] = slice(0, -1, None)
|
||||
slice_spec = tuple(slice_spec) # type: ignore[assignment] # trigger basic indexing
|
||||
|
||||
# concatenate points, cutting off duplicate
|
||||
new_points = np.concatenate(
|
||||
(self.points_matrix[slice_spec], other.points_matrix), # type: ignore[attr-defined]
|
||||
axis=axis,
|
||||
)
|
||||
|
||||
# concatenate point arrays, cutting off duplicate
|
||||
new_point_data = {}
|
||||
for name, point_array in self.point_data.items(): # type: ignore[attr-defined]
|
||||
arr_1 = self._reshape_point_array(point_array) # type: ignore[attr-defined]
|
||||
arr_2 = other._reshape_point_array(other.point_data[name])
|
||||
if not np.array_equal(
|
||||
np.take(arr_1, indices=-1, axis=axis),
|
||||
np.take(arr_2, indices=0, axis=axis),
|
||||
):
|
||||
msg = (
|
||||
f'Grids cannot be joined along axis {axis}, as field '
|
||||
'`{name}` is not identical along the seam.'
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
new_point_data[name] = np.concatenate((arr_1[slice_spec], arr_2), axis=axis).ravel(
|
||||
order='F',
|
||||
)
|
||||
|
||||
new_dims = np.array(self.dimensions) # type: ignore[attr-defined]
|
||||
new_dims[axis] += other.dimensions[axis] - 1
|
||||
|
||||
# concatenate cell arrays
|
||||
new_cell_data = {}
|
||||
for name, cell_array in self.cell_data.items(): # type: ignore[attr-defined]
|
||||
arr_1 = self._reshape_cell_array(cell_array) # type: ignore[attr-defined]
|
||||
arr_2 = other._reshape_cell_array(other.cell_data[name])
|
||||
new_cell_data[name] = np.concatenate((arr_1, arr_2), axis=axis).ravel(order='F')
|
||||
|
||||
# assemble output
|
||||
joined = pyvista.StructuredGrid()
|
||||
joined.dimensions = list(new_dims)
|
||||
joined.points = new_points.reshape((-1, 3), order='F')
|
||||
joined.point_data.update(new_point_data)
|
||||
joined.cell_data.update(new_cell_data)
|
||||
|
||||
return joined
|
||||
@@ -0,0 +1,250 @@
|
||||
"""Filters module with a class to manage filters/algorithms for unstructured grid datasets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.errors import VTKVersionError
|
||||
from pyvista.core.filters import _get_output
|
||||
from pyvista.core.filters import _update_alg
|
||||
from pyvista.core.filters.data_set import DataSetFilters
|
||||
from pyvista.core.filters.poly_data import PolyDataFilters
|
||||
from pyvista.core.utilities.misc import abstract_class
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista.core._typing_core._dataset_types import _UnstructuredGridType
|
||||
|
||||
|
||||
@abstract_class
|
||||
class UnstructuredGridFilters(DataSetFilters):
|
||||
"""An internal class to manage filters/algorithms for unstructured grid datasets."""
|
||||
|
||||
@wraps(PolyDataFilters.delaunay_2d) # type: ignore[has-type]
|
||||
def delaunay_2d(self, *args, **kwargs): # numpydoc ignore=PR01,RT01
|
||||
"""Wrap ``PolyDataFilters.delaunay_2d``."""
|
||||
return PolyDataFilters.delaunay_2d(self, *args, **kwargs) # type: ignore[arg-type]
|
||||
|
||||
@wraps(PolyDataFilters.reconstruct_surface) # type: ignore[has-type]
|
||||
def reconstruct_surface(self, *args, **kwargs): # numpydoc ignore=PR01,RT01
|
||||
"""Wrap ``PolyDataFilters.reconstruct_surface``."""
|
||||
return PolyDataFilters.reconstruct_surface(self, *args, **kwargs) # type: ignore[arg-type]
|
||||
|
||||
def subdivide_tetra(self):
|
||||
"""Subdivide each tetrahedron into twelve tetrahedrons.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
UnstructuredGrid containing the subdivided tetrahedrons.
|
||||
|
||||
Examples
|
||||
--------
|
||||
First, load a sample tetrahedral UnstructuredGrid and plot it.
|
||||
|
||||
>>> from pyvista import examples
|
||||
>>> grid = examples.load_tetbeam()
|
||||
>>> grid.plot(show_edges=True, line_width=2)
|
||||
|
||||
Now, subdivide and plot.
|
||||
|
||||
>>> subdivided = grid.subdivide_tetra()
|
||||
>>> subdivided.plot(show_edges=True, line_width=2)
|
||||
|
||||
"""
|
||||
alg = _vtk.vtkSubdivideTetra()
|
||||
alg.SetInputData(self)
|
||||
_update_alg(alg)
|
||||
return _get_output(alg)
|
||||
|
||||
@_deprecate_positional_args
|
||||
def clean( # noqa: PLR0917
|
||||
self,
|
||||
tolerance=0,
|
||||
remove_unused_points: bool = True, # noqa: FBT001, FBT002
|
||||
produce_merge_map: bool = True, # noqa: FBT001, FBT002
|
||||
average_point_data: bool = True, # noqa: FBT001, FBT002
|
||||
merging_array_name=None,
|
||||
progress_bar: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Merge duplicate points and remove unused points in an UnstructuredGrid.
|
||||
|
||||
This filter, merging coincident points as defined by a merging
|
||||
tolerance and optionally removes unused points. The filter does not
|
||||
modify the topology of the input dataset, nor change the types of
|
||||
cells. It may however, renumber the cell connectivity ids.
|
||||
|
||||
This filter implements :vtk:`vtkStaticCleanUnstructuredGrid`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tolerance : float, default: 0.0
|
||||
The absolute point merging tolerance.
|
||||
|
||||
remove_unused_points : bool, default: True
|
||||
Indicate whether points unused by any cell are removed from the
|
||||
output. Note that when this is off, the filter can successfully
|
||||
process datasets with no cells (and just points). If on in this
|
||||
case, and there are no cells, the output will be empty.
|
||||
|
||||
produce_merge_map : bool, default: False
|
||||
Indicate whether a merge map should be produced on output.
|
||||
The merge map, if requested, maps each input point to its
|
||||
output point id, or provides a value of -1 if the input point
|
||||
is not used in the output. The merge map is associated with
|
||||
the filter's output field data and is named ``"PointMergeMap"``.
|
||||
|
||||
average_point_data : bool, default: True
|
||||
Indicate whether point coordinates and point data of merged points
|
||||
are averaged. When ``True``, the data coordinates and attribute
|
||||
values of all merged points are averaged. When ``False``, the point
|
||||
coordinate and data of the single remaining merged point is
|
||||
retained.
|
||||
|
||||
merging_array_name : str, optional
|
||||
If a ``merging_array_name`` is specified and exists in the
|
||||
``point_data``, then point merging will switch into a mode where
|
||||
merged points must be both geometrically coincident and have
|
||||
matching point data. When set, ``tolerance`` has no effect.
|
||||
|
||||
progress_bar : bool, default: False
|
||||
Display a progress bar to indicate progress.
|
||||
|
||||
Returns
|
||||
-------
|
||||
UnstructuredGrid
|
||||
Cleaned unstructured grid.
|
||||
|
||||
See Also
|
||||
--------
|
||||
remove_unused_points
|
||||
Strictly remove unused points `without` merging points.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Demonstrate cleaning an UnstructuredGrid and show how it can be used to
|
||||
average the point data across merged points.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> hexbeam = examples.load_hexbeam()
|
||||
>>> hexbeam_shifted = hexbeam.translate([1, 0, 0])
|
||||
>>> hexbeam.point_data['data'] = [0] * hexbeam.n_points
|
||||
>>> hexbeam_shifted.point_data['data'] = [1] * hexbeam.n_points
|
||||
>>> merged = hexbeam.merge(hexbeam_shifted, merge_points=False)
|
||||
>>> cleaned = merged.clean(average_point_data=True)
|
||||
>>> cleaned.n_points < merged.n_points
|
||||
True
|
||||
|
||||
Show how point averaging using the ``clean`` method with
|
||||
``average_point_data=True`` results in averaged point data for merged
|
||||
points.
|
||||
|
||||
>>> pl = pv.Plotter(shape=(1, 2))
|
||||
>>> _ = pl.add_mesh(merged, scalars='data', show_scalar_bar=False)
|
||||
>>> pl.subplot(0, 1)
|
||||
>>> _ = pl.add_mesh(cleaned, scalars='data')
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
try:
|
||||
from vtkmodules.vtkFiltersCore import vtkStaticCleanUnstructuredGrid # noqa: PLC0415
|
||||
except ImportError: # pragma no cover
|
||||
msg = 'UnstructuredGrid.clean requires VTK >= 9.2.2'
|
||||
raise VTKVersionError(msg) from None
|
||||
|
||||
alg = vtkStaticCleanUnstructuredGrid()
|
||||
# https://github.com/pyvista/pyvista/pull/6337
|
||||
alg.SetInputDataObject(self.copy()) # type: ignore[attr-defined]
|
||||
alg.SetAbsoluteTolerance(True)
|
||||
alg.SetTolerance(tolerance)
|
||||
alg.SetMergingArray(merging_array_name)
|
||||
alg.SetRemoveUnusedPoints(remove_unused_points)
|
||||
alg.SetProduceMergeMap(produce_merge_map)
|
||||
alg.SetAveragePointData(average_point_data)
|
||||
_update_alg(alg, progress_bar=progress_bar, message='Cleaning Unstructured Grid')
|
||||
return _get_output(alg)
|
||||
|
||||
def remove_unused_points( # type: ignore[misc]
|
||||
self: _UnstructuredGridType,
|
||||
*,
|
||||
inplace: bool = False,
|
||||
) -> _UnstructuredGridType:
|
||||
"""Remove points which are not used by any cells.
|
||||
|
||||
Unlike :meth:`clean`, this filter does `not` merge points.
|
||||
|
||||
.. versionadded:: 0.46
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inplace : bool, default: False
|
||||
If ``True`` the mesh is updated in-place, otherwise a copy is returned.
|
||||
|
||||
See Also
|
||||
--------
|
||||
pyvista.PolyDataFilters.remove_unused_points
|
||||
|
||||
Returns
|
||||
-------
|
||||
UnstructuredGrid
|
||||
Mesh with unused points removed.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create :class:`~pyvista.UnstructuredGrid` with three points. The first two points are
|
||||
coincident and associated with :attr:`~pyvista.CellType.VERTEX` cells, and the third point
|
||||
is "unused" and not associated with any cells.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> cells = [1, 0, 1, 1]
|
||||
>>> celltypes = [pv.CellType.VERTEX, pv.CellType.VERTEX]
|
||||
>>> points = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]]
|
||||
>>> grid = pv.UnstructuredGrid(cells, celltypes, points)
|
||||
>>> grid
|
||||
UnstructuredGrid (...)
|
||||
N Cells: 2
|
||||
N Points: 3
|
||||
X Bounds: 0.000e+00, 1.000e+00
|
||||
Y Bounds: 0.000e+00, 1.000e+00
|
||||
Z Bounds: 0.000e+00, 1.000e+00
|
||||
N Arrays: 0
|
||||
|
||||
Since the third point is unused, we can remove it. Note that coincident points are `not`
|
||||
merged by this filter, so the two vertex points are kept as-is.
|
||||
|
||||
>>> grid = grid.remove_unused_points()
|
||||
>>> grid
|
||||
UnstructuredGrid (...)
|
||||
N Cells: 2
|
||||
N Points: 2
|
||||
X Bounds: 0.000e+00, 0.000e+00
|
||||
Y Bounds: 0.000e+00, 0.000e+00
|
||||
Z Bounds: 0.000e+00, 0.000e+00
|
||||
N Arrays: 0
|
||||
|
||||
"""
|
||||
if self.is_empty:
|
||||
return self if inplace else self.copy()
|
||||
|
||||
out = self.copy()
|
||||
|
||||
# Need to add an extra "dummy" cell to force vtkExtractCells to remap the point IDs
|
||||
cell_array = out.GetCells()
|
||||
cell_array.InsertNextCell(1)
|
||||
|
||||
# Extract all the cells, except for the dummy cell
|
||||
out = out.extract_cells(np.arange(self.n_cells))
|
||||
if (name := 'vtkOriginalPointIds') in (data := out.point_data):
|
||||
del data[name]
|
||||
if (name := 'vtkOriginalCellIds') in (data := out.cell_data):
|
||||
del data[name]
|
||||
|
||||
if inplace:
|
||||
self.copy_from(out)
|
||||
return self
|
||||
return out
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,407 @@
|
||||
"""Wrappers for :vtk:`vtkDataObject`.
|
||||
|
||||
The data objects does not have any sort of spatial reference.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
|
||||
from . import _vtk_core as _vtk
|
||||
from .dataobject import DataObject
|
||||
from .datasetattributes import DataSetAttributes
|
||||
from .utilities.arrays import FieldAssociation
|
||||
from .utilities.arrays import FieldLiteral
|
||||
from .utilities.arrays import RowLiteral
|
||||
from .utilities.arrays import get_array
|
||||
from .utilities.arrays import row_array
|
||||
|
||||
|
||||
class Table(DataObject, _vtk.vtkTable):
|
||||
"""Wrapper for the :vtk:`vtkTable` class.
|
||||
|
||||
Create by passing a 2D NumPy array of shape (``n_rows`` by ``n_columns``)
|
||||
or from a dictionary containing NumPy arrays.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> arrays = np.random.default_rng().random((100, 3))
|
||||
>>> table = pv.Table(arrays)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, deep: bool = True, **kwargs): # noqa: ARG002
|
||||
"""Initialize the table."""
|
||||
super().__init__()
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], _vtk.vtkTable):
|
||||
if deep:
|
||||
self.deep_copy(args[0])
|
||||
else:
|
||||
self.shallow_copy(args[0])
|
||||
elif isinstance(args[0], (np.ndarray, list)):
|
||||
self._from_arrays(args[0])
|
||||
elif isinstance(args[0], dict):
|
||||
self._from_dict(args[0])
|
||||
elif 'pandas.core.frame.DataFrame' in str(type(args[0])):
|
||||
self._from_pandas(args[0])
|
||||
else:
|
||||
msg = f'Table unable to be made from ({type(args[0])})'
|
||||
raise TypeError(msg)
|
||||
|
||||
@staticmethod
|
||||
def _prepare_arrays(arrays):
|
||||
arrays = np.asarray(arrays)
|
||||
if arrays.ndim == 1:
|
||||
return np.reshape(arrays, (1, -1))
|
||||
elif arrays.ndim == 2:
|
||||
return arrays.T
|
||||
else:
|
||||
msg = 'Only 1D or 2D arrays are supported by Tables.'
|
||||
raise ValueError(msg)
|
||||
|
||||
def _from_arrays(self, arrays) -> None:
|
||||
np_table = self._prepare_arrays(arrays)
|
||||
for i, array in enumerate(np_table):
|
||||
self.row_arrays[f'Array {i}'] = array
|
||||
|
||||
def _from_dict(self, array_dict):
|
||||
for array in array_dict.values():
|
||||
if not isinstance(array, np.ndarray) and array.ndim < 3:
|
||||
msg = 'Dictionary must contain only NumPy arrays with maximum of 2D.'
|
||||
raise ValueError(msg)
|
||||
for name, array in array_dict.items():
|
||||
self.row_arrays[name] = array
|
||||
|
||||
def _from_pandas(self, data_frame) -> None:
|
||||
for name in data_frame.keys():
|
||||
self.row_arrays[name] = data_frame[name].values
|
||||
|
||||
@property
|
||||
def n_rows(self):
|
||||
"""Return the number of rows.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of rows.
|
||||
|
||||
"""
|
||||
return self.GetNumberOfRows()
|
||||
|
||||
@n_rows.setter
|
||||
def n_rows(self, n) -> None:
|
||||
"""Set the number of rows.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : int
|
||||
The number of rows.
|
||||
|
||||
"""
|
||||
self.SetNumberOfRows(n)
|
||||
|
||||
@property
|
||||
def n_columns(self):
|
||||
"""Return the number of columns.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of columns.
|
||||
|
||||
"""
|
||||
return self.GetNumberOfColumns()
|
||||
|
||||
@property
|
||||
def n_arrays(self):
|
||||
"""Return the number of columns.
|
||||
|
||||
Alias for: ``n_columns``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of columns.
|
||||
|
||||
"""
|
||||
return self.n_columns
|
||||
|
||||
def _row_array(self, name=None):
|
||||
"""Return row scalars of a vtk object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of row scalars to retrieve.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Numpy array of the row.
|
||||
|
||||
"""
|
||||
return self.row_arrays.get_array(name)
|
||||
|
||||
@property
|
||||
def row_arrays(self):
|
||||
"""Return the all row arrays.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The all row arrays.
|
||||
|
||||
"""
|
||||
return DataSetAttributes(
|
||||
vtkobject=self.GetRowData(),
|
||||
dataset=self, # type: ignore[arg-type]
|
||||
association=FieldAssociation.ROW,
|
||||
)
|
||||
|
||||
def keys(self):
|
||||
"""Return the table keys.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List of the array names of this table.
|
||||
|
||||
"""
|
||||
return self.row_arrays.keys()
|
||||
|
||||
def items(self):
|
||||
"""Return the table items.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List containing tuples pairs of the name and array of the table arrays.
|
||||
|
||||
"""
|
||||
return self.row_arrays.items()
|
||||
|
||||
def values(self):
|
||||
"""Return the table values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List of the table arrays.
|
||||
|
||||
"""
|
||||
return self.row_arrays.values()
|
||||
|
||||
def update(self, data) -> None:
|
||||
"""Set the table data using a dict-like update.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : DataSetAttributes
|
||||
Other dataset attributes to update from.
|
||||
|
||||
"""
|
||||
if isinstance(data, (np.ndarray, list)):
|
||||
# Allow table updates using array data
|
||||
data = self._prepare_arrays(data)
|
||||
data = {f'Array {i}': array for i, array in enumerate(data)}
|
||||
self.row_arrays.update(data)
|
||||
self.Modified()
|
||||
|
||||
def pop(self, name):
|
||||
"""Pop off an array by the specified name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : int or str
|
||||
Index or name of the row array.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.pyvista_ndarray
|
||||
PyVista array.
|
||||
|
||||
"""
|
||||
return self.row_arrays.pop(name)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Search row data for an array."""
|
||||
return self._row_array(name=index)
|
||||
|
||||
def _ipython_key_completions_(self):
|
||||
return self.keys()
|
||||
|
||||
def get(self, index):
|
||||
"""Get an array by its name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : int or str
|
||||
Index or name of the row.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.pyvista_ndarray
|
||||
PyVista array.
|
||||
|
||||
"""
|
||||
return self[index]
|
||||
|
||||
def __setitem__(self, name, scalars) -> None:
|
||||
"""Add/set an array in the row_arrays."""
|
||||
self.row_arrays[name] = scalars
|
||||
|
||||
def _remove_array(self, _, key) -> None:
|
||||
"""Remove a single array by name from each field (internal helper)."""
|
||||
self.row_arrays.remove(key)
|
||||
|
||||
def __delitem__(self, name) -> None:
|
||||
"""Remove an array by the specified name."""
|
||||
del self.row_arrays[name]
|
||||
|
||||
def __iter__(self):
|
||||
"""Return the iterator across all arrays."""
|
||||
for array_name in self.row_arrays:
|
||||
yield self.row_arrays[array_name]
|
||||
|
||||
def _get_attrs(self):
|
||||
"""Return the representation methods."""
|
||||
attrs = []
|
||||
attrs.append(('N Rows', self.n_rows, '{}'))
|
||||
return attrs
|
||||
|
||||
def _repr_html_(self):
|
||||
"""Return a pretty representation for Jupyter notebooks.
|
||||
|
||||
It includes header details and information about all arrays.
|
||||
|
||||
"""
|
||||
fmt = ''
|
||||
if self.n_arrays > 0:
|
||||
fmt += "<table style='width: 100%;'>"
|
||||
fmt += '<tr><th>Header</th><th>Data Arrays</th></tr>'
|
||||
fmt += '<tr><td>'
|
||||
# Get the header info
|
||||
fmt += self.head(display=False, html=True)
|
||||
# Fill out scalars arrays
|
||||
if self.n_arrays > 0:
|
||||
fmt += '</td><td>'
|
||||
fmt += '\n'
|
||||
fmt += "<table style='width: 100%;'>\n"
|
||||
titles = ['Name', 'Type', 'N Comp', 'Min', 'Max']
|
||||
fmt += '<tr>' + ''.join([f'<th>{t}</th>' for t in titles]) + '</tr>\n'
|
||||
row = '<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>\n'
|
||||
row = '<tr>' + ''.join(['<td>{}</td>' for i in range(len(titles))]) + '</tr>\n'
|
||||
|
||||
def format_array(key):
|
||||
"""Format array information for printing (internal helper)."""
|
||||
arr = row_array(self, key)
|
||||
dl, dh = self.get_data_range(key)
|
||||
dl = pyvista.FLOAT_FORMAT.format(dl) # type: ignore[assignment]
|
||||
dh = pyvista.FLOAT_FORMAT.format(dh) # type: ignore[assignment]
|
||||
ncomp = 0 if arr is None else arr.shape[1] if arr.ndim > 1 else 1
|
||||
dtype = None if arr is None else arr.dtype
|
||||
return row.format(key, dtype, ncomp, dl, dh)
|
||||
|
||||
for i in range(self.n_arrays):
|
||||
key = self.GetRowData().GetArrayName(i)
|
||||
fmt += format_array(key)
|
||||
|
||||
fmt += '</table>\n'
|
||||
fmt += '\n'
|
||||
fmt += '</td></tr> </table>'
|
||||
return fmt
|
||||
|
||||
def __repr__(self):
|
||||
"""Return the object representation."""
|
||||
return self.head(display=False, html=False)
|
||||
|
||||
def __str__(self):
|
||||
"""Return the object string representation."""
|
||||
return self.head(display=False, html=False)
|
||||
|
||||
def to_pandas(self):
|
||||
"""Create a Pandas DataFrame from this Table.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pandas.DataFrame
|
||||
This table represented as a pandas dataframe.
|
||||
|
||||
"""
|
||||
try:
|
||||
import pandas as pd # noqa: PLC0415
|
||||
except ImportError: # pragma: no cover
|
||||
msg = 'Install ``pandas`` to use this feature.'
|
||||
raise ImportError(msg)
|
||||
data_frame = pd.DataFrame()
|
||||
for name, array in self.items():
|
||||
data_frame[name] = array
|
||||
return data_frame
|
||||
|
||||
def save(self, *args, **kwargs): # pragma: no cover
|
||||
"""Save the table."""
|
||||
msg = "Please use the `to_pandas` method and harness Pandas' wonderful file IO methods."
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
def get_data_range( # type: ignore[override]
|
||||
self,
|
||||
arr: str | None = None,
|
||||
preference: FieldLiteral | RowLiteral = 'row',
|
||||
) -> tuple[float, float]:
|
||||
"""Get the min and max of a named array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arr : str, numpy.ndarray, optional
|
||||
The name of the array to get the range. If ``None``, the active scalar
|
||||
is used.
|
||||
|
||||
preference : str, optional
|
||||
When scalars is specified, this is the preferred array type
|
||||
to search for in the dataset. Must be either ``'row'`` or
|
||||
``'field'``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
``(min, max)`` of the array.
|
||||
|
||||
"""
|
||||
if arr is None:
|
||||
# use the first array in the row data
|
||||
arr = self.GetRowData().GetArrayName(0)
|
||||
if isinstance(arr, str):
|
||||
arr = get_array(self, arr, preference=preference) # type: ignore[assignment]
|
||||
# If array has no tuples return a NaN range
|
||||
if arr.size == 0 or not np.issubdtype(arr.dtype, np.number): # type: ignore[attr-defined]
|
||||
return (np.nan, np.nan)
|
||||
# Use the array range
|
||||
return np.nanmin(arr), np.nanmax(arr)
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return ``True`` if the table has no rows and no columns.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> table = pv.Table()
|
||||
>>> table.is_empty
|
||||
True
|
||||
|
||||
>>> arrays = np.random.default_rng().random((100, 3))
|
||||
>>> table = pv.Table(arrays)
|
||||
>>> table.is_empty
|
||||
False
|
||||
|
||||
"""
|
||||
return self.n_rows == 0 and self.n_columns == 0
|
||||
@@ -0,0 +1,287 @@
|
||||
"""Contains the PartitionedDataSet class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import MutableSequence
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import overload
|
||||
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
from . import _vtk_core as _vtk
|
||||
from .dataobject import DataObject
|
||||
from .errors import PartitionedDataSetsNotSupported
|
||||
from .utilities.helpers import is_pyvista_dataset
|
||||
from .utilities.helpers import wrap
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from .dataset import DataSet
|
||||
from .utilities.arrays import FieldAssociation
|
||||
|
||||
|
||||
class PartitionedDataSet(DataObject, MutableSequence, _vtk.vtkPartitionedDataSet): # type: ignore[type-arg]
|
||||
"""Wrapper for the :vtk:`vtkPartitionedDataSet` class.
|
||||
|
||||
DataSet which composite dataset to encapsulates a dataset consisting of partitions.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> data = [
|
||||
... pv.Sphere(center=(2, 0, 0)),
|
||||
... pv.Cube(center=(0, 2, 0)),
|
||||
... pv.Cone(),
|
||||
... ]
|
||||
>>> partitions = pv.PartitionedDataSet(data)
|
||||
>>> len(partitions)
|
||||
3
|
||||
|
||||
"""
|
||||
|
||||
if _vtk.vtk_version_info >= (9, 1):
|
||||
_WRITERS = {'.vtpd': _vtk.vtkXMLPartitionedDataSetWriter}
|
||||
|
||||
if _vtk.vtk_version_info >= (9, 4):
|
||||
_WRITERS['.vtkhdf'] = _vtk.vtkHDFWriter
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the PartitionedDataSet."""
|
||||
super().__init__()
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], _vtk.vtkPartitionedDataSet):
|
||||
deep = kwargs.get('deep', True)
|
||||
if deep:
|
||||
self.deep_copy(args[0])
|
||||
else:
|
||||
raise PartitionedDataSetsNotSupported
|
||||
elif isinstance(args[0], (list, tuple)):
|
||||
for partition in args[0]:
|
||||
self.append(partition)
|
||||
self.wrap_nested()
|
||||
|
||||
def wrap_nested(self) -> None:
|
||||
"""Ensure that all nested data structures are wrapped as PyVista datasets.
|
||||
|
||||
This is performed in place.
|
||||
|
||||
"""
|
||||
for i in range(self.n_partitions):
|
||||
partition = self.GetPartition(i)
|
||||
if not is_pyvista_dataset(partition):
|
||||
self.SetPartition(i, wrap(partition))
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> DataSet | None: ... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> PartitionedDataSet: ... # pragma: no cover
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Get a partition by its index."""
|
||||
if isinstance(index, slice):
|
||||
return PartitionedDataSet([self[i] for i in range(self.n_partitions)[index]])
|
||||
else:
|
||||
if index < -self.n_partitions or index >= self.n_partitions:
|
||||
msg = f'index ({index}) out of range for this dataset.'
|
||||
raise IndexError(msg)
|
||||
if index < 0:
|
||||
index = self.n_partitions + index
|
||||
return wrap(self.GetPartition(index))
|
||||
|
||||
@overload
|
||||
def __setitem__(self, index: int, data: DataSet | None) -> None: ... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def __setitem__(
|
||||
self, index: slice, data: Iterable[DataSet | None]
|
||||
) -> None: ... # pragma: no cover
|
||||
|
||||
def __setitem__(
|
||||
self,
|
||||
index: int | slice,
|
||||
data,
|
||||
):
|
||||
"""Set a partition with a VTK data object."""
|
||||
if isinstance(index, slice):
|
||||
for i, d in zip(range(self.n_partitions)[index], data):
|
||||
self.SetPartition(i, d)
|
||||
else:
|
||||
if index < -self.n_partitions or index >= self.n_partitions:
|
||||
msg = f'index ({index}) out of range for this dataset.'
|
||||
raise IndexError(msg)
|
||||
if index < 0:
|
||||
index = self.n_partitions + index
|
||||
self.SetPartition(index, data)
|
||||
|
||||
def __delitem__(self, index: int | slice) -> None:
|
||||
"""Remove a partition at the specified index are not supported."""
|
||||
raise PartitionedDataSetsNotSupported
|
||||
|
||||
def insert(self, index: int, dataset: DataSet) -> None: # numpydoc ignore=PR01
|
||||
"""Insert data before index."""
|
||||
index = range(self.n_partitions)[index]
|
||||
self.n_partitions += 1
|
||||
for i in reversed(range(index, self.n_partitions - 1)):
|
||||
self[i + 1] = self[i]
|
||||
self[index] = dataset
|
||||
|
||||
def pop(self, index: int = -1) -> None: # numpydoc ignore=PR01 # noqa: ARG002
|
||||
"""Pop off a partition at the specified index are not supported."""
|
||||
raise PartitionedDataSetsNotSupported
|
||||
|
||||
def _get_attrs(self):
|
||||
"""Return the representation methods (internal helper)."""
|
||||
attrs = []
|
||||
attrs.append(('N Partitions', self.n_partitions, '{}'))
|
||||
return attrs
|
||||
|
||||
def _repr_html_(self) -> str:
|
||||
"""Define a pretty representation for Jupyter notebooks."""
|
||||
fmt = ''
|
||||
fmt += "<table style='width: 100%;'>"
|
||||
fmt += '<tr><th>Information</th><th>Partitions</th></tr>'
|
||||
fmt += '<tr><td>'
|
||||
fmt += '\n'
|
||||
fmt += '<table>\n'
|
||||
fmt += f'<tr><th>{type(self).__name__}</th><th>Values</th></tr>\n'
|
||||
row = '<tr><td>{}</td><td>{}</td></tr>\n'
|
||||
for attr in self._get_attrs():
|
||||
try:
|
||||
fmt += row.format(attr[0], attr[2].format(*attr[1]))
|
||||
except TypeError:
|
||||
fmt += row.format(attr[0], attr[2].format(attr[1]))
|
||||
fmt += '</table>\n'
|
||||
fmt += '\n'
|
||||
fmt += '</td><td>'
|
||||
fmt += '\n'
|
||||
fmt += '<table>\n'
|
||||
row = '<tr><th>{}</th><th>{}</th></tr>\n'
|
||||
fmt += row.format('Index', 'Type')
|
||||
for i in range(self.n_partitions):
|
||||
data = self[i]
|
||||
fmt += row.format(i, type(data).__name__)
|
||||
fmt += '</table>\n'
|
||||
fmt += '\n'
|
||||
fmt += '</td></tr> </table>'
|
||||
return fmt
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Define an adequate representation."""
|
||||
fmt = f'{type(self).__name__} ({hex(id(self))})\n'
|
||||
max_len = max(len(attr[0]) for attr in self._get_attrs()) + 4
|
||||
row = f' {{:{max_len}s}}' + '{}\n'
|
||||
for attr in self._get_attrs():
|
||||
try:
|
||||
fmt += row.format(attr[0], attr[2].format(*attr[1]))
|
||||
except TypeError:
|
||||
fmt += row.format(attr[0], attr[2].format(attr[1]))
|
||||
return fmt.strip()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return the str representation of the multi partition."""
|
||||
return PartitionedDataSet.__repr__(self)
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Return the number of partitions."""
|
||||
return self.n_partitions
|
||||
|
||||
def copy_meta_from(self, ido, deep) -> None: # numpydoc ignore=PR01
|
||||
"""Copy pyvista meta data onto this object from another object."""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def copy(self, deep: bool = True): # noqa: FBT001, FBT002
|
||||
"""Return a copy of the PartitionedDataSet.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
deep : bool, default: True
|
||||
When ``True``, make a full copy of the object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PartitionedDataSet
|
||||
Deep or shallow copy of the ``PartitionedDataSet``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> data = [
|
||||
... pv.Sphere(center=(2, 0, 0)),
|
||||
... pv.Cube(center=(0, 2, 0)),
|
||||
... pv.Cone(),
|
||||
... ]
|
||||
>>> partitions = pv.PartitionedDataSet(data)
|
||||
>>> new_partitions = partitions.copy()
|
||||
>>> len(new_partitions)
|
||||
3
|
||||
|
||||
"""
|
||||
thistype = type(self)
|
||||
newobject = thistype()
|
||||
if deep:
|
||||
newobject.deep_copy(self)
|
||||
else:
|
||||
raise PartitionedDataSetsNotSupported
|
||||
newobject.copy_meta_from(self, deep)
|
||||
newobject.wrap_nested()
|
||||
return newobject
|
||||
|
||||
@property
|
||||
def n_partitions(self) -> int:
|
||||
"""Return the number of partitions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of partitions.
|
||||
|
||||
"""
|
||||
return self.GetNumberOfPartitions()
|
||||
|
||||
@n_partitions.setter
|
||||
def n_partitions(self, n) -> None:
|
||||
self.SetNumberOfPartitions(n)
|
||||
self.Modified()
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return ``True`` if there are no partitions.
|
||||
|
||||
.. versionadded:: 0.46
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.PartitionedDataSet()
|
||||
>>> mesh.is_empty
|
||||
True
|
||||
|
||||
>>> mesh.append(pv.Sphere())
|
||||
>>> mesh.is_empty
|
||||
False
|
||||
|
||||
"""
|
||||
return self.n_partitions == 0
|
||||
|
||||
def append(self, dataset) -> None:
|
||||
"""Add a data set to the next partition index.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dataset : pyvista.DataSet
|
||||
Dataset to append to this partitioned dataset.
|
||||
|
||||
"""
|
||||
index = self.n_partitions
|
||||
self.n_partitions += 1
|
||||
self[index] = dataset
|
||||
|
||||
def get_data_range( # numpydoc ignore=RT01
|
||||
self: Self, name: str | None, preference: FieldAssociation | str
|
||||
) -> tuple[float, float]: # pragma: no cover
|
||||
"""Get the non-NaN min and max of a named array."""
|
||||
return DataObject.get_data_range(self, name=name, preference=preference)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
||||
"""Contains pyvista_ndarray a numpy ndarray type used in pyvista."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import cast
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import _vtk_core as _vtk
|
||||
from .utilities.arrays import FieldAssociation
|
||||
from .utilities.arrays import convert_array
|
||||
from .utilities.misc import _NoNewAttrMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
import numpy.typing as npt
|
||||
|
||||
from pyvista import DataSet
|
||||
|
||||
from ._typing_core import ArrayLike
|
||||
from ._typing_core import NumpyArray
|
||||
|
||||
|
||||
class pyvista_ndarray(_NoNewAttrMixin, np.ndarray): # numpydoc ignore=PR02 # noqa: N801
|
||||
"""A ndarray which references the owning dataset and the underlying vtk array.
|
||||
|
||||
This array can be acted upon just like a :class:`numpy.ndarray`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : ArrayLike or :vtk:`vtkAbstractArray`
|
||||
Array like.
|
||||
|
||||
dataset : DataSet
|
||||
Input dataset.
|
||||
|
||||
association : pyvista.core.utilities.arrays.FieldAssociation
|
||||
Field association.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Return the points of a Sphere as a :class:`pyvista.pyvista_ndarray`.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mesh.points # doctest:+SKIP
|
||||
pyvista_ndarray([[-5.5511151e-17, 0.0000000e+00, -5.0000000e-01],
|
||||
[ 5.5511151e-17, 0.0000000e+00, 5.0000000e-01],
|
||||
[-5.4059509e-02, 0.0000000e+00, -4.9706897e-01],
|
||||
...,
|
||||
[-1.5616201e-01, -3.3193260e-02, 4.7382659e-01],
|
||||
[-1.0513641e-01, -2.2347433e-02, 4.8831028e-01],
|
||||
[-5.2878179e-02, -1.1239604e-02, 4.9706897e-01]],
|
||||
dtype=float32)
|
||||
|
||||
"""
|
||||
|
||||
def __new__( # noqa: PYI034
|
||||
cls: type[pyvista_ndarray],
|
||||
array: ArrayLike[float] | _vtk.vtkAbstractArray,
|
||||
dataset: DataSet | _vtk.vtkDataSet | _vtk.VTKObjectWrapper | None = None,
|
||||
association: FieldAssociation = FieldAssociation.NONE,
|
||||
) -> pyvista_ndarray:
|
||||
"""Allocate the array."""
|
||||
if isinstance(array, _vtk.vtkAbstractArray):
|
||||
obj = convert_array(array).view(cls)
|
||||
obj.VTKObject = array
|
||||
elif isinstance(array, Iterable):
|
||||
obj = np.asarray(array).view(cls)
|
||||
else:
|
||||
msg = ( # type: ignore[unreachable]
|
||||
f'pyvista_ndarray got an invalid type {type(array)}. '
|
||||
'Expected an Iterable or vtk.vtkAbstractArray'
|
||||
)
|
||||
raise TypeError(msg)
|
||||
|
||||
obj.association = association
|
||||
obj.dataset = _vtk.vtkWeakReference()
|
||||
if isinstance(dataset, _vtk.VTKObjectWrapper):
|
||||
obj.dataset.Set(dataset.VTKObject)
|
||||
else:
|
||||
obj.dataset.Set(cast('_vtk.vtkDataSet', dataset))
|
||||
return obj
|
||||
|
||||
def __array_finalize__(self: pyvista_ndarray, obj: npt.NDArray[Any] | None) -> None:
|
||||
"""Finalize array (associate with parent metadata)."""
|
||||
# this is necessary to ensure that views/slices of pyvista_ndarray
|
||||
# objects stay associated with those of their parents.
|
||||
#
|
||||
# the VTKArray class uses attributes called `DataSet` and `Association`
|
||||
# to hold this data. I don't know why this class doesn't use the same
|
||||
# convention, but here we just map those over to the appropriate
|
||||
# attributes of this class
|
||||
_vtk.VTKArray.__array_finalize__(self, obj) # type: ignore[arg-type]
|
||||
if np.shares_memory(self, obj):
|
||||
self.dataset = getattr(obj, 'dataset', None)
|
||||
self.association = getattr(obj, 'association', FieldAssociation.NONE)
|
||||
self.VTKObject = getattr(obj, 'VTKObject', None)
|
||||
else:
|
||||
self.dataset = None
|
||||
self.association = FieldAssociation.NONE
|
||||
self.VTKObject = None
|
||||
|
||||
def __setitem__(self: pyvista_ndarray, key: int | NumpyArray[int], value: Any) -> None: # type: ignore[override]
|
||||
"""Implement [] set operator.
|
||||
|
||||
When the array is changed it triggers "Modified()" which updates
|
||||
all upstream objects, including any render windows holding the
|
||||
object.
|
||||
"""
|
||||
super().__setitem__(key, value)
|
||||
if self.VTKObject is not None:
|
||||
self.VTKObject.Modified()
|
||||
|
||||
# the associated dataset should also be marked as modified
|
||||
dataset = self.dataset
|
||||
if dataset is not None and dataset.Get():
|
||||
dataset.Get().Modified()
|
||||
|
||||
def __array_wrap__(self: pyvista_ndarray, out_arr, context=None, return_scalar: bool = False): # noqa: ANN001, ANN204, FBT001, FBT002
|
||||
"""Return a numpy scalar if array is 0d.
|
||||
|
||||
See https://github.com/numpy/numpy/issues/5819
|
||||
|
||||
"""
|
||||
if out_arr.ndim:
|
||||
return super().__array_wrap__(out_arr, context, return_scalar)
|
||||
|
||||
# Match numpy's behavior and return a numpy dtype scalar
|
||||
return out_arr[()]
|
||||
|
||||
__getattr__ = _vtk.VTKObjectWrapperCheckSnakeCase.__getattr__
|
||||
@@ -0,0 +1,235 @@
|
||||
"""Utilities routines."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
|
||||
from .arrays import FieldAssociation as FieldAssociation
|
||||
from .arrays import array_from_vtkmatrix as array_from_vtkmatrix
|
||||
from .arrays import cell_array as cell_array
|
||||
from .arrays import convert_array as convert_array
|
||||
from .arrays import convert_string_array as convert_string_array
|
||||
from .arrays import field_array as field_array
|
||||
from .arrays import get_array as get_array
|
||||
from .arrays import get_array_association as get_array_association
|
||||
from .arrays import get_vtk_type as get_vtk_type
|
||||
from .arrays import parse_field_choice as parse_field_choice
|
||||
from .arrays import point_array as point_array
|
||||
from .arrays import raise_has_duplicates as raise_has_duplicates
|
||||
from .arrays import raise_not_matching as raise_not_matching
|
||||
from .arrays import row_array as row_array
|
||||
from .arrays import set_default_active_scalars as set_default_active_scalars
|
||||
from .arrays import set_default_active_vectors as set_default_active_vectors
|
||||
from .arrays import vtk_bit_array_to_char as vtk_bit_array_to_char
|
||||
from .arrays import vtk_id_list_to_array as vtk_id_list_to_array
|
||||
from .arrays import vtkmatrix_from_array as vtkmatrix_from_array
|
||||
from .cells import create_mixed_cells as create_mixed_cells
|
||||
from .cells import get_mixed_cells as get_mixed_cells
|
||||
from .cells import ncells_from_cells as ncells_from_cells
|
||||
from .cells import numpy_to_idarr as numpy_to_idarr
|
||||
from .features import cartesian_to_spherical as cartesian_to_spherical
|
||||
from .features import create_grid as create_grid
|
||||
from .features import grid_from_sph_coords as grid_from_sph_coords
|
||||
from .features import merge as merge
|
||||
from .features import perlin_noise as perlin_noise
|
||||
from .features import sample_function as sample_function
|
||||
from .features import spherical_to_cartesian as spherical_to_cartesian
|
||||
from .features import transform_vectors_sph_to_cart as transform_vectors_sph_to_cart
|
||||
from .features import voxelize as voxelize
|
||||
from .features import voxelize_volume as voxelize_volume
|
||||
from .fileio import from_meshio as from_meshio
|
||||
from .fileio import get_ext as get_ext
|
||||
from .fileio import is_meshio_mesh as is_meshio_mesh
|
||||
from .fileio import read as read
|
||||
from .fileio import read_exodus as read_exodus
|
||||
from .fileio import read_grdecl as read_grdecl
|
||||
from .fileio import read_meshio as read_meshio
|
||||
from .fileio import read_pickle as read_pickle
|
||||
from .fileio import read_texture as read_texture
|
||||
from .fileio import save_meshio as save_meshio
|
||||
from .fileio import save_pickle as save_pickle
|
||||
from .fileio import set_pickle_format as set_pickle_format
|
||||
from .fileio import set_vtkwriter_mode as set_vtkwriter_mode
|
||||
from .fileio import to_meshio as to_meshio
|
||||
from .geometric_objects import NORMALS as NORMALS
|
||||
from .geometric_objects import Arrow as Arrow
|
||||
from .geometric_objects import Box as Box
|
||||
from .geometric_objects import Capsule as Capsule
|
||||
from .geometric_objects import Circle as Circle
|
||||
from .geometric_objects import CircularArc as CircularArc
|
||||
from .geometric_objects import CircularArcFromNormal as CircularArcFromNormal
|
||||
from .geometric_objects import Cone as Cone
|
||||
from .geometric_objects import Cube as Cube
|
||||
from .geometric_objects import Cylinder as Cylinder
|
||||
from .geometric_objects import CylinderStructured as CylinderStructured
|
||||
from .geometric_objects import Disc as Disc
|
||||
from .geometric_objects import Dodecahedron as Dodecahedron
|
||||
from .geometric_objects import Ellipse as Ellipse
|
||||
from .geometric_objects import Icosahedron as Icosahedron
|
||||
from .geometric_objects import Icosphere as Icosphere
|
||||
from .geometric_objects import Line as Line
|
||||
from .geometric_objects import MultipleLines as MultipleLines
|
||||
from .geometric_objects import Octahedron as Octahedron
|
||||
from .geometric_objects import Plane as Plane
|
||||
from .geometric_objects import PlatonicSolid as PlatonicSolid
|
||||
from .geometric_objects import Polygon as Polygon
|
||||
from .geometric_objects import Pyramid as Pyramid
|
||||
from .geometric_objects import Quadrilateral as Quadrilateral
|
||||
from .geometric_objects import Rectangle as Rectangle
|
||||
from .geometric_objects import SolidSphere as SolidSphere
|
||||
from .geometric_objects import SolidSphereGeneric as SolidSphereGeneric
|
||||
from .geometric_objects import Sphere as Sphere
|
||||
from .geometric_objects import Superquadric as Superquadric
|
||||
from .geometric_objects import Tetrahedron as Tetrahedron
|
||||
from .geometric_objects import Text3D as Text3D
|
||||
from .geometric_objects import Triangle as Triangle
|
||||
from .geometric_objects import Tube as Tube
|
||||
from .geometric_objects import Wavelet as Wavelet
|
||||
from .geometric_sources import ArrowSource as ArrowSource
|
||||
from .geometric_sources import AxesGeometrySource as AxesGeometrySource
|
||||
from .geometric_sources import BoxSource as BoxSource
|
||||
from .geometric_sources import ConeSource as ConeSource
|
||||
from .geometric_sources import CubeFacesSource as CubeFacesSource
|
||||
from .geometric_sources import CubeSource as CubeSource
|
||||
from .geometric_sources import CylinderSource as CylinderSource
|
||||
from .geometric_sources import DiscSource as DiscSource
|
||||
from .geometric_sources import LineSource as LineSource
|
||||
from .geometric_sources import MultipleLinesSource as MultipleLinesSource
|
||||
from .geometric_sources import OrthogonalPlanesSource as OrthogonalPlanesSource
|
||||
from .geometric_sources import PlaneSource as PlaneSource
|
||||
from .geometric_sources import PlatonicSolidSource as PlatonicSolidSource
|
||||
from .geometric_sources import PolygonSource as PolygonSource
|
||||
from .geometric_sources import SphereSource as SphereSource
|
||||
from .geometric_sources import SuperquadricSource as SuperquadricSource
|
||||
from .geometric_sources import Text3DSource as Text3DSource
|
||||
from .geometric_sources import translate as translate
|
||||
from .image_sources import ImageEllipsoidSource as ImageEllipsoidSource
|
||||
from .image_sources import ImageGaussianSource as ImageGaussianSource
|
||||
from .image_sources import ImageGridSource as ImageGridSource
|
||||
from .image_sources import ImageMandelbrotSource as ImageMandelbrotSource
|
||||
from .image_sources import ImageNoiseSource as ImageNoiseSource
|
||||
from .image_sources import ImageSinusoidSource as ImageSinusoidSource
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
from .geometric_sources import CapsuleSource as CapsuleSource
|
||||
|
||||
from .cell_quality import cell_quality_info as cell_quality_info
|
||||
from .helpers import axis_rotation as axis_rotation
|
||||
from .helpers import generate_plane as generate_plane
|
||||
from .helpers import is_inside_bounds as is_inside_bounds
|
||||
from .helpers import is_pyvista_dataset as is_pyvista_dataset
|
||||
from .helpers import wrap as wrap
|
||||
from .misc import AnnotatedIntEnum as AnnotatedIntEnum
|
||||
from .misc import abstract_class as abstract_class
|
||||
from .misc import assert_empty_kwargs as assert_empty_kwargs
|
||||
from .misc import check_valid_vector as check_valid_vector
|
||||
from .misc import conditional_decorator as conditional_decorator
|
||||
from .misc import has_module as has_module
|
||||
from .misc import set_new_attribute as set_new_attribute
|
||||
from .misc import threaded as threaded
|
||||
from .misc import try_callback as try_callback
|
||||
from .observers import Observer as Observer
|
||||
from .observers import ProgressMonitor as ProgressMonitor
|
||||
from .observers import VtkErrorCatcher as VtkErrorCatcher
|
||||
from .observers import send_errors_to_logging as send_errors_to_logging
|
||||
from .observers import set_error_output_file as set_error_output_file
|
||||
from .parametric_objects import KochanekSpline as KochanekSpline
|
||||
from .parametric_objects import ParametricBohemianDome as ParametricBohemianDome
|
||||
from .parametric_objects import ParametricBour as ParametricBour
|
||||
from .parametric_objects import ParametricBoy as ParametricBoy
|
||||
from .parametric_objects import ParametricCatalanMinimal as ParametricCatalanMinimal
|
||||
from .parametric_objects import ParametricConicSpiral as ParametricConicSpiral
|
||||
from .parametric_objects import ParametricCrossCap as ParametricCrossCap
|
||||
from .parametric_objects import ParametricDini as ParametricDini
|
||||
from .parametric_objects import ParametricEllipsoid as ParametricEllipsoid
|
||||
from .parametric_objects import ParametricEnneper as ParametricEnneper
|
||||
from .parametric_objects import ParametricFigure8Klein as ParametricFigure8Klein
|
||||
from .parametric_objects import ParametricHenneberg as ParametricHenneberg
|
||||
from .parametric_objects import ParametricKlein as ParametricKlein
|
||||
from .parametric_objects import ParametricKuen as ParametricKuen
|
||||
from .parametric_objects import ParametricMobius as ParametricMobius
|
||||
from .parametric_objects import ParametricPluckerConoid as ParametricPluckerConoid
|
||||
from .parametric_objects import ParametricPseudosphere as ParametricPseudosphere
|
||||
from .parametric_objects import ParametricRandomHills as ParametricRandomHills
|
||||
from .parametric_objects import ParametricRoman as ParametricRoman
|
||||
from .parametric_objects import ParametricSuperEllipsoid as ParametricSuperEllipsoid
|
||||
from .parametric_objects import ParametricSuperToroid as ParametricSuperToroid
|
||||
from .parametric_objects import ParametricTorus as ParametricTorus
|
||||
from .parametric_objects import Spline as Spline
|
||||
from .parametric_objects import parametric_keywords as parametric_keywords
|
||||
from .parametric_objects import surface_from_para as surface_from_para
|
||||
from .points import fit_line_to_points as fit_line_to_points
|
||||
from .points import fit_plane_to_points as fit_plane_to_points
|
||||
from .points import line_segments_from_points as line_segments_from_points
|
||||
from .points import lines_from_points as lines_from_points
|
||||
from .points import make_tri_mesh as make_tri_mesh
|
||||
from .points import principal_axes as principal_axes
|
||||
from .points import vector_poly_data as vector_poly_data
|
||||
from .points import vtk_points as vtk_points
|
||||
from .reader import AVSucdReader as AVSucdReader
|
||||
from .reader import BaseReader as BaseReader
|
||||
from .reader import BinaryMarchingCubesReader as BinaryMarchingCubesReader
|
||||
from .reader import BMPReader as BMPReader
|
||||
from .reader import BYUReader as BYUReader
|
||||
from .reader import CGNSReader as CGNSReader
|
||||
from .reader import DEMReader as DEMReader
|
||||
from .reader import DICOMReader as DICOMReader
|
||||
from .reader import EnSightReader as EnSightReader
|
||||
from .reader import ExodusIIBlockSet as ExodusIIBlockSet
|
||||
from .reader import ExodusIIReader as ExodusIIReader
|
||||
from .reader import FacetReader as FacetReader
|
||||
from .reader import FLUENTCFFReader as FLUENTCFFReader
|
||||
from .reader import FluentReader as FluentReader
|
||||
from .reader import GambitReader as GambitReader
|
||||
from .reader import GaussianCubeReader as GaussianCubeReader
|
||||
from .reader import GESignaReader as GESignaReader
|
||||
from .reader import GIFReader as GIFReader
|
||||
from .reader import GLTFReader as GLTFReader
|
||||
from .reader import HDFReader as HDFReader
|
||||
from .reader import HDRReader as HDRReader
|
||||
from .reader import JPEGReader as JPEGReader
|
||||
from .reader import MetaImageReader as MetaImageReader
|
||||
from .reader import MFIXReader as MFIXReader
|
||||
from .reader import MINCImageReader as MINCImageReader
|
||||
from .reader import MultiBlockPlot3DReader as MultiBlockPlot3DReader
|
||||
from .reader import Nek5000Reader as Nek5000Reader
|
||||
from .reader import NIFTIReader as NIFTIReader
|
||||
from .reader import NRRDReader as NRRDReader
|
||||
from .reader import OBJReader as OBJReader
|
||||
from .reader import OpenFOAMReader as OpenFOAMReader
|
||||
from .reader import ParticleReader as ParticleReader
|
||||
from .reader import PDBReader as PDBReader
|
||||
from .reader import Plot3DFunctionEnum as Plot3DFunctionEnum
|
||||
from .reader import Plot3DMetaReader as Plot3DMetaReader
|
||||
from .reader import PLYReader as PLYReader
|
||||
from .reader import PNGReader as PNGReader
|
||||
from .reader import PNMReader as PNMReader
|
||||
from .reader import PointCellDataSelection as PointCellDataSelection
|
||||
from .reader import POpenFOAMReader as POpenFOAMReader
|
||||
from .reader import ProStarReader as ProStarReader
|
||||
from .reader import PTSReader as PTSReader
|
||||
from .reader import PVDDataSet as PVDDataSet
|
||||
from .reader import PVDReader as PVDReader
|
||||
from .reader import SegYReader as SegYReader
|
||||
from .reader import SLCReader as SLCReader
|
||||
from .reader import STLReader as STLReader
|
||||
from .reader import TecplotReader as TecplotReader
|
||||
from .reader import TIFFReader as TIFFReader
|
||||
from .reader import TimeReader as TimeReader
|
||||
from .reader import VTKDataSetReader as VTKDataSetReader
|
||||
from .reader import VTKPDataSetReader as VTKPDataSetReader
|
||||
from .reader import XdmfReader as XdmfReader
|
||||
from .reader import XMLImageDataReader as XMLImageDataReader
|
||||
from .reader import XMLMultiBlockDataReader as XMLMultiBlockDataReader
|
||||
from .reader import XMLPartitionedDataSetReader as XMLPartitionedDataSetReader
|
||||
from .reader import XMLPImageDataReader as XMLPImageDataReader
|
||||
from .reader import XMLPolyDataReader as XMLPolyDataReader
|
||||
from .reader import XMLPRectilinearGridReader as XMLPRectilinearGridReader
|
||||
from .reader import XMLPUnstructuredGridReader as XMLPUnstructuredGridReader
|
||||
from .reader import XMLRectilinearGridReader as XMLRectilinearGridReader
|
||||
from .reader import XMLStructuredGridReader as XMLStructuredGridReader
|
||||
from .reader import XMLUnstructuredGridReader as XMLUnstructuredGridReader
|
||||
from .reader import get_reader as get_reader
|
||||
from .state_manager import vtk_snake_case as vtk_snake_case
|
||||
from .state_manager import vtk_verbosity as vtk_verbosity
|
||||
from .transform import Transform as Transform
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,334 @@
|
||||
"""Information about cell quality measures."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
from typing import NoReturn
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyvista.core.celltype import _CELL_TYPE_INFO
|
||||
from pyvista.core.celltype import CellType
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
_CellQualityLiteral = Literal[
|
||||
'area',
|
||||
'aspect_frobenius',
|
||||
'aspect_gamma',
|
||||
'aspect_ratio',
|
||||
'collapse_ratio',
|
||||
'condition',
|
||||
'diagonal',
|
||||
'dimension',
|
||||
'distortion',
|
||||
'jacobian',
|
||||
'max_angle',
|
||||
'max_aspect_frobenius',
|
||||
'max_edge_ratio',
|
||||
'med_aspect_frobenius',
|
||||
'min_angle',
|
||||
'oddy',
|
||||
'radius_ratio',
|
||||
'relative_size_squared',
|
||||
'scaled_jacobian',
|
||||
'shape',
|
||||
'shape_and_size',
|
||||
'shear',
|
||||
'shear_and_size',
|
||||
'skew',
|
||||
'stretch',
|
||||
'taper',
|
||||
'volume',
|
||||
'warpage',
|
||||
]
|
||||
|
||||
_CellTypesLiteral = Literal[
|
||||
CellType.TRIANGLE,
|
||||
CellType.QUAD,
|
||||
CellType.TETRA,
|
||||
CellType.HEXAHEDRON,
|
||||
CellType.PYRAMID,
|
||||
CellType.WEDGE,
|
||||
]
|
||||
_CellTypeNamesLiteral = Literal[
|
||||
'TRIANGLE',
|
||||
'triangle',
|
||||
'QUAD',
|
||||
'quad',
|
||||
'TETRA',
|
||||
'tetra',
|
||||
'HEXAHEDRON',
|
||||
'hexahedron',
|
||||
'PYRAMID',
|
||||
'pyramid',
|
||||
'WEDGE',
|
||||
'wedge',
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CellQualityInfo(_NoNewAttrMixin):
|
||||
"""Information about a cell's quality measure."""
|
||||
|
||||
cell_type: _CellTypesLiteral
|
||||
quality_measure: _CellQualityLiteral
|
||||
acceptable_range: tuple[float, float]
|
||||
normal_range: tuple[float, float]
|
||||
full_range: tuple[float, float]
|
||||
unit_cell_value: float
|
||||
|
||||
|
||||
def sqrt(num: float) -> float: # noqa: D103
|
||||
return num**0.5
|
||||
|
||||
|
||||
# Define aliases to help definitions fit on one line
|
||||
INF = float('inf')
|
||||
ANGLE = float((180 / np.pi) * np.arccos(1 / 3))
|
||||
R22 = sqrt(2) / 2
|
||||
R33 = sqrt(3) / 3
|
||||
|
||||
TRIANGLE: Literal[CellType.TRIANGLE] = CellType.TRIANGLE
|
||||
QUAD: Literal[CellType.QUAD] = CellType.QUAD
|
||||
TETRA: Literal[CellType.TETRA] = CellType.TETRA
|
||||
HEXAHEDRON: Literal[CellType.HEXAHEDRON] = CellType.HEXAHEDRON
|
||||
PYRAMID: Literal[CellType.PYRAMID] = CellType.PYRAMID
|
||||
WEDGE: Literal[CellType.WEDGE] = CellType.WEDGE
|
||||
|
||||
Info = CellQualityInfo
|
||||
|
||||
_CELL_QUALITY_INFO = [
|
||||
Info(TRIANGLE, 'area', (0.0, INF), (0.0, INF), (0.0, INF), sqrt(3.0) / 4.0),
|
||||
Info(TRIANGLE, 'aspect_ratio', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TRIANGLE, 'aspect_frobenius', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TRIANGLE, 'condition', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TRIANGLE, 'distortion', (0.5, 1.0), (0.0, 1.0), (-INF, INF), 1.0),
|
||||
Info(TRIANGLE, 'max_angle', (60.0, 90.0), (60.0, 180.0), (0.0, 180.0), 60.0),
|
||||
Info(TRIANGLE, 'min_angle', (30.0, 60.0), (0.0, 60.0), (0.0, 360.0), 60.0),
|
||||
Info(TRIANGLE, 'scaled_jacobian', (0.5, 2 * R33), (-2 * R33, 2 * R33), (-INF, INF), 1.0),
|
||||
Info(TRIANGLE, 'radius_ratio', (1.0, 3.0), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TRIANGLE, 'shape', (0.25, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(TRIANGLE, 'shape_and_size', (0.25, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(QUAD, 'area', (0.0, INF), (0.0, INF), (-INF, INF), 1.0),
|
||||
Info(QUAD, 'aspect_ratio', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(QUAD, 'condition', (1.0, 4), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(QUAD, 'distortion', (0.5, 1.0), (0.0, 1.0), (-INF, INF), 1.0),
|
||||
Info(QUAD, 'jacobian', (0.0, INF), (0.0, INF), (-INF, INF), 1.0),
|
||||
Info(QUAD, 'max_aspect_frobenius', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(QUAD, 'max_angle', (90.0, 135.0), (90.0, 360.0), (0.0, 360.0), 90.0),
|
||||
Info(QUAD, 'max_edge_ratio', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(QUAD, 'med_aspect_frobenius', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(QUAD, 'min_angle', (45.0, 90.0), (0.0, 90.0), (0.0, 360.0), 90.0),
|
||||
Info(QUAD, 'oddy', (0.0, 0.5), (0.0, INF), (0.0, INF), 0.0),
|
||||
Info(QUAD, 'radius_ratio', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(QUAD, 'relative_size_squared', (0.3, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(QUAD, 'scaled_jacobian', (0.3, 1.0), (-1.0, 1.0), (-1.0, 1.0), 1.0),
|
||||
Info(QUAD, 'shape', (0.3, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(QUAD, 'shape_and_size', (0.2, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(QUAD, 'shear', (0.3, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(QUAD, 'shear_and_size', (0.2, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(QUAD, 'skew', (0.0, 0.5), (0.0, 1.0), (0.0, 1.0), 0.0),
|
||||
Info(QUAD, 'stretch', (0.25, 1.0), (0.0, 1.0), (0.0, INF), 1.0),
|
||||
Info(QUAD, 'taper', (0.0, 0.7), (0.0, INF), (0.0, INF), 0.0),
|
||||
Info(QUAD, 'warpage', (0.3, 1.0), (-1.0, 1.0), (-INF, INF), 1.0),
|
||||
Info(TETRA, 'aspect_frobenius', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TETRA, 'aspect_gamma', (1.0, 3.0), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TETRA, 'aspect_ratio', (1.0, 3.0), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TETRA, 'collapse_ratio', (0.1, INF), (0.0, INF), (0.0, INF), sqrt(6.0) / 3.0),
|
||||
Info(TETRA, 'condition', (1.0, 3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TETRA, 'distortion', (0.5, 1.0), (0.0, 1.0), (-INF, INF), 1.0),
|
||||
Info(TETRA, 'jacobian', (0.0, INF), (0.0, INF), (-INF, INF), R22),
|
||||
Info(TETRA, 'min_angle', (40, ANGLE), (0.0, ANGLE), (0.0, 360), ANGLE),
|
||||
Info(TETRA, 'radius_ratio', (1.0, 3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(TETRA, 'relative_size_squared', (0.3, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(TETRA, 'scaled_jacobian', (0.5, 1.0), (-1.0, 1.0), (-INF, INF), 1.0),
|
||||
Info(TETRA, 'shape', (0.3, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(TETRA, 'shape_and_size', (0.2, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(TETRA, 'volume', (0.0, INF), (-INF, INF), (-INF, INF), sqrt(2.0) / 12.0),
|
||||
Info(HEXAHEDRON, 'diagonal', (0.65, 1.0), (0.0, 1.0), (0.0, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'dimension', (0.0, INF), (0.0, INF), (0.0, INF), R33),
|
||||
Info(HEXAHEDRON, 'distortion', (0.5, 1.0), (0.0, 1.0), (-INF, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'jacobian', (0.0, INF), (0.0, INF), (-INF, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'max_edge_ratio', (1.0, 1.3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'max_aspect_frobenius', (1.0, 3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'med_aspect_frobenius', (1.0, 3), (1.0, INF), (1.0, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'oddy', (0.0, 0.5), (0.0, INF), (0.0, INF), 0.0),
|
||||
Info(HEXAHEDRON, 'relative_size_squared', (0.5, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(HEXAHEDRON, 'scaled_jacobian', (0.5, 1.0), (-1.0, 1.0), (-1.0, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'shape', (0.3, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(HEXAHEDRON, 'shape_and_size', (0.2, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(HEXAHEDRON, 'shear', (0.3, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(HEXAHEDRON, 'shear_and_size', (0.2, 1.0), (0.0, 1.0), (0.0, 1.0), 1.0),
|
||||
Info(HEXAHEDRON, 'skew', (0.0, 0.5), (0.0, 1.0), (0.0, INF), 0.0),
|
||||
Info(HEXAHEDRON, 'stretch', (0.25, 1.0), (0.0, 1.0), (0.0, INF), 1.0),
|
||||
Info(HEXAHEDRON, 'taper', (0.0, 0.5), (0.0, INF), (0.0, INF), 0.0),
|
||||
Info(HEXAHEDRON, 'volume', (0.0, INF), (0.0, INF), (-INF, INF), 1.0),
|
||||
Info(PYRAMID, 'volume', (0.0, INF), (-INF, INF), (-INF, INF), sqrt(2.0) / 6.0),
|
||||
Info(WEDGE, 'volume', (0.0, INF), (-INF, INF), (-INF, INF), sqrt(3.0) / 4.0),
|
||||
]
|
||||
|
||||
# Create lookup dict
|
||||
_CELL_QUALITY_LOOKUP: dict[CellType, dict[_CellQualityLiteral, CellQualityInfo]] = {}
|
||||
for info in _CELL_QUALITY_INFO:
|
||||
_CELL_QUALITY_LOOKUP.setdefault(info.cell_type, {})
|
||||
_CELL_QUALITY_LOOKUP[info.cell_type][info.quality_measure] = info
|
||||
|
||||
|
||||
_CELL_TYPE_NAMES = [typ.name for typ in _CELL_QUALITY_LOOKUP.keys()]
|
||||
|
||||
|
||||
def cell_quality_info(
|
||||
cell_type: _CellTypesLiteral | _CellTypeNamesLiteral,
|
||||
quality_measure: _CellQualityLiteral,
|
||||
) -> CellQualityInfo:
|
||||
"""Return information about a cell's quality measure.
|
||||
|
||||
This function returns information about a quality measure computed by
|
||||
:meth:`~pyvista.DataObjectFilters.cell_quality` for a specified
|
||||
:class:`~pyvista.CellType`. The following is provided for each measure:
|
||||
|
||||
- ``acceptable_range``: Well-behaved cells have values in this range.
|
||||
- ``normal_range``: All cells except those with degeneracies have values in this range.
|
||||
- ``full_range``: All cells including degenerate ones have values in this range.
|
||||
- ``unit_cell_value``: The quality measure value for a reference unit cell (e.g.
|
||||
equilateral triangle with edge length of one for triangles).
|
||||
|
||||
This information can help inform if a particular cell is of high or low quality.
|
||||
|
||||
See the tables below for a summary of all cell quality info available from this
|
||||
function.
|
||||
|
||||
.. include:: /api/core/cell_quality/cell_quality_info_table_TRIANGLE.rst
|
||||
|
||||
.. include:: /api/core/cell_quality/cell_quality_info_table_QUAD.rst
|
||||
|
||||
.. include:: /api/core/cell_quality/cell_quality_info_table_HEXAHEDRON.rst
|
||||
|
||||
.. include:: /api/core/cell_quality/cell_quality_info_table_TETRA.rst
|
||||
|
||||
.. include:: /api/core/cell_quality/cell_quality_info_table_WEDGE.rst
|
||||
|
||||
.. include:: /api/core/cell_quality/cell_quality_info_table_PYRAMID.rst
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The information returned by this function is based on the
|
||||
`Verdict Library Reference Manual <https://github.com/sandialabs/verdict/raw/master/SAND2007-2853p.pdf>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
Information is not available for all valid quality measures computed by
|
||||
:meth:`~pyvista.DataObjectFilters.cell_quality`. Only a subset
|
||||
is provided here. If information about a measure is missing and you have
|
||||
knowledge about its acceptable range, normal range, etc., please consider
|
||||
submitting a pull request on GitHub at https://github.com/pyvista/pyvista.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cell_type : CellType | str
|
||||
Cell type to get information about. May be a :class:`~pyvista.CellType` or the
|
||||
name of a cell type as a string.
|
||||
|
||||
quality_measure : str
|
||||
Quality measure to get information about. May be any quality measure from
|
||||
:ref:`cell_quality_measures_table`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
CellQualityInfo
|
||||
Dataclass with information about the quality measure for a specific cell type.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If info is not available for the specified cell type or measure.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:meth:`~pyvista.DataObjectFilters.cell_quality`
|
||||
|
||||
Examples
|
||||
--------
|
||||
Get cell quality info for :attr:`~pyvista.CellType.TRIANGLE` cells and the
|
||||
``'scaled_jacobian'`` quality measure.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> info_tri = pv.cell_quality_info(pv.CellType.TRIANGLE, 'scaled_jacobian')
|
||||
>>> info_tri # doctest: +NORMALIZE_WHITESPACE
|
||||
CellQualityInfo(cell_type=<CellType.TRIANGLE: 5>,
|
||||
quality_measure='scaled_jacobian',
|
||||
acceptable_range=(0.5, 1.1547005383792515),
|
||||
normal_range=(-1.1547005383792515, 1.1547005383792515),
|
||||
full_range=(-inf, inf),
|
||||
unit_cell_value=1.0)
|
||||
|
||||
Show the acceptable range for this measure.
|
||||
|
||||
>>> info_tri.acceptable_range
|
||||
(0.5, 1.1547005383792515)
|
||||
|
||||
Show the value of this measure for equilateral triangles with edge length of one.
|
||||
|
||||
>>> info_tri.unit_cell_value
|
||||
1.0
|
||||
|
||||
Get info for the same measure but for :attr:`~pyvista.CellType.QUAD` cells.
|
||||
|
||||
>>> info_quad = pv.cell_quality_info(pv.CellType.QUAD, 'scaled_jacobian')
|
||||
>>> info_quad # doctest: +NORMALIZE_WHITESPACE
|
||||
CellQualityInfo(cell_type=<CellType.QUAD: 9>,
|
||||
quality_measure='scaled_jacobian',
|
||||
acceptable_range=(0.3, 1.0),
|
||||
normal_range=(-1.0, 1.0),
|
||||
full_range=(-1.0, 1.0),
|
||||
unit_cell_value=1.0)
|
||||
|
||||
Show the acceptable range. Note that it differs for quads compared to triangles.
|
||||
|
||||
>>> info_quad.acceptable_range
|
||||
(0.3, 1.0)
|
||||
|
||||
Show the value of this measure for a square cell with edge length of one.
|
||||
|
||||
>>> info_quad.unit_cell_value
|
||||
1.0
|
||||
|
||||
See :ref:`mesh_quality_example` for more examples using this function.
|
||||
|
||||
"""
|
||||
|
||||
def raise_error(item_: str, valid_options_: Sequence[str]) -> NoReturn:
|
||||
msg = (
|
||||
f'Cell quality info is not available for {item_}. Valid options are:\n{valid_options_}'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
if isinstance(cell_type, str):
|
||||
upper = cell_type.upper()
|
||||
if upper not in _CELL_TYPE_NAMES:
|
||||
item = f'cell type {upper!r}'
|
||||
raise_error(item, _CELL_TYPE_NAMES)
|
||||
value = CellType(_CELL_TYPE_INFO[upper].value)
|
||||
else:
|
||||
value = CellType(cell_type)
|
||||
|
||||
# Lookup measures available for the cell type
|
||||
try:
|
||||
measures = _CELL_QUALITY_LOOKUP[value]
|
||||
except KeyError:
|
||||
item = f'cell type {value.name!r}'
|
||||
raise_error(item, _CELL_TYPE_NAMES)
|
||||
|
||||
# Lookup the measure info
|
||||
try:
|
||||
return measures[quality_measure]
|
||||
except KeyError:
|
||||
item = f'{value.name!r} measure {quality_measure!r}'
|
||||
valid_options = list(measures.keys())
|
||||
raise_error(item, valid_options)
|
||||
@@ -0,0 +1,312 @@
|
||||
"""PyVista wrapping of :vtk:`vtkCellArray`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import deque
|
||||
from itertools import count
|
||||
from itertools import islice
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
from typing import overload
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.celltype import _CELL_TYPE_TO_NUM_POINTS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista import UnstructuredGrid
|
||||
from pyvista.core._typing_core import ArrayLike
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
|
||||
|
||||
def ncells_from_cells(cells: NumpyArray[int]) -> int:
|
||||
"""Get the number of cells from a VTK cell connectivity array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cells : numpy.ndarray
|
||||
A VTK cell connectivity array.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of cells extracted from the given cell connectivity array.
|
||||
|
||||
"""
|
||||
consumer: deque[NumpyArray[int]] = deque(maxlen=0)
|
||||
it = cells.flat
|
||||
for n_cells in count(): # noqa: B007
|
||||
skip = next(it, None)
|
||||
if skip is None:
|
||||
break
|
||||
consumer.extend(islice(it, skip)) # type: ignore[arg-type]
|
||||
return n_cells
|
||||
|
||||
|
||||
@overload
|
||||
def numpy_to_idarr(
|
||||
ind: int | ArrayLike[int],
|
||||
deep: bool = ..., # noqa: FBT001
|
||||
return_ind: Literal[True] = True, # noqa: FBT002
|
||||
) -> _vtk.vtkIdTypeArray: ...
|
||||
@overload
|
||||
def numpy_to_idarr(
|
||||
ind: int | ArrayLike[int],
|
||||
deep: bool = ..., # noqa: FBT001
|
||||
return_ind: Literal[False] = False, # noqa: FBT002
|
||||
) -> tuple[_vtk.vtkIdTypeArray, NumpyArray[int]]: ...
|
||||
@overload
|
||||
def numpy_to_idarr(
|
||||
ind: int | ArrayLike[int],
|
||||
deep: bool = ..., # noqa: FBT001
|
||||
return_ind: bool = ..., # noqa: FBT001
|
||||
) -> tuple[_vtk.vtkIdTypeArray, NumpyArray[int]] | _vtk.vtkIdTypeArray: ...
|
||||
@_deprecate_positional_args(allowed=['ind'])
|
||||
def numpy_to_idarr(
|
||||
ind: int | ArrayLike[int],
|
||||
deep: bool = False, # noqa: FBT001, FBT002
|
||||
return_ind: bool = False, # noqa: FBT001, FBT002
|
||||
) -> tuple[_vtk.vtkIdTypeArray, NumpyArray[int]] | _vtk.vtkIdTypeArray:
|
||||
"""Safely convert a numpy array to a :vtk:`vtkIdTypeArray`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ind : sequence[int]
|
||||
Input sequence to be converted to a :vtk:`vtkIdTypeArray`. Can be either a mask
|
||||
or an integer array-like.
|
||||
deep : bool, default: False
|
||||
If ``True``, deep copy the input data. If ``False``, do not deep copy
|
||||
the input data.
|
||||
return_ind : bool, default: False
|
||||
If ``True``, also return the input array after it has been cast to the
|
||||
proper dtype.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:vtk:`vtkIdTypeArray`
|
||||
Converted array as a :vtk:`vtkIdTypeArray`.
|
||||
numpy.ndarray
|
||||
The input array after it has been cast to the proper dtype. Only
|
||||
returned if `return_ind` is set to ``True``.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If the input array is not a mask or an integer array-like.
|
||||
|
||||
"""
|
||||
ind = np.asarray(ind)
|
||||
|
||||
# np.asarray will eat anything, so we have to weed out bogus inputs
|
||||
if not issubclass(ind.dtype.type, (np.bool_, np.integer)):
|
||||
msg = 'Indices must be either a mask or an integer array-like'
|
||||
raise TypeError(msg)
|
||||
|
||||
if ind.dtype == np.bool_:
|
||||
ind = ind.nonzero()[0].astype(pyvista.ID_TYPE)
|
||||
elif ind.dtype != pyvista.ID_TYPE:
|
||||
ind = ind.astype(pyvista.ID_TYPE)
|
||||
elif not ind.flags['C_CONTIGUOUS']:
|
||||
ind = np.ascontiguousarray(ind, dtype=pyvista.ID_TYPE)
|
||||
|
||||
# must ravel or segfault when saving MultiBlock
|
||||
vtk_idarr = _vtk.numpy_to_vtkIdTypeArray(ind.ravel(), deep=deep)
|
||||
if return_ind:
|
||||
return vtk_idarr, ind
|
||||
return vtk_idarr
|
||||
|
||||
|
||||
def create_mixed_cells(
|
||||
mixed_cell_dict: dict[np.uint8, NumpyArray[int]], nr_points: int | None = None
|
||||
) -> tuple[NumpyArray[np.uint8], NumpyArray[int]]:
|
||||
"""Generate cell arrays for the creation of a pyvista.UnstructuredGrid from a cell dictionary.
|
||||
|
||||
This function generates all required cell arrays according to a given cell
|
||||
dictionary. The given cell-dictionary should contain a proper
|
||||
mapping of vtk_type -> np.ndarray (int), where the given ndarray
|
||||
for each cell-type has to be an array of dimensions [N, D] or
|
||||
[N*D], where N is the number of cells and D is the size of the
|
||||
cells for the given type (e.g. 3 for triangles). Multiple
|
||||
vtk_type keys with associated arrays can be present in one
|
||||
dictionary. This function only accepts cell types of fixed size
|
||||
and not dynamic sized cells like :attr:`~pyvista.CellType.POLYGON`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mixed_cell_dict : dict
|
||||
A dictionary that maps VTK-Enum-types (e.g. :attr:`~pyvista.CellType.TRIANGLE`) to
|
||||
np.ndarrays of type int. The ``np.ndarrays`` describe the cell
|
||||
connectivity.
|
||||
nr_points : int, optional
|
||||
Number of points of the grid. Used only to allow additional runtime
|
||||
checks for invalid indices.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cell_types : numpy.ndarray (uint8)
|
||||
Types of each cell.
|
||||
|
||||
cell_arr : numpy.ndarray (int)
|
||||
VTK-cell array.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If any of the cell types are not supported, have dynamic sized
|
||||
cells, map to values with wrong size, or cell indices point
|
||||
outside the given number of points.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create the cell arrays containing two triangles.
|
||||
|
||||
This will generate cell arrays to generate a mesh with two
|
||||
disconnected triangles from 6 points.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import vtk
|
||||
>>> from pyvista.core.utilities.cells import create_mixed_cells
|
||||
>>> cell_types, cell_arr = create_mixed_cells(
|
||||
... {vtk.VTK_TRIANGLE: np.array([[0, 1, 2], [3, 4, 5]])}
|
||||
... )
|
||||
|
||||
"""
|
||||
if not np.all([k in _CELL_TYPE_TO_NUM_POINTS for k in mixed_cell_dict.keys()]):
|
||||
msg = 'Found unknown or unsupported VTK cell type in your requested cells'
|
||||
raise ValueError(msg)
|
||||
|
||||
if not np.all([_CELL_TYPE_TO_NUM_POINTS[k] > 0 for k in mixed_cell_dict.keys()]):
|
||||
msg = "You requested a cell type with variable length, which can't be used in this method"
|
||||
raise ValueError(msg)
|
||||
|
||||
final_cell_types = []
|
||||
final_cell_arr = []
|
||||
for elem_t, cells_arr in mixed_cell_dict.items():
|
||||
nr_points_per_elem = _CELL_TYPE_TO_NUM_POINTS[elem_t]
|
||||
if (
|
||||
not isinstance(cells_arr, np.ndarray) # type: ignore[redundant-expr]
|
||||
or not np.issubdtype(cells_arr.dtype, np.integer)
|
||||
or cells_arr.ndim not in [1, 2]
|
||||
or (cells_arr.ndim == 1 and cells_arr.size % nr_points_per_elem != 0)
|
||||
or (cells_arr.ndim == 2 and cells_arr.shape[-1] != nr_points_per_elem)
|
||||
):
|
||||
msg = (
|
||||
f'Expected an np.ndarray of size [N, {nr_points_per_elem}] or '
|
||||
f'[N*{nr_points_per_elem}] with an integral type'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
if np.any(cells_arr < 0):
|
||||
msg = f'Non-valid index (<0) given for cells of type {elem_t}'
|
||||
raise ValueError(msg)
|
||||
|
||||
if nr_points is not None and np.any(cells_arr >= nr_points):
|
||||
msg = f'Non-valid index (>={nr_points}) given for cells of type {elem_t}'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Ensure array is not flat
|
||||
cells_arr_not_flat = (
|
||||
cells_arr.reshape([-1, nr_points_per_elem]) if cells_arr.ndim == 1 else cells_arr
|
||||
)
|
||||
|
||||
nr_elems = cells_arr_not_flat.shape[0]
|
||||
final_cell_types.append(np.array([elem_t] * nr_elems, dtype=np.uint8))
|
||||
final_cell_arr.append(
|
||||
np.concatenate(
|
||||
[
|
||||
np.ones_like(cells_arr_not_flat[..., :1]) * nr_points_per_elem,
|
||||
cells_arr_not_flat,
|
||||
],
|
||||
axis=-1,
|
||||
).reshape([-1]),
|
||||
)
|
||||
|
||||
cell_types_out = np.concatenate(final_cell_types)
|
||||
cell_arr_out = np.concatenate(final_cell_arr)
|
||||
|
||||
return cell_types_out, cell_arr_out
|
||||
|
||||
|
||||
def get_mixed_cells(vtkobj: UnstructuredGrid) -> dict[np.uint8, NumpyArray[int]]:
|
||||
"""Create the cells dictionary from the given pyvista.UnstructuredGrid.
|
||||
|
||||
This functions creates a cells dictionary (see
|
||||
create_mixed_cells), with a mapping vtk_type -> np.ndarray (int)
|
||||
for fixed size cell types. The returned dictionary will have
|
||||
arrays of size [N, D], where N is the number of cells and D is the
|
||||
size of the cells for the given type (e.g. 3 for triangles).
|
||||
|
||||
.. versionchanged:: 0.46
|
||||
|
||||
An empty dict ``{}`` is returned instead of ``None`` if the input
|
||||
is empty.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vtkobj : pyvista.UnstructuredGrid
|
||||
The unstructured grid for which the cells dictionary should be computed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Dictionary of cells.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If vtkobj is not a pyvista.UnstructuredGrid, any of the
|
||||
present cells are unsupported, or have dynamic cell sizes,
|
||||
like VTK_POLYGON.
|
||||
|
||||
"""
|
||||
return_dict: dict[np.uint8, NumpyArray[int]] = {}
|
||||
|
||||
if not isinstance(vtkobj, pyvista.UnstructuredGrid):
|
||||
msg = 'Expected a pyvista object' # type: ignore[unreachable]
|
||||
raise TypeError(msg)
|
||||
|
||||
nr_cells = vtkobj.n_cells
|
||||
if nr_cells == 0:
|
||||
return return_dict
|
||||
|
||||
cell_types = vtkobj.celltypes
|
||||
cells = vtkobj.cells
|
||||
|
||||
unique_cell_types = np.unique(cell_types)
|
||||
|
||||
if not np.all([k in _CELL_TYPE_TO_NUM_POINTS for k in unique_cell_types]):
|
||||
msg = 'Found unknown or unsupported VTK cell type in the present cells'
|
||||
raise ValueError(msg)
|
||||
|
||||
if not np.all([_CELL_TYPE_TO_NUM_POINTS[k] > 0 for k in unique_cell_types]):
|
||||
msg = (
|
||||
'You requested a cell-dictionary with a variable length cell, which is not supported '
|
||||
'currently'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
cell_sizes = np.zeros_like(cell_types)
|
||||
for cell_type in unique_cell_types:
|
||||
mask = cell_types == cell_type
|
||||
cell_sizes[mask] = _CELL_TYPE_TO_NUM_POINTS[cell_type]
|
||||
|
||||
cell_ends = np.cumsum(cell_sizes + 1)
|
||||
cell_starts = np.concatenate([np.array([0], dtype=cell_ends.dtype), cell_ends[:-1]]) + 1
|
||||
|
||||
for cell_type in unique_cell_types:
|
||||
cell_size = _CELL_TYPE_TO_NUM_POINTS[cell_type]
|
||||
mask = cell_types == cell_type
|
||||
current_cell_starts = cell_starts[mask]
|
||||
|
||||
cells_inds = current_cell_starts[..., np.newaxis] + np.arange(cell_size)[
|
||||
np.newaxis
|
||||
].astype(
|
||||
cell_starts.dtype,
|
||||
)
|
||||
|
||||
return_dict[cell_type] = cells[cells_inds]
|
||||
|
||||
return return_dict
|
||||
@@ -0,0 +1,153 @@
|
||||
"""Supporting functions for documentation build."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import os.path as op
|
||||
import sys
|
||||
|
||||
|
||||
def linkcode_resolve(domain: str, info: dict[str, str], edit: bool = False) -> str | None: # noqa: FBT001, FBT002
|
||||
"""Determine the URL corresponding to a Python object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
domain : str
|
||||
Only useful when 'py'.
|
||||
|
||||
info : dict
|
||||
With keys "module" and "fullname".
|
||||
|
||||
edit : bool, default=False
|
||||
Jump right to the edit page.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The code URL. Empty string if there is no valid link.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function is used by the `sphinx.ext.linkcode` extension to create the "[Source]"
|
||||
button whose link is edited in this function.
|
||||
|
||||
This has been adapted to deal with our "verbose" decorator.
|
||||
|
||||
Adapted from mne (mne/utils/docs.py), which was adapted from SciPy (doc/source/conf.py).
|
||||
|
||||
"""
|
||||
import pyvista # noqa: PLC0415
|
||||
|
||||
if domain != 'py':
|
||||
return None
|
||||
|
||||
modname = info['module']
|
||||
fullname = info['fullname']
|
||||
|
||||
# Little clean up to avoid pyvista.pyvista
|
||||
if fullname.startswith(modname):
|
||||
fullname = fullname[len(modname) + 1 :]
|
||||
|
||||
submod = sys.modules.get(modname)
|
||||
if submod is None:
|
||||
return None
|
||||
|
||||
obj = submod
|
||||
for part in fullname.split('.'):
|
||||
try:
|
||||
obj = getattr(obj, part)
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
|
||||
# deal with our decorators properly
|
||||
while hasattr(obj, 'fget'):
|
||||
obj = obj.fget
|
||||
|
||||
# deal with wrapped object
|
||||
while hasattr(obj, '__wrapped__'):
|
||||
obj = obj.__wrapped__
|
||||
try:
|
||||
fn = inspect.getsourcefile(obj)
|
||||
except Exception: # noqa: BLE001 # pragma: no cover
|
||||
fn = None
|
||||
|
||||
if not fn: # pragma: no cover
|
||||
try:
|
||||
fn = inspect.getsourcefile(sys.modules[obj.__module__])
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
return None
|
||||
|
||||
fn = op.relpath(fn, start=op.dirname(pyvista.__file__)) # noqa: PTH120
|
||||
fn = '/'.join(op.normpath(fn).split(os.sep)) # in case on Windows # noqa: PTH206
|
||||
|
||||
try:
|
||||
source, lineno = inspect.getsourcelines(obj)
|
||||
except Exception: # noqa: BLE001 # pragma: no cover
|
||||
lineno = None
|
||||
|
||||
linespec = f'#L{lineno}-L{lineno + len(source) - 1}' if lineno and not edit else ''
|
||||
|
||||
if 'dev' in pyvista.__version__:
|
||||
kind = 'main'
|
||||
else: # pragma: no cover
|
||||
kind = f'release/{".".join(pyvista.__version__.split(".")[:2])}'
|
||||
|
||||
blob_or_edit = 'edit' if edit else 'blob'
|
||||
|
||||
return f'http://github.com/pyvista/pyvista/{blob_or_edit}/{kind}/pyvista/{fn}{linespec}'
|
||||
|
||||
|
||||
def pv_html_page_context( # noqa: PLR0917
|
||||
app, # noqa: ARG001
|
||||
pagename: str,
|
||||
templatename: str, # noqa: ARG001
|
||||
context,
|
||||
doctree, # noqa: ARG001
|
||||
) -> None: # pragma: no cover
|
||||
"""Add a function for returning an "edit this page" link pointing to `main`.
|
||||
|
||||
This is specific to PyVista to ensure that the "edit this page" link always
|
||||
goes to the right page, specifically for:
|
||||
|
||||
- Gallery examples
|
||||
- Autosummary examples (using _autosummary)
|
||||
|
||||
"""
|
||||
|
||||
def fix_edit_link_button(link: str) -> str | None:
|
||||
"""Transform "edit on github" links to the correct url.
|
||||
|
||||
This is specific to PyVista to ensure that the "edit this page" link
|
||||
always goes to the right page, specifically for:
|
||||
|
||||
- Gallery examples
|
||||
- Autosummary examples (using _autosummary)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
link : str
|
||||
The link to the github edit interface.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The link to the tip of the main branch for the same file.
|
||||
|
||||
"""
|
||||
if pagename.startswith('examples') and 'index' not in pagename:
|
||||
# This is a gallery example.
|
||||
|
||||
# We can get away with directly using the pagename since "examples"
|
||||
# in the pagename is the same as the "examples" directory in the
|
||||
# repo
|
||||
return f'http://github.com/pyvista/pyvista/edit/main/{pagename}.py'
|
||||
elif '_autosummary' in pagename:
|
||||
# This is an API example
|
||||
fullname = pagename.split('_autosummary')[1][1:]
|
||||
return linkcode_resolve('py', {'module': 'pyvista', 'fullname': fullname}, edit=True)
|
||||
else:
|
||||
return link
|
||||
|
||||
context['fix_edit_link_button'] = fix_edit_link_button
|
||||
@@ -0,0 +1,940 @@
|
||||
"""Module containing geometry helper functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.errors import PyVistaDeprecationWarning
|
||||
from pyvista.core.utilities.helpers import wrap
|
||||
|
||||
|
||||
def _padded_bins(mesh, density):
|
||||
"""Construct bin edges for voxelization.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : pyvista.DataSet
|
||||
Mesh to voxelize.
|
||||
|
||||
density : array_like[float]
|
||||
A list of densities along x,y,z directions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[np.ndarray]
|
||||
List of bin edges for each axis.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Ensures limits of voxelization are padded to ensure the mesh is fully enclosed.
|
||||
|
||||
"""
|
||||
bounds = np.array(mesh.bounds).reshape(3, 2)
|
||||
bin_count = np.ceil(1e-10 + (bounds[:, 1] - bounds[:, 0]) / density)
|
||||
pad = (bin_count * density - (bounds[:, 1] - bounds[:, 0])) / 2
|
||||
|
||||
return [
|
||||
np.arange(bounds[i, 0] - pad[i], bounds[i, 1] + pad[i] + density[i] / 2, density[i])
|
||||
for i in range(3)
|
||||
]
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['mesh'])
|
||||
def voxelize( # noqa: PLR0917
|
||||
mesh,
|
||||
density=None,
|
||||
check_surface: bool = True, # noqa: FBT001, FBT002
|
||||
enclosed: bool = False, # noqa: FBT001, FBT002
|
||||
fit_bounds: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Voxelize mesh to UnstructuredGrid.
|
||||
|
||||
.. deprecated:: 0.46
|
||||
|
||||
This function is deprecated. Use :meth:`pyvista.DataSetFilters.voxelize` instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : pyvista.DataSet
|
||||
Mesh to voxelize.
|
||||
|
||||
density : float | array_like[float]
|
||||
The uniform size of the voxels when single float passed.
|
||||
A list of densities along x,y,z directions.
|
||||
Defaults to 1/100th of the mesh length.
|
||||
|
||||
check_surface : bool, default: True
|
||||
Specify whether to check the surface for closure. If on, then the
|
||||
algorithm first checks to see if the surface is closed and
|
||||
manifold. If the surface is not closed and manifold, a runtime
|
||||
error is raised.
|
||||
|
||||
enclosed : bool, default: False
|
||||
If True, the voxel bounds will be outside the mesh.
|
||||
If False, the voxel bounds will be at or inside the mesh bounds.
|
||||
|
||||
fit_bounds : bool, default: False
|
||||
If enabled, the end bound of the input mesh is used as the end bound of the
|
||||
voxel grid and the density is updated to the closest compatible one. Otherwise,
|
||||
the end bound is excluded. Has no effect if `enclosed` is enabled.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
Voxelized unstructured grid of the original mesh.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Prior to version 0.39.0, this method improperly handled the order of
|
||||
structured coordinates.
|
||||
|
||||
See Also
|
||||
--------
|
||||
pyvista.DataSetFilters.voxelize_rectilinear
|
||||
Similar function that returns a :class:`pyvista.RectilinearGrid` with cell data.
|
||||
|
||||
pyvista.DataSetFilters.voxelize_binary_mask
|
||||
Similar function that returns a :class:`pyvista.ImageData` with point data.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an equal density voxelized mesh.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.download_bunny_coarse().clean() # doctest:+SKIP
|
||||
>>> vox = pv.voxelize(mesh, density=0.01) # doctest:+SKIP
|
||||
>>> vox.plot(show_edges=True) # doctest:+SKIP
|
||||
|
||||
Create a voxelized mesh using unequal density dimensions.
|
||||
|
||||
>>> vox = pv.voxelize(mesh, density=[0.01, 0.005, 0.002]) # doctest:+SKIP
|
||||
>>> vox.plot(show_edges=True) # doctest:+SKIP
|
||||
|
||||
Create an equal density voxel volume without enclosing input mesh.
|
||||
|
||||
>>> vox = pv.voxelize(mesh, density=0.01) # doctest:+SKIP
|
||||
>>> vox = vox.select_enclosed_points(mesh, tolerance=0.0) # doctest:+SKIP
|
||||
>>> vox.plot(scalars='SelectedPoints', show_edges=True) # doctest:+SKIP
|
||||
|
||||
Create an equal density voxel volume enclosing input mesh.
|
||||
|
||||
>>> vox = pv.voxelize(mesh, density=0.01, enclosed=True) # doctest:+SKIP
|
||||
>>> vox = vox.select_enclosed_points(mesh, tolerance=0.0) # doctest:+SKIP
|
||||
>>> vox.plot(scalars='SelectedPoints', show_edges=True) # doctest:+SKIP
|
||||
|
||||
Create a voxelized mesh that does not fit the input mesh's bounds. Notice the
|
||||
cropped rectangular box.
|
||||
|
||||
>>> mesh = pv.Cube(x_length=0.25) # doctest:+SKIP
|
||||
>>> vox = pv.voxelize(mesh=mesh, density=0.2) # doctest:+SKIP
|
||||
>>> pl = pv.Plotter() # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(mesh=vox, show_edges=True, color='yellow') # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(
|
||||
... mesh=mesh, show_edges=True, line_width=5, opacity=0.4
|
||||
... ) # doctest:+SKIP
|
||||
>>> pl.show() # doctest:+SKIP
|
||||
|
||||
Create a voxelized mesh that fits the input mesh's bounds. The rectangular mesh is
|
||||
now complete. Notice that the voxel size was updated to fit the bounds in the first
|
||||
direction.
|
||||
|
||||
>>> vox = pv.voxelize(mesh=mesh, density=0.2, fit_bounds=True) # doctest:+SKIP
|
||||
>>> pl = pv.Plotter() # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(mesh=vox, show_edges=True, color='yellow') # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(
|
||||
... mesh=mesh, show_edges=True, line_width=5, opacity=0.4
|
||||
... ) # doctest:+SKIP
|
||||
>>> pl.show() # doctest:+SKIP
|
||||
|
||||
"""
|
||||
# Deprecated on v0.46.0, estimated removal on v0.49.0
|
||||
warnings.warn(
|
||||
'`pyvista.voxelize` is deprecated. Use `pyvista.DataSetFilters.voxelize` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
return _voxelize_legacy(
|
||||
mesh=mesh,
|
||||
density=density,
|
||||
check_surface=check_surface,
|
||||
enclosed=enclosed,
|
||||
fit_bounds=fit_bounds,
|
||||
)
|
||||
|
||||
|
||||
def _voxelize_legacy(
|
||||
mesh,
|
||||
*,
|
||||
density=None,
|
||||
check_surface: bool = True,
|
||||
enclosed: bool = False,
|
||||
fit_bounds: bool = False,
|
||||
):
|
||||
"""Voxelize mesh to UnstructuredGrid.
|
||||
|
||||
The public `voxelize` function is deprecated but we need to keep it for
|
||||
generating the PyVista logo.
|
||||
|
||||
"""
|
||||
if not pyvista.is_pyvista_dataset(mesh):
|
||||
mesh = wrap(mesh)
|
||||
if density is None:
|
||||
density = mesh.length / 100
|
||||
if isinstance(density, (int, float, np.number)):
|
||||
density_x, density_y, density_z = [density] * 3
|
||||
elif isinstance(density, (Sequence, np.ndarray)):
|
||||
density_x, density_y, density_z = density
|
||||
else:
|
||||
msg = f'Invalid density {density!r}, expected number or array-like.'
|
||||
raise TypeError(msg)
|
||||
|
||||
# check and pre-process input mesh
|
||||
surface = mesh.extract_geometry() # filter preserves topology
|
||||
if not surface.faces.size:
|
||||
# we have a point cloud or an empty mesh
|
||||
msg = 'Input mesh must have faces for voxelization.'
|
||||
raise ValueError(msg)
|
||||
if not surface.is_all_triangles:
|
||||
# reduce chance for artifacts, see gh-1743
|
||||
surface.triangulate(inplace=True)
|
||||
|
||||
if enclosed:
|
||||
# Get x, y, z bin edges
|
||||
x, y, z = _padded_bins(mesh, [density_x, density_y, density_z])
|
||||
else:
|
||||
x_min, x_max, y_min, y_max, z_min, z_max = mesh.bounds
|
||||
if fit_bounds:
|
||||
# Calculate an integer number of voxels, floor to ensure that the voxels
|
||||
# don't exceed the input mesh
|
||||
nof_voxels_x = int(np.round((x_max - x_min) / density_x))
|
||||
nof_voxels_y = int(np.round((y_max - y_min) / density_y))
|
||||
nof_voxels_z = int(np.round((z_max - z_min) / density_z))
|
||||
|
||||
# One additional point is required to ensure the proper number of voxels
|
||||
x = np.linspace(x_min, x_max, nof_voxels_x + 1)
|
||||
y = np.linspace(y_min, y_max, nof_voxels_y + 1)
|
||||
z = np.linspace(z_min, z_max, nof_voxels_z + 1)
|
||||
else:
|
||||
x = np.arange(x_min, x_max, density_x)
|
||||
y = np.arange(y_min, y_max, density_y)
|
||||
z = np.arange(z_min, z_max, density_z)
|
||||
|
||||
x, y, z = np.meshgrid(x, y, z, indexing='ij')
|
||||
# indexing='ij' is used here in order to make grid and ugrid with x-y-z ordering,
|
||||
# not y-x-z ordering, see https://github.com/pyvista/pyvista/pull/4365
|
||||
|
||||
# Create unstructured grid from the structured grid
|
||||
grid = pyvista.StructuredGrid(x, y, z)
|
||||
ugrid = pyvista.UnstructuredGrid(grid)
|
||||
|
||||
if enclosed:
|
||||
# Normalise cells to unit size
|
||||
ugrid_norm = ugrid.copy()
|
||||
surface_norm = surface.copy()
|
||||
ugrid_norm.points /= np.array(density)
|
||||
surface_norm.points /= np.array(density)
|
||||
# Select cells if they're within one unit of the surface
|
||||
ugrid_norm = ugrid_norm.compute_implicit_distance(surface_norm)
|
||||
mask = ugrid_norm['implicit_distance'] < 1
|
||||
del ugrid_norm, surface_norm
|
||||
else:
|
||||
# get part of the mesh within the mesh's bounding surface.
|
||||
selection = ugrid.select_enclosed_points(
|
||||
surface, tolerance=0.0, check_surface=check_surface
|
||||
)
|
||||
mask = selection.point_data['SelectedPoints'].view(np.bool_)
|
||||
del selection
|
||||
|
||||
# extract cells from point indices
|
||||
return ugrid.extract_points(mask)
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['mesh'])
|
||||
def voxelize_volume( # noqa: PLR0917
|
||||
mesh,
|
||||
density=None,
|
||||
check_surface: bool = True, # noqa: FBT001, FBT002
|
||||
enclosed: bool = False, # noqa: FBT001, FBT002
|
||||
fit_bounds: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Voxelize mesh to create a RectilinearGrid voxel volume.
|
||||
|
||||
Creates a voxel volume that encloses the input mesh and discretizes the cells
|
||||
within the volume that intersect or are contained within the input mesh.
|
||||
``InsideMesh``, an array in ``cell_data``, is ``1`` for cells inside and ``0`` outside.
|
||||
|
||||
.. deprecated:: 0.46
|
||||
|
||||
This function is deprecated. Use :meth:`pyvista.DataSetFilters.voxelize_rectilinear`
|
||||
instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : pyvista.DataSet
|
||||
Mesh to voxelize.
|
||||
|
||||
density : float | array_like[float]
|
||||
The uniform size of the voxels when single float passed.
|
||||
Nonuniform voxel size if a list of values are passed along x,y,z directions.
|
||||
Defaults to 1/100th of the mesh length.
|
||||
|
||||
check_surface : bool, default: True
|
||||
Specify whether to check the surface for closure. If on, then the
|
||||
algorithm first checks to see if the surface is closed and
|
||||
manifold. If the surface is not closed and manifold, a runtime
|
||||
error is raised.
|
||||
|
||||
enclosed : bool, default: False
|
||||
If True, the voxel bounds will be outside the mesh.
|
||||
If False, the voxel bounds will be at or inside the mesh bounds.
|
||||
|
||||
fit_bounds : bool, default: False
|
||||
If enabled, the end bound of the input mesh is used as the end bound of the
|
||||
voxel grid and the density is updated to the closest compatible one. Otherwise,
|
||||
the end bound is excluded. Has no effect if `enclosed` is enabled.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.RectilinearGrid
|
||||
RectilinearGrid as voxelized volume with discretized cells.
|
||||
|
||||
See Also
|
||||
--------
|
||||
pyvista.DataSetFilters.voxelize
|
||||
Similar function that returns a :class:`pyvista.UnstructuredGrid` of
|
||||
:attr:`~pyvista.CellType.VOXEL` cells.
|
||||
|
||||
pyvista.DataSetFilters.voxelize_binary_mask
|
||||
Similar function that returns a :class:`pyvista.ImageData` with point data.
|
||||
|
||||
pyvista.DataSetFilters.select_enclosed_points
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an equal density voxel volume from input mesh.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
|
||||
Load file from PyVista examples.
|
||||
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.download_cow() # doctest:+SKIP
|
||||
|
||||
Create an equal density voxel volume and plot the result.
|
||||
|
||||
>>> vox = pv.voxelize_volume(mesh, density=0.15) # doctest:+SKIP
|
||||
>>> cpos = [(15, 3, 15), (0, 0, 0), (0, 0, 0)] # doctest:+SKIP
|
||||
>>> vox.plot(scalars='InsideMesh', show_edges=True, cpos=cpos) # doctest:+SKIP
|
||||
|
||||
Slice the voxel volume to view ``InsideMesh``.
|
||||
|
||||
>>> slices = vox.slice_orthogonal() # doctest:+SKIP
|
||||
>>> slices.plot(scalars='InsideMesh', show_edges=True) # doctest:+SKIP
|
||||
|
||||
Create a voxel volume from unequal density dimensions and plot result.
|
||||
|
||||
>>> vox = pv.voxelize_volume(mesh, density=[0.15, 0.15, 0.5]) # doctest:+SKIP
|
||||
>>> vox.plot(scalars='InsideMesh', show_edges=True, cpos=cpos) # doctest:+SKIP
|
||||
|
||||
Slice the unequal density voxel volume to view ``InsideMesh``.
|
||||
|
||||
>>> slices = vox.slice_orthogonal() # doctest:+SKIP
|
||||
>>> slices.plot(
|
||||
... scalars='InsideMesh', show_edges=True, cpos=cpos
|
||||
... ) # doctest:+SKIP
|
||||
|
||||
Create an equal density voxel volume without enclosing input mesh.
|
||||
|
||||
>>> vox = pv.voxelize_volume(mesh, density=0.15) # doctest:+SKIP
|
||||
>>> vox = vox.select_enclosed_points(mesh, tolerance=0.0) # doctest:+SKIP
|
||||
>>> vox.plot(
|
||||
... scalars='SelectedPoints', show_edges=True, cpos=cpos
|
||||
... ) # doctest:+SKIP
|
||||
|
||||
Create an equal density voxel volume enclosing input mesh.
|
||||
|
||||
>>> vox = pv.voxelize_volume(
|
||||
... mesh, density=0.15, enclosed=True
|
||||
... ) # doctest:+SKIP
|
||||
>>> vox = vox.select_enclosed_points(mesh, tolerance=0.0) # doctest:+SKIP
|
||||
>>> vox.plot(
|
||||
... scalars='SelectedPoints', show_edges=True, cpos=cpos
|
||||
... ) # doctest:+SKIP
|
||||
|
||||
Create an equal density voxel volume that does not fit the input mesh's bounds.
|
||||
|
||||
>>> mesh = pv.examples.load_nut() # doctest:+SKIP
|
||||
>>> vox = pv.voxelize_volume(mesh=mesh, density=2.5) # doctest:+SKIP
|
||||
>>> pl = pv.Plotter() # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(mesh=vox, show_edges=True) # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(mesh=mesh, show_edges=True, opacity=1) # doctest:+SKIP
|
||||
>>> pl.show() # doctest:+SKIP
|
||||
|
||||
Create an equal density voxel volume that fits the input mesh's bounds.
|
||||
|
||||
>>> vox = pv.voxelize_volume(
|
||||
... mesh=mesh, density=2.5, fit_bounds=True
|
||||
... ) # doctest:+SKIP
|
||||
>>> pl = pv.Plotter() # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(mesh=vox, show_edges=True) # doctest:+SKIP
|
||||
>>> _ = pl.add_mesh(mesh=mesh, show_edges=True, opacity=1) # doctest:+SKIP
|
||||
>>> pl.show() # doctest:+SKIP
|
||||
|
||||
"""
|
||||
# Deprecated on v0.46.0, estimated removal on v0.49.0
|
||||
warnings.warn(
|
||||
'`pyvista.voxelize_volume` is deprecated. Use '
|
||||
'`pyvista.DataSetFilters.voxelize_rectilinear` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
mesh = wrap(mesh)
|
||||
if density is None:
|
||||
density = mesh.length / 100
|
||||
if isinstance(density, (int, float, np.number)):
|
||||
density_x, density_y, density_z = [density] * 3
|
||||
elif isinstance(density, (Sequence, np.ndarray)):
|
||||
density_x, density_y, density_z = density
|
||||
else:
|
||||
msg = f'Invalid density {density!r}, expected number or array-like.'
|
||||
raise TypeError(msg)
|
||||
|
||||
# check and pre-process input mesh
|
||||
surface = mesh.extract_geometry() # filter preserves topology
|
||||
if not surface.faces.size:
|
||||
# we have a point cloud or an empty mesh
|
||||
msg = 'Input mesh must have faces for voxelization.'
|
||||
raise ValueError(msg)
|
||||
if not surface.is_all_triangles:
|
||||
# reduce chance for artifacts, see gh-1743
|
||||
surface.triangulate(inplace=True)
|
||||
|
||||
if enclosed:
|
||||
# Get x, y, z bin edges
|
||||
x, y, z = _padded_bins(mesh, [density_x, density_y, density_z])
|
||||
else:
|
||||
x_min, x_max, y_min, y_max, z_min, z_max = mesh.bounds
|
||||
if fit_bounds:
|
||||
# Calculate an integer number of voxels, floor to ensure that the voxels
|
||||
# don't exceed the input mesh
|
||||
nof_voxels_x = int(np.round((x_max - x_min) / density_x))
|
||||
nof_voxels_y = int(np.round((y_max - y_min) / density_y))
|
||||
nof_voxels_z = int(np.round((z_max - z_min) / density_z))
|
||||
|
||||
# One additional point is required to ensure the proper number of voxels
|
||||
x = np.linspace(x_min, x_max, nof_voxels_x + 1)
|
||||
y = np.linspace(y_min, y_max, nof_voxels_y + 1)
|
||||
z = np.linspace(z_min, z_max, nof_voxels_z + 1)
|
||||
else:
|
||||
x = np.arange(x_min, x_max, density_x)
|
||||
y = np.arange(y_min, y_max, density_y)
|
||||
z = np.arange(z_min, z_max, density_z)
|
||||
|
||||
# Create a RectilinearGrid
|
||||
voi = pyvista.RectilinearGrid(x, y, z)
|
||||
|
||||
# get part of the mesh within the mesh's bounding surface.
|
||||
selection = voi.select_enclosed_points(surface, tolerance=0.0, check_surface=check_surface)
|
||||
mask_vol = selection.point_data['SelectedPoints'].view(np.bool_)
|
||||
|
||||
# Get voxels that fall within input mesh boundaries
|
||||
cell_ids = np.unique(voi.extract_points(np.argwhere(mask_vol))['vtkOriginalCellIds'])
|
||||
|
||||
# Create new element of grid where all cells _within_ mesh boundary are
|
||||
# given new name 'MeshCells' and a discrete value of 1
|
||||
voi['InsideMesh'] = np.zeros(voi.n_cells)
|
||||
voi['InsideMesh'][cell_ids] = 1
|
||||
|
||||
return voi
|
||||
|
||||
|
||||
def create_grid(dataset, dimensions=(101, 101, 101)):
|
||||
"""Create a uniform grid surrounding the given dataset.
|
||||
|
||||
The output grid will have the specified dimensions and is commonly used
|
||||
for interpolating the input dataset.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dataset : DataSet
|
||||
Input dataset used as a reference for the grid creation.
|
||||
dimensions : tuple[int, int, int], default: (101, 101, 101)
|
||||
The dimensions of the grid to be created. Each value in the tuple
|
||||
represents the number of grid points along the corresponding axis.
|
||||
|
||||
Raises
|
||||
------
|
||||
NotImplementedError
|
||||
If the dimensions parameter is set to None. Currently, the function
|
||||
does not support automatically determining the "optimal" grid size
|
||||
based on the sparsity of the points in the input dataset.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ImageData
|
||||
A uniform grid with the specified dimensions that surrounds the input
|
||||
dataset.
|
||||
|
||||
"""
|
||||
bounds = np.array(dataset.bounds)
|
||||
if dimensions is None:
|
||||
# TODO: we should implement an algorithm to automatically determine an
|
||||
# "optimal" grid size by looking at the sparsity of the points in the
|
||||
# input dataset - I actually think VTK might have this implemented
|
||||
# somewhere
|
||||
msg = 'Please specify dimensions.'
|
||||
raise NotImplementedError(msg)
|
||||
dimensions = np.array(dimensions, dtype=int)
|
||||
image = pyvista.ImageData()
|
||||
image.dimensions = dimensions
|
||||
dims = dimensions - 1
|
||||
dims[dims == 0] = 1
|
||||
image.spacing = (bounds[1::2] - bounds[:-1:2]) / dims
|
||||
image.origin = bounds[::2]
|
||||
return image
|
||||
|
||||
|
||||
def grid_from_sph_coords(theta, phi, r):
|
||||
"""Create a structured grid from arrays of spherical coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
theta : array_like[float]
|
||||
Azimuthal angle in degrees ``[0, 360]``.
|
||||
phi : array_like[float]
|
||||
Polar (zenith) angle in degrees ``[0, 180]``.
|
||||
r : array_like[float]
|
||||
Distance (radius) from the point of origin.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.StructuredGrid
|
||||
Structured grid.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:ref:`spherical_example`
|
||||
|
||||
"""
|
||||
x, y, z = np.meshgrid(np.radians(theta), np.radians(phi), r)
|
||||
# Transform grid to cartesian coordinates
|
||||
x_cart = z * np.sin(y) * np.cos(x)
|
||||
y_cart = z * np.sin(y) * np.sin(x)
|
||||
z_cart = z * np.cos(y)
|
||||
# Make a grid object
|
||||
return pyvista.StructuredGrid(x_cart, y_cart, z_cart)
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def transform_vectors_sph_to_cart(theta, phi, r, u, v, w): # noqa: PLR0917 # numpydoc ignore=RT02
|
||||
"""Transform vectors from spherical (r, phi, theta) to cartesian coordinates (z, y, x).
|
||||
|
||||
Note the "reverse" order of arrays's axes, commonly used in geosciences.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
theta : array_like[float]
|
||||
Azimuthal angle in degrees ``[0, 360]`` of shape ``(M,)``.
|
||||
phi : array_like[float]
|
||||
Polar (zenith) angle in degrees ``[0, 180]`` of shape ``(N,)``.
|
||||
r : array_like[float]
|
||||
Distance (radius) from the point of origin of shape ``(P,)``.
|
||||
u : array_like[float]
|
||||
X-component of the vector of shape ``(P, N, M)``.
|
||||
v : array_like[float]
|
||||
Y-component of the vector of shape ``(P, N, M)``.
|
||||
w : array_like[float]
|
||||
Z-component of the vector of shape ``(P, N, M)``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
u_t, v_t, w_t : :class:`numpy.ndarray`
|
||||
Arrays of transformed x-, y-, z-components, respectively.
|
||||
|
||||
"""
|
||||
xx, yy, _ = np.meshgrid(np.radians(theta), np.radians(phi), r, indexing='ij')
|
||||
th, ph = xx.squeeze(), yy.squeeze()
|
||||
|
||||
# Transform wind components from spherical to cartesian coordinates
|
||||
# https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates
|
||||
u_t = np.sin(ph) * np.cos(th) * w + np.cos(ph) * np.cos(th) * v - np.sin(th) * u
|
||||
v_t = np.sin(ph) * np.sin(th) * w + np.cos(ph) * np.sin(th) * v + np.cos(th) * u
|
||||
w_t = np.cos(ph) * w - np.sin(ph) * v
|
||||
|
||||
return u_t, v_t, w_t
|
||||
|
||||
|
||||
def cartesian_to_spherical(x, y, z):
|
||||
"""Convert 3D Cartesian coordinates to spherical coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y, z : numpy.ndarray
|
||||
Cartesian coordinates.
|
||||
|
||||
Returns
|
||||
-------
|
||||
r : numpy.ndarray
|
||||
Radial distance.
|
||||
|
||||
phi : numpy.ndarray
|
||||
Angle (radians) with respect to the polar axis. Also known
|
||||
as polar angle.
|
||||
|
||||
theta : numpy.ndarray
|
||||
Angle (radians) of rotation from the initial meridian plane.
|
||||
Also known as azimuthal angle.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> grid = pv.ImageData(dimensions=(3, 3, 3))
|
||||
>>> x, y, z = grid.points.T
|
||||
>>> r, phi, theta = pv.cartesian_to_spherical(x, y, z)
|
||||
|
||||
"""
|
||||
xy2 = x**2 + y**2
|
||||
r = np.sqrt(xy2 + z**2)
|
||||
phi = np.arctan2(np.sqrt(xy2), z) # the polar angle in radian angles
|
||||
theta = np.arctan2(y, x) # the azimuth angle in radian angles
|
||||
|
||||
return r, phi, theta
|
||||
|
||||
|
||||
def spherical_to_cartesian(r, phi, theta):
|
||||
"""Convert Spherical coordinates to 3D Cartesian coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r : numpy.ndarray
|
||||
Radial distance.
|
||||
|
||||
phi : numpy.ndarray
|
||||
Angle (radians) with respect to the polar axis. Also known
|
||||
as polar angle.
|
||||
|
||||
theta : numpy.ndarray
|
||||
Angle (radians) of rotation from the initial meridian plane.
|
||||
Also known as azimuthal angle.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray, numpy.ndarray, numpy.ndarray
|
||||
Cartesian coordinates.
|
||||
|
||||
"""
|
||||
s = np.sin(phi)
|
||||
x = r * s * np.cos(theta)
|
||||
y = r * s * np.sin(theta)
|
||||
z = r * np.cos(phi)
|
||||
return x, y, z
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['datasets'])
|
||||
def merge( # noqa: PLR0917
|
||||
datasets,
|
||||
merge_points: bool = True, # noqa: FBT001, FBT002
|
||||
main_has_priority: bool | None = None, # noqa: FBT001
|
||||
progress_bar: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Merge several datasets.
|
||||
|
||||
.. note::
|
||||
The behavior of this filter varies from the
|
||||
:func:`PolyDataFilters.boolean_union` filter. This filter
|
||||
does not attempt to create a manifold mesh and will include
|
||||
internal surfaces when two meshes overlap.
|
||||
|
||||
.. warning::
|
||||
|
||||
The merge order of this filter depends on the installed version
|
||||
of VTK. For example, if merging meshes ``a``, ``b``, and ``c``,
|
||||
the merged order is ``bca`` for VTK<9.5 and ``abc`` for VTK>=9.5.
|
||||
This may be a breaking change for some applications. If only
|
||||
merging two meshes, it may be possible to maintain `some` backwards
|
||||
compatibility by swapping the input order of the two meshes,
|
||||
though this may also affect the merged arrays and is therefore
|
||||
not fully backwards-compatible.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
datasets : sequence[:class:`pyvista.DataSet`]
|
||||
Sequence of datasets. Can be of any :class:`pyvista.DataSet`.
|
||||
|
||||
merge_points : bool, default: True
|
||||
Merge equivalent points when ``True``.
|
||||
|
||||
main_has_priority : bool, default: True
|
||||
When this parameter is ``True`` and ``merge_points=True``, the arrays
|
||||
of the merging grids will be overwritten by the original main mesh.
|
||||
|
||||
.. deprecated:: 0.46
|
||||
|
||||
This keyword will be removed in a future version. The main mesh
|
||||
always has priority with VTK 9.5.0 or later.
|
||||
|
||||
progress_bar : bool, default: False
|
||||
Display a progress bar to indicate progress.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.DataSet
|
||||
:class:`pyvista.PolyData` if all items in datasets are
|
||||
:class:`pyvista.PolyData`, otherwise returns a
|
||||
:class:`pyvista.UnstructuredGrid`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Merge two polydata datasets.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> sphere = pv.Sphere(center=(0, 0, 1))
|
||||
>>> cube = pv.Cube()
|
||||
>>> mesh = pv.merge([cube, sphere])
|
||||
>>> mesh.plot()
|
||||
|
||||
"""
|
||||
if not isinstance(datasets, Sequence):
|
||||
msg = f'Expected a sequence, got {type(datasets).__name__}'
|
||||
raise TypeError(msg)
|
||||
|
||||
if len(datasets) < 1:
|
||||
msg = 'Expected at least one dataset.'
|
||||
raise ValueError(msg)
|
||||
|
||||
first = datasets[0]
|
||||
if not isinstance(first, pyvista.DataSet):
|
||||
msg = f'Expected pyvista.DataSet, not {type(first).__name__}'
|
||||
raise TypeError(msg)
|
||||
|
||||
return datasets[0].merge(
|
||||
datasets[1:],
|
||||
merge_points=merge_points,
|
||||
main_has_priority=main_has_priority,
|
||||
progress_bar=progress_bar,
|
||||
)
|
||||
|
||||
|
||||
def perlin_noise(amplitude, freq: Sequence[float], phase: Sequence[float]):
|
||||
"""Return the implicit function that implements Perlin noise.
|
||||
|
||||
Uses :vtk:`vtkPerlinNoise` and computes a Perlin noise field as
|
||||
an implicit function. :vtk:`vtkPerlinNoise` is a concrete
|
||||
implementation of :vtk:`vtkImplicitFunction`. Perlin noise,
|
||||
originally described by Ken Perlin, is a non-periodic and
|
||||
continuous noise function useful for modeling real-world objects.
|
||||
|
||||
The amplitude and frequency of the noise pattern are
|
||||
adjustable. This implementation of Perlin noise is derived closely
|
||||
from Greg Ward's version in Graphics Gems II.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
amplitude : float
|
||||
Amplitude of the noise function.
|
||||
|
||||
``amplitude`` can be negative. The noise function varies
|
||||
randomly between ``-|Amplitude|`` and
|
||||
``|Amplitude|``. Therefore the range of values is
|
||||
``2*|Amplitude|`` large. The initial amplitude is 1.
|
||||
|
||||
freq : sequence[float]
|
||||
The frequency, or physical scale, of the noise function
|
||||
(higher is finer scale).
|
||||
|
||||
The frequency can be adjusted per axis, or the same for all axes.
|
||||
|
||||
phase : sequence[float]
|
||||
Set/get the phase of the noise function.
|
||||
|
||||
This parameter can be used to shift the noise function within
|
||||
space (perhaps to avoid a beat with a noise pattern at another
|
||||
scale). Phase tends to repeat about every unit, so a phase of
|
||||
0.5 is a half-cycle shift.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:vtk:`vtkPerlinNoise`
|
||||
Instance of :vtk:`vtkPerlinNoise` to a Perlin noise field as an
|
||||
implicit function. Use with :func:`~pyvista.sample_function`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:ref:`perlin_noise_2d_example`
|
||||
:ref:`perlin_noise_3d_example`
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a Perlin noise function with an amplitude of 0.1, frequency
|
||||
for all axes of 1, and a phase of 0 for all axes.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> noise = pv.perlin_noise(0.1, (1, 1, 1), (0, 0, 0))
|
||||
|
||||
Sample Perlin noise over a structured grid and plot it.
|
||||
|
||||
>>> grid = pv.sample_function(noise, bounds=[0, 5, 0, 5, 0, 5])
|
||||
>>> grid.plot()
|
||||
|
||||
"""
|
||||
noise = _vtk.vtkPerlinNoise()
|
||||
noise.SetAmplitude(amplitude)
|
||||
noise.SetFrequency(freq)
|
||||
noise.SetPhase(phase)
|
||||
return noise
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['function'])
|
||||
def sample_function( # noqa: PLR0917
|
||||
function: _vtk.vtkImplicitFunction,
|
||||
bounds: Sequence[float] = (-1.0, 1.0, -1.0, 1.0, -1.0, 1.0),
|
||||
dim: Sequence[int] = (50, 50, 50),
|
||||
compute_normals: bool = False, # noqa: FBT001, FBT002
|
||||
output_type: np.dtype = np.double, # type: ignore[assignment]
|
||||
capping: bool = False, # noqa: FBT001, FBT002
|
||||
cap_value: float = sys.float_info.max,
|
||||
scalar_arr_name: str = 'scalars',
|
||||
normal_arr_name: str = 'normals',
|
||||
progress_bar: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Sample an implicit function over a structured point set.
|
||||
|
||||
Uses :vtk:`vtkSampleFunction`
|
||||
|
||||
This method evaluates an implicit function and normals at each
|
||||
point in a :vtk:`vtkStructuredPoints`. The user can specify the
|
||||
sample dimensions and location in space to perform the sampling.
|
||||
|
||||
To create closed surfaces (in conjunction with the
|
||||
:vtk:`vtkContourFilter`), capping can be turned on to set a particular
|
||||
value on the boundaries of the sample space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
function : :vtk:`vtkImplicitFunction`
|
||||
Implicit function to evaluate. For example, the function
|
||||
generated from :func:`perlin_noise() <pyvista.core.utilities.features.perlin_noise>`.
|
||||
|
||||
bounds : sequence[float], default: (-1.0, 1.0, -1.0, 1.0, -1.0, 1.0)
|
||||
Specify the bounds in the format of:
|
||||
|
||||
- ``(x_min, x_max, y_min, y_max, z_min, z_max)``.
|
||||
|
||||
dim : sequence[float], default: (50, 50, 50)
|
||||
Dimensions of the data on which to sample in the format of
|
||||
``(xdim, ydim, zdim)``.
|
||||
|
||||
compute_normals : bool, default: False
|
||||
Enable or disable the computation of normals.
|
||||
|
||||
output_type : numpy.dtype, default: numpy.double
|
||||
Set the output scalar type. One of the following:
|
||||
|
||||
- ``np.float64``
|
||||
- ``np.float32``
|
||||
- ``np.int64``
|
||||
- ``np.uint64``
|
||||
- ``np.int32``
|
||||
- ``np.uint32``
|
||||
- ``np.int16``
|
||||
- ``np.uint16``
|
||||
- ``np.int8``
|
||||
- ``np.uint8``
|
||||
|
||||
capping : bool, default: False
|
||||
Enable or disable capping. If capping is enabled, then the outer
|
||||
boundaries of the structured point set are set to cap value. This can
|
||||
be used to ensure surfaces are closed.
|
||||
|
||||
cap_value : float, default: sys.float_info.max
|
||||
Capping value used with the ``capping`` parameter.
|
||||
|
||||
scalar_arr_name : str, default: "scalars"
|
||||
Set the scalar array name for this data set.
|
||||
|
||||
normal_arr_name : str, default: "normals"
|
||||
Set the normal array name for this data set.
|
||||
|
||||
progress_bar : bool, default: False
|
||||
Display a progress bar to indicate progress.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
Uniform grid with sampled data.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Sample Perlin noise over a structured grid in 3D.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> noise = pv.perlin_noise(0.1, (1, 1, 1), (0, 0, 0))
|
||||
>>> grid = pv.sample_function(
|
||||
... noise, bounds=[0, 3.0, -0, 1.0, 0, 1.0], dim=(60, 20, 20)
|
||||
... )
|
||||
>>> grid.plot(cmap='gist_earth_r', show_scalar_bar=False, show_edges=True)
|
||||
|
||||
Sample Perlin noise in 2D and plot it.
|
||||
|
||||
>>> noise = pv.perlin_noise(0.1, (5, 5, 5), (0, 0, 0))
|
||||
>>> surf = pv.sample_function(noise, dim=(200, 200, 1))
|
||||
>>> surf.plot()
|
||||
|
||||
See :ref:`perlin_noise_2d_example` and :ref:`perlin_noise_3d_example`
|
||||
for a full example using this function.
|
||||
|
||||
"""
|
||||
# internal import to avoide circular dependency
|
||||
from pyvista.core.filters import _update_alg # noqa: PLC0415
|
||||
|
||||
samp = _vtk.vtkSampleFunction()
|
||||
samp.SetImplicitFunction(function)
|
||||
samp.SetSampleDimensions(dim) # type: ignore[call-overload]
|
||||
samp.SetModelBounds(bounds)
|
||||
samp.SetComputeNormals(compute_normals)
|
||||
samp.SetCapping(capping)
|
||||
samp.SetCapValue(cap_value)
|
||||
samp.SetNormalArrayName(normal_arr_name)
|
||||
samp.SetScalarArrayName(scalar_arr_name)
|
||||
|
||||
if output_type == np.float64:
|
||||
samp.SetOutputScalarTypeToDouble()
|
||||
elif output_type == np.float32:
|
||||
samp.SetOutputScalarTypeToFloat()
|
||||
elif output_type == np.int64:
|
||||
if os.name == 'nt':
|
||||
msg = 'This function on Windows only supports int32 or smaller'
|
||||
raise ValueError(msg)
|
||||
samp.SetOutputScalarTypeToLong()
|
||||
elif output_type == np.uint64:
|
||||
if os.name == 'nt':
|
||||
msg = 'This function on Windows only supports int32 or smaller'
|
||||
raise ValueError(msg)
|
||||
samp.SetOutputScalarTypeToUnsignedLong()
|
||||
elif output_type == np.int32:
|
||||
samp.SetOutputScalarTypeToInt()
|
||||
elif output_type == np.uint32:
|
||||
samp.SetOutputScalarTypeToUnsignedInt()
|
||||
elif output_type == np.int16:
|
||||
samp.SetOutputScalarTypeToShort()
|
||||
elif output_type == np.uint16:
|
||||
samp.SetOutputScalarTypeToUnsignedShort()
|
||||
elif output_type == np.int8:
|
||||
samp.SetOutputScalarTypeToChar()
|
||||
elif output_type == np.uint8:
|
||||
samp.SetOutputScalarTypeToUnsignedChar()
|
||||
else:
|
||||
msg = f'Invalid output_type {output_type}'
|
||||
raise ValueError(msg)
|
||||
|
||||
_update_alg(samp, progress_bar=progress_bar, message='Sampling')
|
||||
return wrap(samp.GetOutput())
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,398 @@
|
||||
"""Core helper utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import deque
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import overload
|
||||
|
||||
import numpy as np
|
||||
from typing_extensions import TypeIs
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _validation
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
|
||||
from . import transformations
|
||||
from .fileio import from_meshio
|
||||
from .fileio import is_meshio_mesh
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from meshio import Mesh
|
||||
from trimesh import Trimesh
|
||||
|
||||
from pyvista import DataObject
|
||||
from pyvista import DataSet
|
||||
from pyvista import ExplicitStructuredGrid
|
||||
from pyvista import ImageData
|
||||
from pyvista import MultiBlock
|
||||
from pyvista import PartitionedDataSet
|
||||
from pyvista import PointSet
|
||||
from pyvista import PolyData
|
||||
from pyvista import RectilinearGrid
|
||||
from pyvista import StructuredGrid
|
||||
from pyvista import Table
|
||||
from pyvista import UnstructuredGrid
|
||||
from pyvista import pyvista_ndarray
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
from pyvista.core._typing_core import VectorLike
|
||||
from pyvista.wrappers import _WrappableVTKDataObjectType
|
||||
|
||||
|
||||
# vtkDataSet overloads
|
||||
# Overload types should match the mappings in the `pyvista._wrappers` dict
|
||||
# Overloads should be ordered from narrow types (child class) to general types (parent class)
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkPolyData) -> PolyData: ... # type: ignore[overload-overlap]
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkStructuredGrid) -> StructuredGrid: ... # type: ignore[overload-overlap]
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkExplicitStructuredGrid) -> ExplicitStructuredGrid: ... # type: ignore[overload-overlap]
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkUnstructuredGrid) -> UnstructuredGrid: ... # type: ignore[overload-overlap]
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkPointSet) -> PointSet: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkRectilinearGrid) -> RectilinearGrid: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkStructuredPoints) -> ImageData: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkImageData) -> ImageData: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkMultiBlockDataSet) -> MultiBlock: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkTable) -> Table: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkPartitionedDataSet) -> PartitionedDataSet: ...
|
||||
|
||||
|
||||
# General catch-all cases
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkDataSet) -> DataSet: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkDataObject) -> DataObject: ...
|
||||
|
||||
|
||||
# Misc overloads
|
||||
@overload
|
||||
def wrap(dataset: NumpyArray[float]) -> PolyData | ImageData: ...
|
||||
@overload
|
||||
def wrap(dataset: _vtk.vtkAbstractArray) -> pyvista_ndarray: ...
|
||||
@overload
|
||||
def wrap(dataset: None) -> None: ...
|
||||
|
||||
|
||||
# Third-party meshes
|
||||
@overload
|
||||
def wrap(dataset: Trimesh) -> PolyData: ...
|
||||
# TODO: Support meshio overload
|
||||
# @overload
|
||||
# def wrap(dataset: Mesh) -> UnstructuredGrid: ...
|
||||
def wrap( # noqa: PLR0911
|
||||
dataset: _WrappableVTKDataObjectType
|
||||
| DataObject
|
||||
| Trimesh
|
||||
| Mesh
|
||||
| _vtk.vtkAbstractArray
|
||||
| NumpyArray[float]
|
||||
| None,
|
||||
) -> DataObject | pyvista_ndarray | None:
|
||||
"""Wrap any given VTK data object to its appropriate PyVista data object.
|
||||
|
||||
Other formats that are supported include:
|
||||
|
||||
* 2D :class:`numpy.ndarray` of XYZ vertices
|
||||
* 3D :class:`numpy.ndarray` representing a volume. Values will be scalars.
|
||||
* 3D :class:`trimesh.Trimesh` mesh.
|
||||
* 3D :class:`meshio.Mesh` mesh.
|
||||
|
||||
.. versionchanged:: 0.38.0
|
||||
If the passed object is already a wrapped PyVista object, then
|
||||
this is no-op and will return that object directly. In previous
|
||||
versions of PyVista, this would perform a shallow copy.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dataset : :class:`numpy.ndarray` | :class:`trimesh.Trimesh` | vtk.DataSet
|
||||
Dataset to wrap.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.DataSet
|
||||
The PyVista wrapped dataset.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:ref:`wrap_trimesh_example`
|
||||
|
||||
Examples
|
||||
--------
|
||||
Wrap a numpy array representing a random point cloud.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> points = np.random.default_rng().random((10, 3))
|
||||
>>> cloud = pv.wrap(points)
|
||||
>>> cloud
|
||||
PolyData (...)
|
||||
N Cells: 10
|
||||
N Points: 10
|
||||
N Strips: 0
|
||||
X Bounds: ...
|
||||
Y Bounds: ...
|
||||
Z Bounds: ...
|
||||
N Arrays: 0
|
||||
|
||||
Wrap a VTK object.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> import vtk
|
||||
>>> points = vtk.vtkPoints()
|
||||
>>> p = [1.0, 2.0, 3.0]
|
||||
>>> vertices = vtk.vtkCellArray()
|
||||
>>> pid = points.InsertNextPoint(p)
|
||||
>>> _ = vertices.InsertNextCell(1)
|
||||
>>> _ = vertices.InsertCellPoint(pid)
|
||||
>>> point = vtk.vtkPolyData()
|
||||
>>> _ = point.SetPoints(points)
|
||||
>>> _ = point.SetVerts(vertices)
|
||||
>>> mesh = pv.wrap(point)
|
||||
>>> mesh
|
||||
PolyData (...)
|
||||
N Cells: 1
|
||||
N Points: 1
|
||||
N Strips: 0
|
||||
X Bounds: 1.000e+00, 1.000e+00
|
||||
Y Bounds: 2.000e+00, 2.000e+00
|
||||
Z Bounds: 3.000e+00, 3.000e+00
|
||||
N Arrays: 0
|
||||
|
||||
Wrap a Trimesh object.
|
||||
|
||||
>>> import trimesh
|
||||
>>> import pyvista as pv
|
||||
>>> points = [[0, 0, 0], [0, 0, 1], [0, 1, 0]]
|
||||
>>> faces = [[0, 1, 2]]
|
||||
>>> tmesh = trimesh.Trimesh(points, faces=faces, process=False)
|
||||
>>> mesh = pv.wrap(tmesh)
|
||||
>>> mesh # doctest:+SKIP
|
||||
PolyData (0x7fc55ff27ad0)
|
||||
N Cells: 1
|
||||
N Points: 3
|
||||
X Bounds: 0.000e+00, 0.000e+00
|
||||
Y Bounds: 0.000e+00, 1.000e+00
|
||||
Z Bounds: 0.000e+00, 1.000e+00
|
||||
N Arrays: 0
|
||||
|
||||
"""
|
||||
# Return if None
|
||||
if dataset is None:
|
||||
return None
|
||||
|
||||
if isinstance(dataset, tuple(pyvista._wrappers.values())):
|
||||
# Return object if it is already wrapped
|
||||
return cast('DataObject', dataset)
|
||||
|
||||
# Check if dataset is a numpy array. We do this first since
|
||||
# pyvista_ndarray contains a VTK type that we don't want to
|
||||
# directly wrap.
|
||||
if isinstance(dataset, (np.ndarray, pyvista.pyvista_ndarray)):
|
||||
if dataset.ndim == 1 and dataset.shape[0] == 3:
|
||||
return pyvista.PolyData(dataset)
|
||||
if dataset.ndim > 1 and dataset.ndim < 3 and dataset.shape[1] == 3:
|
||||
return pyvista.PolyData(dataset)
|
||||
elif dataset.ndim == 3:
|
||||
mesh = pyvista.ImageData(dimensions=dataset.shape)
|
||||
if isinstance(dataset, pyvista.pyvista_ndarray):
|
||||
# this gets rid of pesky VTK reference since we're raveling this
|
||||
dataset = np.asarray(dataset)
|
||||
mesh['values'] = dataset.ravel(order='F')
|
||||
mesh.active_scalars_name = 'values'
|
||||
return mesh
|
||||
else:
|
||||
msg = 'NumPy array could not be wrapped pyvista.'
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
# wrap VTK arrays as pyvista_ndarray
|
||||
if isinstance(dataset, _vtk.vtkDataArray):
|
||||
return pyvista.pyvista_ndarray(dataset)
|
||||
|
||||
# Check if a dataset is a VTK type
|
||||
if hasattr(dataset, 'GetClassName'):
|
||||
key = dataset.GetClassName()
|
||||
try:
|
||||
return pyvista._wrappers[key](dataset)
|
||||
except KeyError:
|
||||
msg = f'VTK data type ({key}) is not currently supported by pyvista.'
|
||||
raise TypeError(msg)
|
||||
|
||||
# wrap meshio
|
||||
if is_meshio_mesh(dataset):
|
||||
return from_meshio(dataset)
|
||||
|
||||
# wrap trimesh
|
||||
if dataset.__class__.__name__ == 'Trimesh':
|
||||
# trimesh doesn't pad faces
|
||||
dataset = cast('Trimesh', dataset)
|
||||
polydata = pyvista.PolyData.from_regular_faces(
|
||||
np.asarray(dataset.vertices),
|
||||
faces=dataset.faces,
|
||||
)
|
||||
# If the Trimesh object has uv, pass them to the PolyData
|
||||
if hasattr(dataset.visual, 'uv') and dataset.visual.uv is not None:
|
||||
polydata.active_texture_coordinates = np.asarray(dataset.visual.uv)
|
||||
return polydata
|
||||
|
||||
# otherwise, flag tell the user we can't wrap this object
|
||||
msg = f'Unable to wrap ({type(dataset)}) into a pyvista type.'
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
|
||||
def is_pyvista_dataset(obj: Any) -> TypeIs[pyvista.DataSet | pyvista.MultiBlock]:
|
||||
"""Return ``True`` if the object is a PyVista wrapped dataset.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj : Any
|
||||
Any object to test.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` when the object is a :class:`pyvista.DataSet`.
|
||||
|
||||
"""
|
||||
return isinstance(obj, (pyvista.DataSet, pyvista.MultiBlock))
|
||||
|
||||
|
||||
def generate_plane(normal: VectorLike[float], origin: VectorLike[float]):
|
||||
"""Return a :vtk:`vtkPlane`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
normal : sequence[float]
|
||||
Three item sequence representing the normal of the plane.
|
||||
|
||||
origin : sequence[float]
|
||||
Three item sequence representing the origin of the plane.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:vtk:`vtkPlane`
|
||||
VTK plane.
|
||||
|
||||
"""
|
||||
plane = _vtk.vtkPlane()
|
||||
# NORMAL MUST HAVE MAGNITUDE OF 1
|
||||
normal_ = _validation.validate_array3(normal, dtype_out=float)
|
||||
normal_ = normal_ / np.linalg.norm(normal_)
|
||||
plane.SetNormal(*normal_)
|
||||
plane.SetOrigin(*origin)
|
||||
return plane
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['points', 'angle'])
|
||||
def axis_rotation( # noqa: PLR0917
|
||||
points: NumpyArray[float],
|
||||
angle: float,
|
||||
inplace: bool = False, # noqa: FBT001, FBT002
|
||||
deg: bool = True, # noqa: FBT001, FBT002
|
||||
axis='z',
|
||||
):
|
||||
"""Rotate points by angle about an axis.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : numpy.ndarray
|
||||
Array of points with shape ``(N, 3)``.
|
||||
|
||||
angle : float
|
||||
Rotation angle.
|
||||
|
||||
inplace : bool, default: False
|
||||
Updates points in-place while returning nothing.
|
||||
|
||||
deg : bool, default: True
|
||||
If ``True``, the angle is interpreted as degrees instead of
|
||||
radians.
|
||||
|
||||
axis : str, default: "z"
|
||||
Name of axis to rotate about. Valid options are ``'x'``, ``'y'``,
|
||||
and ``'z'``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Rotated points.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Rotate a set of points by 90 degrees about the x-axis in-place.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> points = examples.load_airplane().points
|
||||
>>> points_orig = points.copy()
|
||||
>>> pv.axis_rotation(points, 90, axis='x', deg=True, inplace=True)
|
||||
>>> assert np.all(np.isclose(points[:, 0], points_orig[:, 0]))
|
||||
>>> assert np.all(np.isclose(points[:, 1], -points_orig[:, 2]))
|
||||
>>> assert np.all(np.isclose(points[:, 2], points_orig[:, 1]))
|
||||
|
||||
"""
|
||||
axis = axis.lower()
|
||||
axis_to_vec = {'x': (1, 0, 0), 'y': (0, 1, 0), 'z': (0, 0, 1)}
|
||||
|
||||
if axis not in axis_to_vec:
|
||||
msg = 'Invalid axis. Must be either "x", "y", or "z"'
|
||||
raise ValueError(msg)
|
||||
|
||||
rot_mat = transformations.axis_angle_rotation(axis_to_vec[axis], angle, deg=deg)
|
||||
return transformations.apply_transformation_to_points(rot_mat, points, inplace=inplace)
|
||||
|
||||
|
||||
def is_inside_bounds(point, bounds):
|
||||
"""Check if a point is inside a set of bounds.
|
||||
|
||||
This is implemented through recursion so that this is N-dimensional.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
point : sequence[float]
|
||||
Three item cartesian point (i.e. ``[x, y, z]``).
|
||||
|
||||
bounds : sequence[float]
|
||||
Six item bounds in the form of ``(x_min, x_max, y_min, y_max, z_min, z_max)``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` when ``point`` is inside ``bounds``.
|
||||
|
||||
"""
|
||||
if isinstance(point, (int, float)):
|
||||
point = [point]
|
||||
if isinstance(point, (np.ndarray, Sequence)) and not isinstance(
|
||||
point,
|
||||
deque,
|
||||
):
|
||||
if len(bounds) < 2 * len(point) or len(bounds) % 2 != 0:
|
||||
msg = 'Bounds mismatch point dimensionality'
|
||||
raise ValueError(msg)
|
||||
point = deque(point)
|
||||
bounds = deque(bounds)
|
||||
return is_inside_bounds(point, bounds)
|
||||
if not isinstance(point, deque):
|
||||
msg = f'Unknown input data type ({type(point)}).'
|
||||
raise TypeError(msg)
|
||||
if len(point) < 1:
|
||||
return True
|
||||
p = point.popleft()
|
||||
lower, upper = bounds.popleft(), bounds.popleft()
|
||||
if lower <= p <= upper:
|
||||
return is_inside_bounds(point, bounds)
|
||||
return False
|
||||
@@ -0,0 +1,879 @@
|
||||
"""Provide sources for generating images."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
from .helpers import wrap
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
class ImageEllipsoidSource(
|
||||
_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkImageEllipsoidSource
|
||||
):
|
||||
"""Create a binary image of an ellipsoid class.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
center : sequence[float]
|
||||
The center of the ellipsoid.
|
||||
|
||||
radius : tuple
|
||||
The radius of the ellipsoid.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an image of an ellipsoid.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> source = pv.ImageEllipsoidSource(
|
||||
... whole_extent=(0, 20, 0, 20, 0, 0),
|
||||
... center=(10, 10, 0),
|
||||
... radius=(3, 4, 5),
|
||||
... )
|
||||
>>> source.output.plot(cpos='xy')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, whole_extent=None, center=None, radius=None) -> None:
|
||||
super().__init__()
|
||||
if whole_extent is not None:
|
||||
self.whole_extent = whole_extent
|
||||
if center is not None:
|
||||
self.center = center
|
||||
if radius is not None:
|
||||
self.radius = radius
|
||||
|
||||
@property
|
||||
def whole_extent(self) -> Sequence[int]:
|
||||
"""Get extent of the whole output image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
return self.GetWholeExtent()
|
||||
|
||||
@whole_extent.setter
|
||||
def whole_extent(self, whole_extent: Sequence[int]) -> None:
|
||||
"""Set extent of the whole output image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
self.SetWholeExtent(whole_extent) # type: ignore[call-overload]
|
||||
|
||||
@property
|
||||
def center(self) -> tuple[float, float, float]:
|
||||
"""Get the center of the ellipsoid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[float, float, float]
|
||||
The center of the ellipsoid.
|
||||
|
||||
"""
|
||||
return self.GetCenter()
|
||||
|
||||
@center.setter
|
||||
def center(self, center: Sequence[float]) -> None:
|
||||
"""Set the center of the ellipsoid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
center : sequence[float]
|
||||
The center of the ellipsoid.
|
||||
|
||||
"""
|
||||
self.SetCenter(center)
|
||||
|
||||
@property
|
||||
def radius(self) -> Sequence[float]:
|
||||
"""Get the radius of the ellipsoid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[float]
|
||||
The radius of the ellipsoid.
|
||||
|
||||
"""
|
||||
return self.GetRadius()
|
||||
|
||||
@radius.setter
|
||||
def radius(self, radius: Sequence[float]) -> None:
|
||||
"""Set the radius of the ellipsoid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : sequence[float]
|
||||
The radius of the ellipsoid.
|
||||
|
||||
"""
|
||||
self.SetRadius(radius)
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""Get the output image as a ImageData.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
The output image.
|
||||
|
||||
"""
|
||||
self.Update()
|
||||
return wrap(self.GetOutput())
|
||||
|
||||
|
||||
class ImageMandelbrotSource(
|
||||
_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkImageMandelbrotSource
|
||||
):
|
||||
"""Create an image of the Mandelbrot set.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
maxiter : int
|
||||
The maximum number of iterations.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an image of the Mandelbrot set.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> source = pv.ImageMandelbrotSource(
|
||||
... whole_extent=(0, 200, 0, 200, 0, 0),
|
||||
... maxiter=100,
|
||||
... )
|
||||
>>> source.output.plot(cpos='xy')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, whole_extent=None, maxiter=None) -> None:
|
||||
super().__init__()
|
||||
if whole_extent is not None:
|
||||
self.whole_extent = whole_extent
|
||||
if maxiter is not None:
|
||||
self.maxiter = maxiter
|
||||
|
||||
@property
|
||||
def whole_extent(self) -> Sequence[int]:
|
||||
"""Get extent of the whole output image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
return self.GetWholeExtent()
|
||||
|
||||
@whole_extent.setter
|
||||
def whole_extent(self, whole_extent: Sequence[int]) -> None:
|
||||
"""Set extent of the whole output image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
self.SetWholeExtent(whole_extent) # type: ignore[call-overload]
|
||||
|
||||
@property
|
||||
def maxiter(self) -> int:
|
||||
"""Get the maximum number of iterations.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The maximum number of iterations.
|
||||
|
||||
"""
|
||||
return self.GetMaximumNumberOfIterations()
|
||||
|
||||
@maxiter.setter
|
||||
def maxiter(self, maxiter: int) -> None:
|
||||
"""Set the maximum number of iterations.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
maxiter : int
|
||||
The maximum number of iterations.
|
||||
|
||||
"""
|
||||
self.SetMaximumNumberOfIterations(maxiter)
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""Get the output image as a ImageData.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
The output image.
|
||||
|
||||
"""
|
||||
self.Update()
|
||||
return wrap(self.GetOutput())
|
||||
|
||||
|
||||
class ImageNoiseSource(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkImageNoiseSource):
|
||||
"""Create an image filled with uniform noise.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
minimum : float
|
||||
The minimum value for the generated noise.
|
||||
|
||||
maximum : float
|
||||
The maximum value for the generated noise.
|
||||
|
||||
seed : int, optional
|
||||
Seed the random number generator with a value.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an image of noise.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> source = pv.ImageNoiseSource(
|
||||
... whole_extent=(0, 200, 0, 200, 0, 0),
|
||||
... minimum=0,
|
||||
... maximum=255,
|
||||
... seed=0,
|
||||
... )
|
||||
>>> source.output.plot(cpos='xy')
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def __init__( # noqa: PLR0917
|
||||
self,
|
||||
whole_extent=(0, 255, 0, 255, 0, 0),
|
||||
minimum=0.0,
|
||||
maximum=1.0,
|
||||
seed=None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if whole_extent is not None:
|
||||
self.whole_extent = whole_extent
|
||||
if minimum is not None:
|
||||
self.minimum = minimum
|
||||
if maximum is not None:
|
||||
self.maximum = maximum
|
||||
if seed is not None:
|
||||
self.seed(seed)
|
||||
|
||||
@property
|
||||
def whole_extent(self) -> Sequence[int]:
|
||||
"""Get extent of the whole output image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
return self._whole_extent
|
||||
|
||||
@whole_extent.setter
|
||||
def whole_extent(self, whole_extent: Sequence[int]) -> None:
|
||||
"""Set extent of the whole output image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
self._whole_extent = whole_extent
|
||||
self.SetWholeExtent(whole_extent)
|
||||
|
||||
@property
|
||||
def minimum(self) -> float:
|
||||
"""Get the minimum value for the generated noise.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The minimum value for the generated noise.
|
||||
|
||||
"""
|
||||
return self.GetMinimum()
|
||||
|
||||
@minimum.setter
|
||||
def minimum(self, minimum: float) -> None:
|
||||
"""Set the minimum value for the generated noise.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
minimum : float
|
||||
The minimum value for the generated noise.
|
||||
|
||||
"""
|
||||
self.SetMinimum(minimum)
|
||||
|
||||
@property
|
||||
def maximum(self) -> float:
|
||||
"""Get the maximum value for the generated noise.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The maximum value for the generated noise.
|
||||
|
||||
"""
|
||||
return self.GetMaximum()
|
||||
|
||||
@maximum.setter
|
||||
def maximum(self, maximum: float) -> None:
|
||||
"""Set the maximum value for the generated noise.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
maximum : float
|
||||
The maximum value for the generated noise.
|
||||
|
||||
"""
|
||||
self.SetMaximum(maximum)
|
||||
|
||||
def seed(self, value: int) -> None:
|
||||
"""Seed the random number generator with a value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : int
|
||||
The seed value for the random number generator to use.
|
||||
|
||||
"""
|
||||
_vtk.vtkMath().RandomSeed(value)
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""Get the output image as a ImageData.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
The output image.
|
||||
|
||||
"""
|
||||
self.Update()
|
||||
return wrap(self.GetOutput())
|
||||
|
||||
|
||||
class ImageSinusoidSource(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkImageSinusoidSource):
|
||||
"""Create an image of a sinusoid.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
direction : tuple
|
||||
The direction vector which determines the sinusoidal orientation.
|
||||
|
||||
period : float
|
||||
The period of the sinusoid in pixel.
|
||||
|
||||
phase : tuple
|
||||
The phase of the sinusoid in pixel.
|
||||
|
||||
amplitude : float
|
||||
The magnitude of the sinusoid.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an image of a sinusoid.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> source = pv.ImageSinusoidSource(
|
||||
... whole_extent=(0, 200, 0, 200, 0, 0),
|
||||
... period=20.0,
|
||||
... phase=0.0,
|
||||
... amplitude=255,
|
||||
... direction=(1.0, 0.0, 0.0),
|
||||
... )
|
||||
>>> source.output.plot(cpos='xy')
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def __init__( # noqa: PLR0917
|
||||
self,
|
||||
whole_extent=None,
|
||||
direction=None,
|
||||
period=None,
|
||||
phase=None,
|
||||
amplitude=None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if whole_extent is not None:
|
||||
self.whole_extent = whole_extent
|
||||
if direction is not None:
|
||||
self.direction = direction
|
||||
if period is not None:
|
||||
self.period = period
|
||||
if phase is not None:
|
||||
self.phase = phase
|
||||
if amplitude is not None:
|
||||
self.amplitude = amplitude
|
||||
|
||||
@property
|
||||
def whole_extent(self) -> Sequence[int]:
|
||||
"""Get extent of the whole output image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
return self._whole_extent
|
||||
|
||||
@whole_extent.setter
|
||||
def whole_extent(self, whole_extent: Sequence[int]) -> None:
|
||||
"""Set extent of the whole output image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
self._whole_extent = whole_extent
|
||||
self.SetWholeExtent(
|
||||
whole_extent[0],
|
||||
whole_extent[1],
|
||||
whole_extent[2],
|
||||
whole_extent[3],
|
||||
whole_extent[4],
|
||||
whole_extent[5],
|
||||
)
|
||||
|
||||
@property
|
||||
def direction(self) -> Sequence[float]:
|
||||
"""Get the direction of the sinusoid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[float]
|
||||
The direction of the sinusoid.
|
||||
|
||||
"""
|
||||
return self.GetDirection()
|
||||
|
||||
@direction.setter
|
||||
def direction(self, direction: Sequence[float]) -> None:
|
||||
"""Set the direction of the sinusoid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
direction : sequence[float]
|
||||
The direction of the sinusoid.
|
||||
|
||||
"""
|
||||
self.SetDirection(direction) # type: ignore[call-overload]
|
||||
|
||||
@property
|
||||
def period(self) -> float:
|
||||
"""Get the period of the sinusoid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The period of the sinusoid in pixel.
|
||||
|
||||
"""
|
||||
return self.GetPeriod()
|
||||
|
||||
@period.setter
|
||||
def period(self, period: float) -> None:
|
||||
"""Set the period of the sinusoid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
period : float
|
||||
The period of the sinusoid in pixel.
|
||||
|
||||
"""
|
||||
self.SetPeriod(period)
|
||||
|
||||
@property
|
||||
def phase(self) -> Sequence[float]:
|
||||
"""Get the phase of the sinusoid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[float]
|
||||
The phase of the sinusoid in pixel.
|
||||
|
||||
"""
|
||||
return self.GetPhase() # type: ignore[return-value]
|
||||
|
||||
@phase.setter
|
||||
def phase(self, phase: Sequence[float]) -> None:
|
||||
"""Set the phase of the sinusoid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
phase : sequence[float]
|
||||
The phase of the sinusoid in pixel.
|
||||
|
||||
"""
|
||||
self.SetPhase(phase) # type: ignore[arg-type]
|
||||
|
||||
@property
|
||||
def amplitude(self) -> float:
|
||||
"""Get the magnitude of the sinusoid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The magnitude of the sinusoid.
|
||||
|
||||
"""
|
||||
return self.GetAmplitude()
|
||||
|
||||
@amplitude.setter
|
||||
def amplitude(self, amplitude: float) -> None:
|
||||
"""Set the magnitude of the sinusoid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
amplitude : float
|
||||
The magnitude of the sinusoid.
|
||||
|
||||
"""
|
||||
self.SetAmplitude(amplitude)
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""Get the output image as a ImageData.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
The output image.
|
||||
|
||||
"""
|
||||
self.Update()
|
||||
return wrap(self.GetOutput())
|
||||
|
||||
|
||||
class ImageGaussianSource(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkImageGaussianSource):
|
||||
"""Create a binary image with Gaussian pixel values.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
center : sequence[float]
|
||||
The center of the gaussian.
|
||||
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
maximum : float
|
||||
The maximum value of the gaussian.
|
||||
|
||||
std : sequence[float]
|
||||
The standard deviation of the gaussian.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an image of Gaussian pixel values.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> source = pv.ImageGaussianSource(
|
||||
... center=(100, 100, 0),
|
||||
... whole_extent=(0, 200, 0, 200, 0, 0),
|
||||
... maximum=255,
|
||||
... std=100.0,
|
||||
... )
|
||||
>>> source.output.plot(cpos='xy')
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def __init__( # noqa: PLR0917
|
||||
self, center=None, whole_extent=None, maximum=None, std=None
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if center is not None:
|
||||
self.center = center
|
||||
if whole_extent is not None:
|
||||
self.whole_extent = whole_extent
|
||||
if maximum is not None:
|
||||
self.maximum = maximum
|
||||
if std is not None:
|
||||
self.std = std
|
||||
|
||||
@property
|
||||
def center(self) -> tuple[float, float, float]:
|
||||
"""Get the center of the gaussian.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[float, float, float]
|
||||
The center of the gaussian.
|
||||
|
||||
"""
|
||||
return self.GetCenter()
|
||||
|
||||
@center.setter
|
||||
def center(self, center: Sequence[float]) -> None:
|
||||
"""Set the center of the gaussian.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
center : sequence[float]
|
||||
The center of the gaussian.
|
||||
|
||||
"""
|
||||
self.SetCenter(center)
|
||||
|
||||
@property
|
||||
def whole_extent(self) -> Sequence[int]:
|
||||
"""Get extent of the whole output image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
return self._whole_extent
|
||||
|
||||
@whole_extent.setter
|
||||
def whole_extent(self, whole_extent: Sequence[int]) -> None:
|
||||
"""Set extent of the whole output image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
whole_extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
self._whole_extent = whole_extent
|
||||
self.SetWholeExtent(
|
||||
whole_extent[0],
|
||||
whole_extent[1],
|
||||
whole_extent[2],
|
||||
whole_extent[3],
|
||||
whole_extent[4],
|
||||
whole_extent[5],
|
||||
)
|
||||
|
||||
@property
|
||||
def maximum(self) -> float:
|
||||
"""Get the maximum value of the gaussian.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The maximum value of the gaussian.
|
||||
|
||||
"""
|
||||
return self.GetMaximum()
|
||||
|
||||
@maximum.setter
|
||||
def maximum(self, maximum: float) -> None:
|
||||
"""Set the maximum value of the gaussian.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
maximum : float
|
||||
The maximum value of the gaussian.
|
||||
|
||||
"""
|
||||
self.SetMaximum(maximum)
|
||||
|
||||
@property
|
||||
def std(self) -> float:
|
||||
"""Get the standard deviation of the gaussian.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The standard deviation of the gaussian.
|
||||
|
||||
"""
|
||||
return self.GetStandardDeviation()
|
||||
|
||||
@std.setter
|
||||
def std(self, std: float) -> None:
|
||||
"""Set the standard deviation of the gaussian.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
std : float
|
||||
The standard deviation of the gaussian.
|
||||
|
||||
"""
|
||||
self.SetStandardDeviation(std)
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""Get the output image as a ImageData.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
The output image.
|
||||
|
||||
"""
|
||||
self.Update()
|
||||
return wrap(self.GetOutput())
|
||||
|
||||
|
||||
class ImageGridSource(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkImageGridSource):
|
||||
"""Create an image of a grid.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
origin : sequence[float]
|
||||
The origin of the grid.
|
||||
|
||||
extent : sequence[int]
|
||||
The extent of the whole output image, Default: (0,255,0,255,0,0).
|
||||
|
||||
spacing : tuple
|
||||
The pixel spacing.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an image of a grid.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> source = pv.ImageGridSource(
|
||||
... extent=(0, 20, 0, 20, 0, 0),
|
||||
... spacing=(1, 1, 1),
|
||||
... )
|
||||
>>> source.output.plot(cpos='xy')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, origin=None, extent=None, spacing=None) -> None:
|
||||
super().__init__()
|
||||
if origin is not None:
|
||||
self.origin = origin
|
||||
if extent is not None:
|
||||
self.extent = extent
|
||||
if spacing is not None:
|
||||
self.spacing = spacing
|
||||
|
||||
@property
|
||||
def origin(self) -> Sequence[float]:
|
||||
"""Get the origin of the data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[float]
|
||||
The origin of the grid.
|
||||
|
||||
"""
|
||||
return self.GetGridOrigin()
|
||||
|
||||
@origin.setter
|
||||
def origin(self, origin: Sequence[float]) -> None:
|
||||
"""Set the origin of the data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
origin : sequence[float]
|
||||
The origin of the grid.
|
||||
|
||||
"""
|
||||
self.SetGridOrigin(origin) # type: ignore[arg-type]
|
||||
|
||||
@property
|
||||
def extent(self) -> Sequence[int]:
|
||||
"""Get extent of the whole output image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
return self.GetDataExtent()
|
||||
|
||||
@extent.setter
|
||||
def extent(self, extent: Sequence[int]) -> None:
|
||||
"""Set extent of the whole output image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
extent : sequence[int]
|
||||
The extent of the whole output image.
|
||||
|
||||
"""
|
||||
self.SetDataExtent(extent)
|
||||
|
||||
@property
|
||||
def spacing(self) -> Sequence[float]:
|
||||
"""Get the spacing of the grid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sequence[float]
|
||||
The pixel spacing.
|
||||
|
||||
"""
|
||||
return self.GetDataSpacing()
|
||||
|
||||
@spacing.setter
|
||||
def spacing(self, spacing: Sequence[float]) -> None:
|
||||
"""Set the spacing of the grid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
spacing : sequence[float]
|
||||
The pixel spacing.
|
||||
|
||||
"""
|
||||
self.SetDataSpacing(spacing)
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""Get the output image as a ImageData.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
The output image.
|
||||
|
||||
"""
|
||||
self.Update()
|
||||
return wrap(self.GetOutput())
|
||||
@@ -0,0 +1,479 @@
|
||||
"""Miscellaneous core utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABCMeta
|
||||
from collections.abc import Sequence
|
||||
import enum
|
||||
from functools import cache
|
||||
import importlib
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from typing_extensions import Self
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from pyvista._typing_core import ArrayLike
|
||||
from pyvista._typing_core import NumpyArray
|
||||
from pyvista._typing_core import VectorLike
|
||||
|
||||
_T = TypeVar('_T')
|
||||
|
||||
T = TypeVar('T', bound='AnnotatedIntEnum')
|
||||
|
||||
|
||||
def assert_empty_kwargs(**kwargs) -> bool:
|
||||
"""Assert that all keyword arguments have been used (internal helper).
|
||||
|
||||
If any keyword arguments are passed, a ``TypeError`` is raised.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
**kwargs : dict
|
||||
Keyword arguments passed to the function.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` when successful.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If any keyword arguments are passed, a ``TypeError`` is raised.
|
||||
|
||||
"""
|
||||
n = len(kwargs)
|
||||
if n == 0:
|
||||
return True
|
||||
caller = sys._getframe(1).f_code.co_name
|
||||
keys = list(kwargs.keys())
|
||||
bad_arguments = ', '.join([f'"{key}"' for key in keys])
|
||||
grammar = 'is an invalid keyword argument' if n == 1 else 'are invalid keyword arguments'
|
||||
message = f'{bad_arguments} {grammar} for `{caller}`'
|
||||
raise TypeError(message)
|
||||
|
||||
|
||||
def check_valid_vector(point: VectorLike[float], name: str = '') -> None:
|
||||
"""Check if a vector contains three components.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
point : VectorLike[float]
|
||||
Input vector to check. Must be an iterable with exactly three components.
|
||||
name : str, optional
|
||||
Name to use in the error messages. If not provided, "Vector" will be used.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If the input is not an iterable.
|
||||
ValueError
|
||||
If the input does not have exactly three components.
|
||||
|
||||
"""
|
||||
if not isinstance(point, (Sequence, np.ndarray)):
|
||||
msg = f'{name} must be a length three iterable of floats.'
|
||||
raise TypeError(msg)
|
||||
if len(point) != 3:
|
||||
if name == '':
|
||||
name = 'Vector'
|
||||
msg = f'{name} must be a length three iterable of floats.'
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def abstract_class(cls_): # noqa: ANN001, ANN201 # numpydoc ignore=RT01
|
||||
"""Decorate a class, overriding __new__.
|
||||
|
||||
Preventing a class from being instantiated similar to abc.ABCMeta
|
||||
but does not require an abstract method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cls_ : type
|
||||
The class to be decorated as abstract.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **kwargs): # noqa: ANN001, ANN202, ARG001, N807
|
||||
if cls is cls_:
|
||||
msg = f'{cls.__name__} is an abstract class and may not be instantiated.'
|
||||
raise TypeError(msg)
|
||||
return super(cls_, cls).__new__(cls)
|
||||
|
||||
cls_.__new__ = __new__
|
||||
return cls_
|
||||
|
||||
|
||||
class AnnotatedIntEnum(int, enum.Enum):
|
||||
"""Annotated enum type."""
|
||||
|
||||
annotation: str
|
||||
|
||||
def __new__(cls, value: int, annotation: str) -> Self:
|
||||
"""Initialize."""
|
||||
obj = int.__new__(cls, value)
|
||||
obj._value_ = value
|
||||
obj.annotation = annotation
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, input_str: str) -> Self:
|
||||
"""Create an enum member from a string.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_str : str
|
||||
The string representation of the annotation for the enum member.
|
||||
|
||||
Returns
|
||||
-------
|
||||
AnnotatedIntEnum
|
||||
The enum member with the specified annotation.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If there is no enum member with the specified annotation.
|
||||
|
||||
"""
|
||||
for value in cls:
|
||||
if value.annotation.lower() == input_str.lower():
|
||||
return value
|
||||
msg = f'{cls.__name__} has no value matching {input_str}'
|
||||
raise ValueError(msg)
|
||||
|
||||
@classmethod
|
||||
def from_any(cls, value: AnnotatedIntEnum | int | str) -> Self:
|
||||
"""Create an enum member from a string, int, etc.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : int | str | AnnotatedIntEnum
|
||||
The value used to determine the corresponding enum member.
|
||||
|
||||
Returns
|
||||
-------
|
||||
AnnotatedIntEnum
|
||||
The enum member matching the specified value.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If there is no enum member matching the specified value.
|
||||
|
||||
"""
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
elif isinstance(value, int):
|
||||
return cls(value) # type: ignore[call-arg]
|
||||
elif isinstance(value, str):
|
||||
return cls.from_str(value)
|
||||
else:
|
||||
msg = f'Invalid type {type(value)} for class {cls.__name__}.' # type: ignore[unreachable]
|
||||
raise TypeError(msg)
|
||||
|
||||
|
||||
@cache
|
||||
def has_module(module_name: str) -> bool:
|
||||
"""Return if a module can be imported.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module_name : str
|
||||
Name of the module to check.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` if the module can be imported, otherwise ``False``.
|
||||
|
||||
"""
|
||||
module_spec = importlib.util.find_spec(module_name)
|
||||
return module_spec is not None
|
||||
|
||||
|
||||
def try_callback(func, *args) -> None: # noqa: ANN001
|
||||
"""Wrap a given callback in a try statement.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
Callable object.
|
||||
|
||||
*args
|
||||
Any arguments.
|
||||
|
||||
"""
|
||||
try:
|
||||
func(*args)
|
||||
except Exception: # noqa: BLE001 # pragma: no cover
|
||||
etype, exc, tb = sys.exc_info()
|
||||
stack = traceback.extract_tb(tb)[1:]
|
||||
formatted_exception = 'Encountered issue in callback (most recent call last):\n' + ''.join(
|
||||
traceback.format_list(stack) + traceback.format_exception_only(etype, exc),
|
||||
).rstrip('\n')
|
||||
warnings.warn(formatted_exception)
|
||||
|
||||
|
||||
def threaded(fn): # noqa: ANN001, ANN201
|
||||
"""Call a function using a thread.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fn : callable
|
||||
Callable object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
function
|
||||
Wrapped function.
|
||||
|
||||
"""
|
||||
|
||||
def wrapper(*args, **kwargs): # noqa: ANN202
|
||||
thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class conditional_decorator: # noqa: N801
|
||||
"""Conditional decorator for methods.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dec : callable
|
||||
The decorator to be applied conditionally.
|
||||
condition : bool
|
||||
Condition to match. If ``True``, the decorator is applied. If
|
||||
``False``, the function is returned unchanged.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, dec, condition) -> None: # noqa: ANN001
|
||||
"""Initialize."""
|
||||
self.decorator = dec
|
||||
self.condition = condition
|
||||
|
||||
def __call__(self, func): # noqa: ANN001, ANN204
|
||||
"""Call the decorated function if condition is matched."""
|
||||
if not self.condition:
|
||||
# Return the function unchanged, not decorated.
|
||||
return func
|
||||
return self.decorator(func)
|
||||
|
||||
|
||||
def _check_range(value: float, rng: Sequence[float], parm_name: str) -> None:
|
||||
"""Check if a parameter is within a range."""
|
||||
if value < rng[0] or value > rng[1]:
|
||||
msg = (
|
||||
f'The value {float(value)} for `{parm_name}` is outside the '
|
||||
f'acceptable range {tuple(rng)}.'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
class _AutoFreezeMeta(type):
|
||||
"""Metaclass to automatically freeze a class when called."""
|
||||
|
||||
def __call__(cls: type[_T], *args, **kwargs) -> _T:
|
||||
obj = super().__call__(*args, **kwargs) # type: ignore[misc]
|
||||
obj._no_new_attributes(cls)
|
||||
return obj
|
||||
|
||||
|
||||
class _AutoFreezeABCMeta(_AutoFreezeMeta, ABCMeta):
|
||||
"""Metaclass to combine automatic attribute freezing with ABC support."""
|
||||
|
||||
|
||||
class _NoNewAttrMixin(metaclass=_AutoFreezeABCMeta):
|
||||
"""Mixin to prevent adding new attributes.
|
||||
|
||||
This class is mainly used to prevent users from setting the wrong attributes on an
|
||||
object. It freezes the attributes when called and prevents setting new ones via
|
||||
"normal" methods like ``obj.foo = 42``.
|
||||
"""
|
||||
|
||||
def _no_new_attributes(self, this_class: type) -> None:
|
||||
"""Prevent setting additional attributes."""
|
||||
object.__setattr__(self, '__frozen', True)
|
||||
object.__setattr__(self, '__frozen_by_class', this_class)
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> None:
|
||||
"""Prevent adding new attributes to classes using "normal" methods."""
|
||||
if not key.startswith('_'):
|
||||
# Check if this class froze itself. Any frozen state already set by parent classes,
|
||||
# e.g. by calling super().__init__(), will be ignored. This allows subclasses to set
|
||||
# attributes during init without being affect by a parent class init.
|
||||
frozen = self.__dict__.get('__frozen', False)
|
||||
frozen_by = self.__dict__.get('__frozen_by_class', None)
|
||||
if (
|
||||
frozen
|
||||
and frozen_by is type(self)
|
||||
and not (key in type(self).__dict__ or hasattr(self, key))
|
||||
):
|
||||
from pyvista import PyVistaAttributeError # noqa: PLC0415
|
||||
|
||||
msg = (
|
||||
f'Attribute {key!r} does not exist and cannot be added to class '
|
||||
f'{self.__class__.__name__!r}\nUse `pv.set_new_attribute` to set new '
|
||||
f'attributes or consider setting a private variable (with `_` prefix) instead.'
|
||||
)
|
||||
raise PyVistaAttributeError(msg)
|
||||
object.__setattr__(self, key, value)
|
||||
|
||||
|
||||
def set_new_attribute(obj: object, name: str, value: Any) -> None:
|
||||
"""Set a new attribute for this object.
|
||||
|
||||
Python allows arbitrarily setting new attributes on objects at any time,
|
||||
but PyVista's classes do not allow this. If an attribute is not part of
|
||||
PyVista's API, an ``AttributeError`` is normally raised when attempting
|
||||
to set it.
|
||||
|
||||
Use :func:`set_new_attribute` to override this and set a new attribute anyway.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Set a new custom attribute on a mesh.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.PolyData()
|
||||
>>> pv.set_new_attribute(mesh, 'foo', 42)
|
||||
>>> mesh.foo
|
||||
42
|
||||
|
||||
.. versionadded:: 0.46
|
||||
|
||||
"""
|
||||
if hasattr(obj, name):
|
||||
from pyvista import PyVistaAttributeError # noqa: PLC0415
|
||||
|
||||
msg = (
|
||||
f'Attribute {name!r} already exists. '
|
||||
'`set_new_attribute` can only be used for setting NEW attributes.'
|
||||
)
|
||||
raise PyVistaAttributeError(msg)
|
||||
object.__setattr__(obj, name, value)
|
||||
|
||||
|
||||
def _reciprocal(
|
||||
x: ArrayLike[float], tol: float = 1e-8, value_if_division_by_zero: float = 0.0
|
||||
) -> NumpyArray[float]:
|
||||
"""Compute the element-wise reciprocal and avoid division by zero.
|
||||
|
||||
The reciprocal of elements with an absolute value less than a
|
||||
specified tolerance has the value specified by ``default_if_div_by_zero``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
Input array.
|
||||
tol : float
|
||||
Tolerance value. Values smaller than ``tol`` have a reciprocal of zero.
|
||||
value_if_division_by_zero : float
|
||||
Default value given to values less than ``tol``, i.e. the value given if division
|
||||
by zero is detected.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Element-wise reciprocal of the input.
|
||||
|
||||
"""
|
||||
x = np.array(x)
|
||||
x = x if np.issubdtype(x.dtype, np.floating) else x.astype(float)
|
||||
zero = np.abs(x) < tol
|
||||
x[~zero] = np.reciprocal(x[~zero])
|
||||
x[zero] = value_if_division_by_zero
|
||||
return x
|
||||
|
||||
|
||||
class _classproperty(property): # noqa: N801
|
||||
"""Read-only class property decorator.
|
||||
|
||||
Use this decaorator as an alternative to chaining `@classmethod`
|
||||
and `@property` which is deprecated.
|
||||
|
||||
See:
|
||||
- https://docs.python.org/library/functions.html#classmethod
|
||||
- https://stackoverflow.com/a/13624858
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista.core.utilities.misc import _classproperty
|
||||
>>> class Foo:
|
||||
... @_classproperty
|
||||
... def bar(cls): ...
|
||||
|
||||
"""
|
||||
|
||||
def __get__(self: property, owner_self: Any, owner_cls: type | None = None) -> Any:
|
||||
return self.fget(owner_cls) # type: ignore[misc]
|
||||
|
||||
|
||||
class _NameMixin:
|
||||
"""Add a 'name' property to a class.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self) -> str: # numpydoc ignore=RT01
|
||||
"""Get or set the unique name identifier used by PyVista."""
|
||||
if not hasattr(self, '_name') or self._name is None:
|
||||
address = (
|
||||
self.GetAddressAsString('')
|
||||
if hasattr(self, 'GetAddressAsString')
|
||||
else hex(id(self))
|
||||
)
|
||||
return f'{type(self).__name__}({address})'
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str) -> None:
|
||||
if not value:
|
||||
msg = 'Name must be truthy.'
|
||||
raise ValueError(msg)
|
||||
object.__setattr__(self, '_name', str(value))
|
||||
|
||||
|
||||
class _BoundsSizeMixin:
|
||||
@property
|
||||
def bounds_size(self) -> tuple[float, float, float]:
|
||||
"""Return the size of each axis of the object's bounding box.
|
||||
|
||||
.. versionadded:: 0.46
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[float, float, float]
|
||||
Size of each x-y-z axis.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Get the size of a cube. The cube has edge lengths af ``(1.0, 1.0, 1.0)``
|
||||
by default.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Cube()
|
||||
>>> mesh.bounds_size
|
||||
(1.0, 1.0, 1.0)
|
||||
|
||||
"""
|
||||
bounds = self.bounds # type: ignore[attr-defined]
|
||||
return (
|
||||
bounds.x_max - bounds.x_min,
|
||||
bounds.y_max - bounds.y_min,
|
||||
bounds.z_max - bounds.z_min,
|
||||
)
|
||||
@@ -0,0 +1,292 @@
|
||||
"""Core error utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from typing import NamedTuple
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
|
||||
def set_error_output_file(filename):
|
||||
"""Set a file to write out the VTK errors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str, Path
|
||||
Path to the file to write VTK errors to.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:vtk:`vtkFileOutputWindow`
|
||||
VTK file output window.
|
||||
:vtk:`vtkOutputWindow`
|
||||
VTK output window.
|
||||
|
||||
"""
|
||||
filename = Path(filename).expanduser().resolve()
|
||||
fileOutputWindow = _vtk.vtkFileOutputWindow()
|
||||
if pyvista.vtk_version_info < (9, 2, 2): # pragma no cover
|
||||
fileOutputWindow.SetFileName(str(filename))
|
||||
else:
|
||||
fileOutputWindow.SetFileName(filename)
|
||||
outputWindow = _vtk.vtkOutputWindow()
|
||||
outputWindow.SetInstance(fileOutputWindow)
|
||||
return fileOutputWindow, outputWindow
|
||||
|
||||
|
||||
class VtkErrorCatcher:
|
||||
"""Context manager to temporarily catch VTK errors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raise_errors : bool, default: False
|
||||
Raise a ``RuntimeError`` when a VTK error is encountered.
|
||||
|
||||
send_to_logging : bool, default: True
|
||||
Determine whether VTK errors raised within the context should
|
||||
also be sent to logging.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Catch VTK errors using the context manager.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> with pv.VtkErrorCatcher() as error_catcher:
|
||||
... sphere = pv.Sphere()
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def __init__(self, raise_errors: bool = False, send_to_logging: bool = True) -> None: # noqa: FBT001, FBT002
|
||||
"""Initialize context manager."""
|
||||
self.raise_errors = raise_errors
|
||||
self.send_to_logging = send_to_logging
|
||||
|
||||
def __enter__(self) -> None:
|
||||
"""Observe VTK string output window for errors."""
|
||||
error_output = _vtk.vtkStringOutputWindow()
|
||||
error_win = _vtk.vtkOutputWindow()
|
||||
self._error_output_orig = error_win.GetInstance()
|
||||
error_win.SetInstance(error_output)
|
||||
obs = Observer(log=self.send_to_logging, store_history=True)
|
||||
obs.observe(error_output)
|
||||
self._observer = obs
|
||||
|
||||
def __exit__(self, *args):
|
||||
"""Stop observing VTK string output window."""
|
||||
error_win = _vtk.vtkOutputWindow()
|
||||
error_win.SetInstance(self._error_output_orig)
|
||||
self.events = self._observer.event_history
|
||||
if self.raise_errors and self.events:
|
||||
errors = [RuntimeError(f'{e.kind}: {e.alert}', e.path, e.address) for e in self.events]
|
||||
raise RuntimeError(errors)
|
||||
|
||||
|
||||
class VtkEvent(NamedTuple):
|
||||
"""Named tuple to store VTK event information."""
|
||||
|
||||
kind: str
|
||||
path: str
|
||||
address: str
|
||||
alert: str
|
||||
|
||||
|
||||
class Observer(_NoNewAttrMixin):
|
||||
"""A standard class for observing VTK objects."""
|
||||
|
||||
@_deprecate_positional_args(allowed=['event_type'])
|
||||
def __init__(
|
||||
self,
|
||||
event_type='ErrorEvent',
|
||||
log: bool = True, # noqa: FBT001, FBT002
|
||||
store_history: bool = False, # noqa: FBT001, FBT002
|
||||
) -> None:
|
||||
"""Initialize observer."""
|
||||
self.__event_occurred = False
|
||||
self.__message = None
|
||||
self.__message_etc = None
|
||||
self.CallDataType = 'string0'
|
||||
self.__observing = False
|
||||
self.event_type = event_type
|
||||
self.__log = log
|
||||
|
||||
self.store_history = store_history
|
||||
self.event_history: list[VtkEvent] = []
|
||||
|
||||
@staticmethod
|
||||
def parse_message(message): # numpydoc ignore=RT01
|
||||
"""Parse the given message."""
|
||||
# Message format
|
||||
regex = re.compile(r'([A-Z]+):\sIn\s(.+),\sline\s.+\n\w+\s\((.+)\):\s(.+)')
|
||||
try:
|
||||
kind, path, address, alert = regex.findall(message)[0]
|
||||
except Exception: # noqa: BLE001
|
||||
return '', '', '', message
|
||||
else:
|
||||
return kind, path, address, alert
|
||||
|
||||
def log_message(self, kind, alert) -> None:
|
||||
"""Parse different event types and passes them to logging."""
|
||||
if kind == 'ERROR':
|
||||
logging.error(alert) # noqa: LOG015
|
||||
else:
|
||||
logging.warning(alert) # noqa: LOG015
|
||||
|
||||
def __call__(self, _obj, _event, message) -> None:
|
||||
"""Declare standard call function for the observer.
|
||||
|
||||
On an event occurrence, this function executes.
|
||||
|
||||
"""
|
||||
try:
|
||||
self.__event_occurred = True
|
||||
self.__message_etc = message
|
||||
kind, path, address, alert = self.parse_message(message)
|
||||
self.__message = alert
|
||||
if self.store_history:
|
||||
self.event_history.append(VtkEvent(kind, path, address, alert))
|
||||
if self.__log:
|
||||
self.log_message(kind, alert)
|
||||
except Exception: # noqa: BLE001 # pragma: no cover
|
||||
try:
|
||||
if len(message) > 120:
|
||||
message = f'{message[:100]!r} ... ({len(message)} characters)'
|
||||
else:
|
||||
message = repr(message)
|
||||
print(
|
||||
f'PyVista error in handling VTK error message:\n{message}',
|
||||
file=sys.__stdout__,
|
||||
)
|
||||
traceback.print_tb(sys.last_traceback, file=sys.__stderr__)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
|
||||
def has_event_occurred(self): # numpydoc ignore=RT01
|
||||
"""Ask self if an error has occurred since last queried.
|
||||
|
||||
This resets the observer's status.
|
||||
|
||||
"""
|
||||
occ = self.__event_occurred
|
||||
self.__event_occurred = False
|
||||
return occ
|
||||
|
||||
@_deprecate_positional_args
|
||||
def get_message(self, etc: bool = False): # noqa: FBT001, FBT002
|
||||
"""Get the last set error message.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The last set error message.
|
||||
|
||||
"""
|
||||
if etc:
|
||||
return self.__message_etc
|
||||
return self.__message
|
||||
|
||||
def observe(self, algorithm):
|
||||
"""Make this an observer of an algorithm."""
|
||||
if self.__observing:
|
||||
msg = 'This error observer is already observing an algorithm.'
|
||||
raise RuntimeError(msg)
|
||||
if hasattr(algorithm, 'GetExecutive') and algorithm.GetExecutive() is not None:
|
||||
algorithm.GetExecutive().AddObserver(self.event_type, self)
|
||||
algorithm.AddObserver(self.event_type, self)
|
||||
self.__observing = True
|
||||
|
||||
|
||||
def send_errors_to_logging(): # numpydoc ignore=RT01
|
||||
"""Send all VTK error/warning messages to Python's logging module."""
|
||||
error_output = _vtk.vtkStringOutputWindow()
|
||||
error_win = _vtk.vtkOutputWindow()
|
||||
error_win.SetInstance(error_output)
|
||||
obs = Observer()
|
||||
return obs.observe(error_output)
|
||||
|
||||
|
||||
class ProgressMonitor(_NoNewAttrMixin):
|
||||
"""A standard class for monitoring the progress of a VTK algorithm.
|
||||
|
||||
This must be use in a ``with`` context and it will block keyboard
|
||||
interrupts from happening until the exit event as interrupts will crash
|
||||
the kernel if the VTK algorithm is still executing.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
algorithm
|
||||
VTK algorithm or filter.
|
||||
|
||||
message : str, default: ""
|
||||
Message to display in the progress bar.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, algorithm, message=''):
|
||||
"""Initialize observer."""
|
||||
if not importlib.util.find_spec('tqdm'):
|
||||
msg = 'Please install `tqdm` to monitor algorithms.'
|
||||
raise ImportError(msg)
|
||||
self.event_type = _vtk.vtkCommand.ProgressEvent
|
||||
self.progress = 0.0
|
||||
self._last_progress = self.progress
|
||||
self.algorithm = algorithm
|
||||
self.message = message
|
||||
self._interrupt_signal_received = False
|
||||
self._old_progress = 0
|
||||
self._old_handler = None
|
||||
self._progress_bar = None
|
||||
|
||||
def handler(self, sig, frame) -> None:
|
||||
"""Pass signal to custom interrupt handler."""
|
||||
self._interrupt_signal_received = (sig, frame) # type: ignore[assignment]
|
||||
logging.debug('SIGINT received. Delaying KeyboardInterrupt until VTK algorithm finishes.') # noqa: LOG015
|
||||
|
||||
def __call__(self, obj, *args) -> None: # noqa: ARG002
|
||||
"""Call progress update callback.
|
||||
|
||||
On an event occurrence, this function executes.
|
||||
"""
|
||||
if self._interrupt_signal_received:
|
||||
obj.AbortExecuteOn()
|
||||
else:
|
||||
progress = obj.GetProgress()
|
||||
step = progress - self._old_progress
|
||||
self._progress_bar.update(step) # type: ignore[union-attr]
|
||||
self._old_progress = progress
|
||||
|
||||
def __enter__(self):
|
||||
"""Enter event for ``with`` context."""
|
||||
from tqdm import tqdm # noqa: PLC0415
|
||||
|
||||
# check if in main thread
|
||||
if threading.current_thread().__class__.__name__ == '_MainThread':
|
||||
self._old_handler = signal.signal(signal.SIGINT, self.handler)
|
||||
self._progress_bar = tqdm(
|
||||
total=1,
|
||||
leave=True,
|
||||
bar_format='{l_bar}{bar}[{elapsed}<{remaining}]',
|
||||
)
|
||||
self._progress_bar.set_description(self.message)
|
||||
self.algorithm.AddObserver(self.event_type, self)
|
||||
return self._progress_bar
|
||||
|
||||
def __exit__(self, *args) -> None:
|
||||
"""Exit event for ``with`` context."""
|
||||
self._progress_bar.total = 1 # type: ignore[union-attr]
|
||||
self._progress_bar.refresh() # type: ignore[union-attr]
|
||||
self._progress_bar.close() # type: ignore[union-attr]
|
||||
self.algorithm.RemoveObservers(self.event_type)
|
||||
if threading.current_thread().__class__.__name__ == '_MainThread':
|
||||
signal.signal(signal.SIGINT, self._old_handler)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,874 @@
|
||||
"""Points related utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
from typing import overload
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _validation
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista import PolyData
|
||||
from pyvista.core._typing_core import MatrixLike
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
from pyvista.core._typing_core import VectorLike
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['points'])
|
||||
def vtk_points( # noqa: PLR0917
|
||||
points: VectorLike[float] | MatrixLike[float],
|
||||
deep: bool = True, # noqa: FBT001, FBT002
|
||||
force_float: bool = False, # noqa: FBT001, FBT002
|
||||
allow_empty: bool = True, # noqa: FBT001, FBT002
|
||||
) -> _vtk.vtkPoints:
|
||||
"""Convert numpy array or array-like to a :vtk:`vtkPoints` object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : numpy.ndarray or sequence
|
||||
Points to convert. Should be 1 or 2 dimensional. Accepts a
|
||||
single point or several points.
|
||||
|
||||
deep : bool, default: True
|
||||
Perform a deep copy of the array. Only applicable if
|
||||
``points`` is a :class:`numpy.ndarray`.
|
||||
|
||||
force_float : bool, default: False
|
||||
Casts the datatype to ``float32`` if points datatype is
|
||||
non-float. Set this to ``False`` to allow non-float types,
|
||||
though this may lead to truncation of intermediate floats
|
||||
when transforming datasets.
|
||||
|
||||
allow_empty : bool, default: True
|
||||
Allow ``points`` to be an empty array. If ``False``, points
|
||||
must be strictly one- or two-dimensional.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
Returns
|
||||
-------
|
||||
:vtk:`vtkPoints`
|
||||
The :vtk:`vtkPoints` object.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> points = np.random.default_rng().random((10, 3))
|
||||
>>> vpoints = pv.vtk_points(points)
|
||||
>>> vpoints # doctest:+SKIP
|
||||
(vtkmodules.vtkCommonCore.vtkPoints)0x7f0c2e26af40
|
||||
|
||||
"""
|
||||
try:
|
||||
points_ = _validation.validate_arrayNx3(points, name='points')
|
||||
except ValueError as e:
|
||||
if 'points has shape (0,)' in repr(e) and allow_empty:
|
||||
points_ = np.empty(shape=(0, 3), dtype=np.array(points).dtype)
|
||||
else:
|
||||
raise
|
||||
|
||||
if force_float and not np.issubdtype(points_.dtype, np.floating):
|
||||
warnings.warn(
|
||||
'Points is not a float type. This can cause issues when '
|
||||
'transforming or applying filters. Casting to '
|
||||
'``np.float32``. Disable this by passing '
|
||||
'``force_float=False``.',
|
||||
)
|
||||
points_ = points_.astype(np.float32)
|
||||
|
||||
# use the underlying vtk data if present to avoid memory leaks
|
||||
if not deep and isinstance(points_, pyvista.pyvista_ndarray) and points_.VTKObject is not None:
|
||||
vtk_object = points_.VTKObject
|
||||
|
||||
# we can only use the underlying data if `points` is not a slice of
|
||||
# the VTK data object
|
||||
if vtk_object.GetSize() == points_.size:
|
||||
vtkpts = _vtk.vtkPoints()
|
||||
vtkpts.SetData(points_.VTKObject)
|
||||
return vtkpts
|
||||
else:
|
||||
deep = True
|
||||
|
||||
# points must be contiguous
|
||||
points_ = np.require(points_, requirements=['C'])
|
||||
vtkpts = _vtk.vtkPoints()
|
||||
vtk_arr = _vtk.numpy_to_vtk(points_, deep=deep)
|
||||
vtkpts.SetData(vtk_arr)
|
||||
|
||||
return vtkpts
|
||||
|
||||
|
||||
def line_segments_from_points(points: VectorLike[float] | MatrixLike[float]) -> PolyData:
|
||||
"""Generate non-connected line segments from points.
|
||||
|
||||
Assumes points are ordered as line segments and an even number of
|
||||
points.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : array_like[float]
|
||||
Points representing line segments. An even number must be
|
||||
given as every two vertices represent a single line
|
||||
segment. For example, two line segments would be represented
|
||||
as ``np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0], [1, 1, 0]])``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
PolyData with lines and cells.
|
||||
|
||||
Examples
|
||||
--------
|
||||
This example plots two line segments at right angles to each other.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> points = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0], [1, 1, 0]])
|
||||
>>> lines = pv.line_segments_from_points(points)
|
||||
>>> lines.plot()
|
||||
|
||||
"""
|
||||
if len(points) % 2 != 0:
|
||||
msg = 'An even number of points must be given to define each segment.'
|
||||
raise ValueError(msg)
|
||||
# Assuming ordered points, create array defining line order
|
||||
n_points = len(points)
|
||||
n_lines = n_points // 2
|
||||
lines = np.c_[
|
||||
2 * np.ones(n_lines, np.int_),
|
||||
np.arange(0, n_points - 1, step=2),
|
||||
np.arange(1, n_points + 1, step=2),
|
||||
]
|
||||
poly = pyvista.PolyData()
|
||||
poly.points = points
|
||||
poly.lines = lines
|
||||
return poly
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['points'])
|
||||
def lines_from_points(
|
||||
points: VectorLike[float] | MatrixLike[float],
|
||||
close: bool = False, # noqa: FBT001, FBT002
|
||||
) -> PolyData:
|
||||
"""Make a connected line set given an array of points.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : array_like[float]
|
||||
Points representing the vertices of the connected
|
||||
segments. For example, two line segments would be represented
|
||||
as ``np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0]])``.
|
||||
|
||||
close : bool, default: False
|
||||
If ``True``, close the line segments into a loop.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
PolyData with lines and cells.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> points = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
|
||||
>>> poly = pv.lines_from_points(points)
|
||||
>>> poly.plot(line_width=5)
|
||||
|
||||
"""
|
||||
poly = pyvista.PolyData()
|
||||
poly.points = points
|
||||
cells = np.full((len(points) - 1, 3), 2, dtype=np.int_)
|
||||
cells[:, 1] = np.arange(0, len(points) - 1, dtype=np.int_)
|
||||
cells[:, 2] = np.arange(1, len(points), dtype=np.int_)
|
||||
if close:
|
||||
cells = np.append(cells, [[2, len(points) - 1, 0]], axis=0)
|
||||
poly.lines = cells
|
||||
return poly
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['points'])
|
||||
def fit_plane_to_points( # noqa: PLR0917
|
||||
points: MatrixLike[float],
|
||||
return_meta: bool = False, # noqa: FBT001, FBT002
|
||||
resolution: int = 10,
|
||||
init_normal: VectorLike[float] | None = None,
|
||||
) -> PolyData | tuple[PolyData, float, NumpyArray[float]]:
|
||||
"""Fit a plane to points using its :func:`principal_axes`.
|
||||
|
||||
The plane is automatically sized and oriented to fit the extents of
|
||||
the points.
|
||||
|
||||
.. versionchanged:: 0.42.0
|
||||
The generated plane is now sized and oriented to match the points.
|
||||
|
||||
.. versionchanged:: 0.42.0
|
||||
The center of the plane (returned if ``return_meta=True``) is now
|
||||
computed as the center of the generated plane mesh. In previous
|
||||
versions, the center of the input points was returned.
|
||||
|
||||
.. versionchanged:: 0.45.0
|
||||
The internal method used for fitting the plane has changed. Previously, singular
|
||||
value decomposition (SVD) was used, but eigenvectors are now used instead.
|
||||
See warning below.
|
||||
|
||||
.. warning::
|
||||
The sign of the plane's normal vector prior to version 0.45 may differ
|
||||
from the latest version. This may impact methods which rely on the plane's
|
||||
direction. Use ``init_normal`` to control the sign explicitly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : array_like[float]
|
||||
Size ``[N x 3]`` sequence of points to fit a plane through.
|
||||
|
||||
return_meta : bool, default: False
|
||||
If ``True``, also returns the center and normal of the
|
||||
generated plane.
|
||||
|
||||
resolution : int, default: 10
|
||||
Number of points on the plane mesh along its edges. Specify two numbers to
|
||||
set the resolution along the plane's long and short edge (respectively) or
|
||||
a single number to set both edges to have the same resolution.
|
||||
|
||||
.. versionadded:: 0.45.0
|
||||
|
||||
init_normal : VectorLike[float] | str, optional
|
||||
Flip the normal of the plane such that it best aligns with this vector. Can be
|
||||
a vector or string specifying the axis by name (e.g. ``'x'`` or ``'-x'``, etc.).
|
||||
|
||||
.. versionadded:: 0.45.0
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Plane mesh.
|
||||
|
||||
pyvista.pyvista_ndarray
|
||||
Plane center if ``return_meta=True``.
|
||||
|
||||
pyvista.pyvista_ndarray
|
||||
Plane normal if ``return_meta=True``.
|
||||
|
||||
See Also
|
||||
--------
|
||||
fit_line_to_points
|
||||
Fit a line using the first principal axis of the points.
|
||||
|
||||
principal_axes
|
||||
Compute axes vectors which best fit a set of points.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Fit a plane to a random point cloud.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> from pyvista import examples
|
||||
>>>
|
||||
>>> rng = np.random.default_rng(seed=0)
|
||||
>>> cloud = rng.random((10, 3))
|
||||
>>> cloud[:, 2] *= 0.1
|
||||
>>>
|
||||
>>> plane = pv.fit_plane_to_points(cloud)
|
||||
|
||||
Plot the point cloud and fitted plane.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(plane, style='wireframe', line_width=4)
|
||||
>>> _ = pl.add_points(
|
||||
... cloud,
|
||||
... render_points_as_spheres=True,
|
||||
... color='r',
|
||||
... point_size=30,
|
||||
... )
|
||||
>>> pl.show()
|
||||
|
||||
Fit a plane to a mesh and return its metadata. Set the plane resolution to 1
|
||||
so that the plane has no internal points or edges.
|
||||
|
||||
>>> mesh = examples.download_shark()
|
||||
>>> plane, center, normal = pv.fit_plane_to_points(
|
||||
... mesh.points, return_meta=True, resolution=1
|
||||
... )
|
||||
|
||||
Plot the mesh and fitted plane.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(plane, show_edges=True, opacity=0.25)
|
||||
>>> _ = pl.add_mesh(mesh, color='gray')
|
||||
>>> pl.camera_position = [
|
||||
... (-117, 76, 235),
|
||||
... (1.69, -1.38, 0),
|
||||
... (0.189, 0.957, -0.22),
|
||||
... ]
|
||||
>>> pl.show()
|
||||
|
||||
Use the metadata with :meth:`pyvista.DataObjectFilters.clip` to split the mesh into
|
||||
two.
|
||||
|
||||
>>> first_half, second_half = mesh.clip(
|
||||
... origin=center, normal=normal, return_clipped=True
|
||||
... )
|
||||
|
||||
Plot the two halves of the clipped mesh.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(first_half, color='red')
|
||||
>>> _ = pl.add_mesh(second_half, color='blue')
|
||||
>>> pl.camera_position = [
|
||||
... (-143, 43, 40),
|
||||
... (-8.7, -11, -14),
|
||||
... (0.25, 0.92, -0.29),
|
||||
... ]
|
||||
>>> pl.show()
|
||||
|
||||
Note that it is pointing in the positive z-direction.
|
||||
|
||||
>>> normal
|
||||
pyvista_ndarray([5.2734075e-09, 6.7008443e-08, 1.0000000e+00],
|
||||
dtype=float32)
|
||||
|
||||
Use ``init_normal`` to flip the sign and make it negative instead.
|
||||
|
||||
>>> _, _, normal = pv.fit_plane_to_points(
|
||||
... mesh.points, return_meta=True, init_normal='-z'
|
||||
... )
|
||||
>>> normal
|
||||
pyvista_ndarray([-5.2734155e-09, -6.7008422e-08, -1.0000000e+00],
|
||||
dtype=float32)
|
||||
|
||||
"""
|
||||
valid_resolution = _validation.validate_array(
|
||||
resolution,
|
||||
must_have_shape=[(), (2,)],
|
||||
must_be_integer=True,
|
||||
broadcast_to=(2,),
|
||||
dtype_out=int,
|
||||
)
|
||||
i_resolution, j_resolution = valid_resolution
|
||||
|
||||
# Align points to the xyz-axes
|
||||
aligned, matrix = pyvista.PolyData(points).align_xyz(
|
||||
return_matrix=True, axis_2_direction=init_normal
|
||||
)
|
||||
|
||||
# Fit plane to xyz-aligned mesh
|
||||
i_size, j_size, _ = aligned.bounds_size
|
||||
plane = pyvista.Plane(
|
||||
i_size=i_size,
|
||||
j_size=j_size,
|
||||
i_resolution=i_resolution,
|
||||
j_resolution=j_resolution,
|
||||
)
|
||||
|
||||
# Transform plane back to input points positioning
|
||||
inverse_matrix = pyvista.Transform(matrix).inverse_matrix
|
||||
plane.transform(inverse_matrix, inplace=True)
|
||||
|
||||
if return_meta:
|
||||
# Compute center and normal from the plane's points and normals
|
||||
center = np.mean(plane.points, axis=0)
|
||||
normal = np.mean(plane.point_normals, axis=0)
|
||||
return plane, center, normal
|
||||
return plane
|
||||
|
||||
|
||||
def fit_line_to_points(
|
||||
points: MatrixLike[float],
|
||||
*,
|
||||
resolution: int = 1,
|
||||
init_direction: VectorLike[float] | None = None,
|
||||
return_meta: bool = False,
|
||||
) -> PolyData | tuple[PolyData, float, NumpyArray[float]]:
|
||||
"""Fit a line to points using its :func:`principal_axes`.
|
||||
|
||||
The line is automatically sized and oriented to fit the extents of
|
||||
the points.
|
||||
|
||||
.. versionadded:: 0.45.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : MatrixLike[float]
|
||||
Size ``[N x 3]`` array of points to fit a line through.
|
||||
|
||||
resolution : int, default: 1
|
||||
Number of pieces to divide the line into.
|
||||
|
||||
init_direction : VectorLike[float], optional
|
||||
Flip the direction of the line's points such that it best aligns with this
|
||||
vector. Can be a vector or string specifying the axis by name (e.g. ``'x'``
|
||||
or ``'-x'``, etc.).
|
||||
|
||||
return_meta : bool, default: False
|
||||
If ``True``, also returns the length (magnitude) and direction of the line.
|
||||
|
||||
See Also
|
||||
--------
|
||||
fit_plane_to_points
|
||||
Fit a plane using the first two principal axes of the points.
|
||||
|
||||
principal_axes
|
||||
Compute axes vectors which best fit a set of points.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Line mesh.
|
||||
|
||||
float
|
||||
Line length if ``return_meta=True``.
|
||||
|
||||
numpy.ndarray
|
||||
Line direction (unit vector) if ``return_meta=True``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Download a point cloud. The points trace a path along topographical surface.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.download_gpr_path()
|
||||
|
||||
Fit a line to the points and plot the result. The line of best fit is colored red.
|
||||
|
||||
>>> line = pv.fit_line_to_points(mesh.points)
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(mesh, color='black', line_width=10)
|
||||
>>> _ = pl.add_mesh(line, color='red', line_width=5)
|
||||
>>> pl.show()
|
||||
|
||||
Fit a line to a mesh and return the metadata.
|
||||
|
||||
>>> mesh = examples.download_human()
|
||||
>>> line, length, direction = pv.fit_line_to_points(
|
||||
... mesh.points, return_meta=True
|
||||
... )
|
||||
|
||||
Show the length of the line.
|
||||
|
||||
>>> length
|
||||
167.6145387467733
|
||||
|
||||
Plot the line as an arrow to show its direction.
|
||||
|
||||
>>> arrow = pv.Arrow(
|
||||
... start=line.points[0],
|
||||
... direction=direction,
|
||||
... scale=length,
|
||||
... tip_length=0.2,
|
||||
... tip_radius=0.04,
|
||||
... shaft_radius=0.01,
|
||||
... )
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(mesh, opacity=0.5)
|
||||
>>> _ = pl.add_mesh(arrow, color='red')
|
||||
>>> pl.show()
|
||||
|
||||
Set ``init_direction`` to the positive z-axis to flip the line's direction.
|
||||
|
||||
>>> mesh = examples.download_human()
|
||||
>>> line, length, direction = pv.fit_line_to_points(
|
||||
... mesh.points, init_direction='z', return_meta=True
|
||||
... )
|
||||
|
||||
Plot the results again with an arrow.
|
||||
|
||||
>>> arrow = pv.Arrow(
|
||||
... start=line.points[0],
|
||||
... direction=direction,
|
||||
... scale=length,
|
||||
... tip_length=0.2,
|
||||
... tip_radius=0.04,
|
||||
... shaft_radius=0.01,
|
||||
... )
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(mesh, opacity=0.5)
|
||||
>>> _ = pl.add_mesh(arrow, color='red')
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
# Align points to the xyz-axes
|
||||
aligned, matrix = pyvista.PolyData(points).align_xyz(
|
||||
axis_0_direction=init_direction, return_matrix=True
|
||||
)
|
||||
|
||||
# Fit line to xyz-aligned mesh
|
||||
point_a = (aligned.bounds.x_min, 0, 0)
|
||||
point_b = (aligned.bounds.x_max, 0, 0)
|
||||
line_mesh = pyvista.LineSource(point_a, point_b, resolution=resolution).output
|
||||
|
||||
# Transform line back to input points positioning
|
||||
inverse_matrix = pyvista.Transform(matrix).inverse_matrix
|
||||
line_mesh.transform(inverse_matrix, inplace=True)
|
||||
|
||||
if return_meta:
|
||||
return line_mesh, line_mesh.length, matrix[0, :3]
|
||||
return line_mesh
|
||||
|
||||
|
||||
def make_tri_mesh(points: NumpyArray[float], faces: NumpyArray[int]) -> PolyData:
|
||||
"""Construct a ``pyvista.PolyData`` mesh using points and faces arrays.
|
||||
|
||||
Construct a mesh from an Nx3 array of points and an Mx3 array of
|
||||
triangle indices, resulting in a mesh with N vertices and M
|
||||
triangles. This function does not require the standard VTK
|
||||
"padding" column and simplifies mesh creation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : np.ndarray
|
||||
Array of points with shape ``(N, 3)`` storing the vertices of the
|
||||
triangle mesh.
|
||||
|
||||
faces : np.ndarray
|
||||
Array of indices with shape ``(M, 3)`` containing the triangle
|
||||
indices.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
PolyData instance containing the triangle mesh.
|
||||
|
||||
Examples
|
||||
--------
|
||||
This example discretizes the unit square into a triangle mesh with
|
||||
nine vertices and eight faces.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> points = np.array(
|
||||
... [
|
||||
... [0, 0, 0],
|
||||
... [0.5, 0, 0],
|
||||
... [1, 0, 0],
|
||||
... [0, 0.5, 0],
|
||||
... [0.5, 0.5, 0],
|
||||
... [1, 0.5, 0],
|
||||
... [0, 1, 0],
|
||||
... [0.5, 1, 0],
|
||||
... [1, 1, 0],
|
||||
... ]
|
||||
... )
|
||||
>>> faces = np.array(
|
||||
... [
|
||||
... [0, 1, 4],
|
||||
... [4, 7, 6],
|
||||
... [2, 5, 4],
|
||||
... [4, 5, 8],
|
||||
... [0, 4, 3],
|
||||
... [3, 4, 6],
|
||||
... [1, 2, 4],
|
||||
... [4, 8, 7],
|
||||
... ]
|
||||
... )
|
||||
>>> tri_mesh = pv.make_tri_mesh(points, faces)
|
||||
>>> tri_mesh.plot(show_edges=True, line_width=5)
|
||||
|
||||
"""
|
||||
if points.shape[1] != 3:
|
||||
msg = 'Points array should have shape (N, 3).'
|
||||
raise ValueError(msg)
|
||||
if faces.ndim != 2 or faces.shape[1] != 3:
|
||||
msg = 'Face array should have shape (M, 3).'
|
||||
raise ValueError(msg)
|
||||
cells = np.empty((faces.shape[0], 4), dtype=faces.dtype)
|
||||
cells[:, 0] = 3
|
||||
cells[:, 1:] = faces
|
||||
return pyvista.PolyData(points, cells)
|
||||
|
||||
|
||||
def vector_poly_data(
|
||||
orig: VectorLike[float] | MatrixLike[float], vec: VectorLike[float] | MatrixLike[float]
|
||||
) -> PolyData:
|
||||
"""Create a pyvista.PolyData object composed of vectors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
orig : array_like[float]
|
||||
Array of vector origins.
|
||||
|
||||
vec : array_like[float]
|
||||
Array of vectors.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Mesh containing the ``orig`` points along with the
|
||||
``'vectors'`` and ``'mag'`` point arrays representing the
|
||||
vectors and magnitude of the vectors at each point.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create basic vector field. This is a point cloud where each point
|
||||
has a vector and magnitude attached to it.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> x, y = np.meshgrid(np.linspace(-5, 5, 10), np.linspace(-5, 5, 10))
|
||||
>>> points = np.vstack((x.ravel(), y.ravel(), np.zeros(x.size))).T
|
||||
>>> u = x / np.sqrt(x**2 + y**2)
|
||||
>>> v = y / np.sqrt(x**2 + y**2)
|
||||
>>> vectors = np.vstack((u.ravel() ** 3, v.ravel() ** 3, np.zeros(u.size))).T
|
||||
>>> pdata = pv.vector_poly_data(points, vectors)
|
||||
>>> pdata.point_data.keys()
|
||||
['vectors', 'mag']
|
||||
|
||||
Convert these to arrows and plot it.
|
||||
|
||||
>>> pdata.glyph(orient='vectors', scale='mag').plot()
|
||||
|
||||
"""
|
||||
# shape, dimension checking
|
||||
if not isinstance(orig, np.ndarray):
|
||||
orig = np.asarray(orig)
|
||||
|
||||
if not isinstance(vec, np.ndarray):
|
||||
vec = np.asarray(vec)
|
||||
|
||||
if orig.ndim != 2:
|
||||
orig = orig.reshape((-1, 3))
|
||||
elif orig.shape[1] != 3:
|
||||
msg = 'orig array must be 3D'
|
||||
raise ValueError(msg)
|
||||
|
||||
if vec.ndim != 2:
|
||||
vec = vec.reshape((-1, 3))
|
||||
elif vec.shape[1] != 3:
|
||||
msg = 'vec array must be 3D'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Create vtk points and cells objects
|
||||
vpts = _vtk.vtkPoints()
|
||||
vpts.SetData(_vtk.numpy_to_vtk(np.ascontiguousarray(orig), deep=True))
|
||||
|
||||
npts = orig.shape[0]
|
||||
vcells = pyvista.core.cell.CellArray.from_regular_cells(
|
||||
np.arange(npts, dtype=pyvista.ID_TYPE).reshape((npts, 1)),
|
||||
)
|
||||
|
||||
# Create vtkPolyData object
|
||||
pdata = _vtk.vtkPolyData()
|
||||
pdata.SetPoints(vpts)
|
||||
pdata.SetVerts(vcells)
|
||||
|
||||
# Add vectors to polydata
|
||||
name = 'vectors'
|
||||
vtkfloat = _vtk.numpy_to_vtk(np.ascontiguousarray(vec), deep=True)
|
||||
vtkfloat.SetName(name)
|
||||
pdata.GetPointData().AddArray(vtkfloat)
|
||||
pdata.GetPointData().SetActiveVectors(name)
|
||||
|
||||
# Add magnitude of vectors to polydata
|
||||
name = 'mag'
|
||||
scalars = (vec * vec).sum(1) ** 0.5
|
||||
vtkfloat = _vtk.numpy_to_vtk(np.ascontiguousarray(scalars), deep=True)
|
||||
vtkfloat.SetName(name)
|
||||
pdata.GetPointData().AddArray(vtkfloat)
|
||||
pdata.GetPointData().SetActiveScalars(name)
|
||||
|
||||
return pyvista.PolyData(pdata)
|
||||
|
||||
|
||||
@overload
|
||||
def principal_axes(points: MatrixLike[float]) -> NumpyArray[float]: ...
|
||||
@overload
|
||||
def principal_axes(
|
||||
points: MatrixLike[float],
|
||||
*,
|
||||
return_std: Literal[True] = True,
|
||||
) -> tuple[NumpyArray[float], NumpyArray[float]]: ...
|
||||
@overload
|
||||
def principal_axes(
|
||||
points: MatrixLike[float],
|
||||
*,
|
||||
return_std: Literal[False] = False,
|
||||
) -> NumpyArray[float]: ...
|
||||
@overload
|
||||
def principal_axes(
|
||||
points: MatrixLike[float], *, return_std: bool = ...
|
||||
) -> NumpyArray[float] | tuple[NumpyArray[float], NumpyArray[float]]: ...
|
||||
def principal_axes(
|
||||
points: MatrixLike[float], *, return_std: bool = False
|
||||
) -> NumpyArray[float] | tuple[NumpyArray[float], NumpyArray[float]]:
|
||||
"""Compute the principal axes of a set of points.
|
||||
|
||||
Principal axes are orthonormal vectors that best fit a set of points. The axes
|
||||
are also known as the principal components in Principal Component Analysis (PCA),
|
||||
or the right singular vectors from the Singular Value Decomposition (SVD).
|
||||
|
||||
The axes are computed as the eigenvectors of the covariance matrix from the
|
||||
mean-centered points, and are processed to ensure that they form a right-handed
|
||||
coordinate frame.
|
||||
|
||||
The axes explain the total variance of the points. The first axis explains the
|
||||
largest percentage of variance, followed by the second axis, followed again by
|
||||
the third axis which explains the smallest percentage of variance.
|
||||
|
||||
The axes may be used to build an oriented bounding box or to align the points to
|
||||
another set of axes (e.g. the world XYZ axes).
|
||||
|
||||
.. note::
|
||||
The computed axes are not unique, and the sign of each axis direction can be
|
||||
arbitrarily changed.
|
||||
|
||||
.. note::
|
||||
This implementation creates a temporary array of the same size as the input
|
||||
array, and is therefore not optimal in terms of its memory requirements.
|
||||
A more memory-efficient computation may be supported in a future release.
|
||||
|
||||
.. versionadded:: 0.45.0
|
||||
|
||||
See Also
|
||||
--------
|
||||
fit_plane_to_points
|
||||
Fit a plane to points using the first two principal axes.
|
||||
|
||||
pyvista.DataSetFilters.align_xyz
|
||||
Filter which aligns principal axes to the x-y-z axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : MatrixLike[float]
|
||||
Nx3 array of points.
|
||||
|
||||
return_std : bool, default: False
|
||||
If ``True``, also returns the standard deviation of the points along each axis.
|
||||
Standard deviation is computed as the square root of the eigenvalues of the
|
||||
mean-centered covariance matrix.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
3x3 orthonormal array with the principal axes as row vectors.
|
||||
|
||||
numpy.ndarray
|
||||
Three-item array of the standard deviations along each axis.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> rng = np.random.default_rng(seed=0) # only seeding for the example
|
||||
|
||||
Create a mesh with points that have the largest variation in ``X``,
|
||||
followed by ``Y``, then ``Z``.
|
||||
|
||||
>>> radii = np.array((6, 3, 1)) # x-y-z radii
|
||||
>>> mesh = pv.ParametricEllipsoid(
|
||||
... xradius=radii[0], yradius=radii[1], zradius=radii[2]
|
||||
... )
|
||||
|
||||
Plot the mesh and highlight its points in black.
|
||||
|
||||
>>> p = pv.Plotter()
|
||||
>>> _ = p.add_mesh(mesh)
|
||||
>>> _ = p.add_points(mesh, color='black')
|
||||
>>> _ = p.show_grid()
|
||||
>>> p.show()
|
||||
|
||||
Compute its principal axes and return the standard deviations.
|
||||
|
||||
>>> axes, std = pv.principal_axes(mesh.points, return_std=True)
|
||||
>>> axes
|
||||
pyvista_ndarray([[-1.0000000e+00, -3.8287229e-08, 3.6589407e-10],
|
||||
[-3.8287229e-08, 1.0000000e+00, -3.0685656e-09],
|
||||
[-3.6589393e-10, -3.0685656e-09, -1.0000000e+00]],
|
||||
dtype=float32)
|
||||
|
||||
Note that the principal axes have ones along the diagonal and zeros
|
||||
in the off-diagonal. This indicates that the first principal axis is
|
||||
aligned with the x-axis, the second with the y-axis, and third with
|
||||
the z-axis. This is expected, since the mesh is already axis-aligned.
|
||||
|
||||
However, since the signs of the principal axes are arbitrary, the
|
||||
first and third axes in this case have a negative direction.
|
||||
|
||||
Show the standard deviation along each axis.
|
||||
|
||||
>>> std
|
||||
array([3.014956 , 1.507478 , 0.7035637], dtype=float32)
|
||||
|
||||
Compare this to using :meth:`numpy.std` for the computation.
|
||||
|
||||
>>> np.std(mesh.points, axis=0)
|
||||
pyvista_ndarray([3.0149572, 1.5074761, 0.7035699], dtype=float32)
|
||||
|
||||
Since the points are axis-aligned, the two results agree in this case. In general,
|
||||
however, these two methods differ in that :meth:`numpy.std` with `axis=0` computes
|
||||
the standard deviation along the `x-y-z` axes, whereas the standard deviation
|
||||
returned by :meth:`principal_axes` is computed along the principal axes.
|
||||
|
||||
Convert the values to proportions for analysis.
|
||||
|
||||
>>> std / sum(std)
|
||||
array([0.5769149 , 0.28845742, 0.1346276 ], dtype=float32)
|
||||
|
||||
From this result, we can determine that the axes explain approximately
|
||||
58%, 29%, and 13% of the total variance in the points, respectively.
|
||||
|
||||
Let's compare this to the proportions of the known radii of the ellipsoid.
|
||||
|
||||
>>> radii / sum(radii)
|
||||
array([0.6, 0.3, 0.1])
|
||||
|
||||
Note how the two ratios are similar, but do not match exactly. This is
|
||||
because the points of the ellipsoid are prolate and are denser near the
|
||||
poles. If the points were normally distributed, however, the proportions
|
||||
would match exactly.
|
||||
|
||||
Create an array of normally distributed points scaled along the x-y-z axes.
|
||||
Use the same scaling as the radii of the ellipsoid from the previous example.
|
||||
|
||||
>>> normal_points = rng.normal(size=(1000, 3))
|
||||
>>> scaled_points = normal_points * radii
|
||||
>>> axes, std = pv.principal_axes(scaled_points, return_std=True)
|
||||
>>> axes
|
||||
array([[-0.99997578, 0.00682346, 0.00136972],
|
||||
[ 0.00681368, 0.99995213, -0.00702282],
|
||||
[-0.00141757, -0.00701331, -0.9999744 ]])
|
||||
|
||||
Once again, the axes have ones along the diagonal as expected since the
|
||||
points are already axis-aligned. Now let's examine the standard deviation
|
||||
and compare the relative proportions.
|
||||
|
||||
>>> std
|
||||
array([5.94466738, 2.89590334, 1.02103169])
|
||||
|
||||
>>> std / sum(std)
|
||||
array([0.60280948, 0.29365444, 0.10353608])
|
||||
|
||||
>>> radii / sum(radii)
|
||||
array([0.6, 0.3, 0.1])
|
||||
|
||||
Since the points are normally distributed, the relative proportion of
|
||||
the standard deviation matches the scaling of the axes almost perfectly.
|
||||
|
||||
"""
|
||||
points = _validation.validate_arrayNx3(points)
|
||||
|
||||
points_centered = points - np.mean(points, axis=0)
|
||||
eig_vals, eig_vectors = np.linalg.eigh(points_centered.T @ points_centered)
|
||||
axes = eig_vectors.T[::-1] # columns, ascending order -> rows, descending order
|
||||
|
||||
# Ensure axes form a right-handed coordinate frame
|
||||
if np.linalg.det(axes) < 0:
|
||||
axes[2] *= -1
|
||||
|
||||
if return_std:
|
||||
# Compute standard deviation and swap order from ascending -> descending
|
||||
std = np.sqrt(np.abs(eig_vals) / len(points))[::-1]
|
||||
return axes, std
|
||||
return axes
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,312 @@
|
||||
"""Context manager for controlling global state variables."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
import contextlib
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Generic
|
||||
from typing import Literal
|
||||
from typing import TypeVar
|
||||
from typing import cast
|
||||
from typing import final
|
||||
from typing import get_args
|
||||
from typing import overload
|
||||
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class _StateManager(contextlib.AbstractContextManager[None], ABC, Generic[T]):
|
||||
"""Abstract base class for managing a global state variable.
|
||||
|
||||
Subclasses must:
|
||||
|
||||
- Specify a `Literal` as the subclass' type argument. The literal's
|
||||
arguments must specify all allowable options for the state variable.
|
||||
- Define a getter and setter for the state. Input validation is not
|
||||
required - the input is automatically validated when setting the state.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista.core.utilities.state_manager import _StateManager
|
||||
>>> from typing import Literal
|
||||
|
||||
Define the available options as a ``Literal`` and initialize a global state variable.
|
||||
|
||||
>>> _StateOptions = Literal['on', 'off']
|
||||
>>> _GLOBAL_STATE = ['off'] # Init global state. Use list to make it mutable.
|
||||
|
||||
Define the class and its state property.
|
||||
|
||||
>>> class MyState(_StateManager[_StateOptions]):
|
||||
... @property
|
||||
... def _state(self) -> _StateOptions:
|
||||
... return _GLOBAL_STATE[0]
|
||||
...
|
||||
... @_state.setter
|
||||
... def _state(self, state: _StateOptions) -> None:
|
||||
... _GLOBAL_STATE[0] = state
|
||||
|
||||
Finally, create an instance of the state manager.
|
||||
|
||||
>>> my_state = MyState()
|
||||
|
||||
Get the state.
|
||||
|
||||
>>> my_state()
|
||||
'off'
|
||||
|
||||
Set the state.
|
||||
|
||||
>>> _ = my_state('on')
|
||||
>>> my_state()
|
||||
'on'
|
||||
|
||||
Use it as a context manager to set the state temporarily:
|
||||
|
||||
>>> with my_state('off'):
|
||||
... pass
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _get_state_options_from_literal(cls) -> tuple[str | int | bool]:
|
||||
state_manager_fullname = f'{_StateManager.__module__}.{_StateManager.__name__}'
|
||||
for base in getattr(cls, '__orig_bases__', ()):
|
||||
if str(base).startswith(state_manager_fullname):
|
||||
# Get StateManager's typing args
|
||||
state_manager_args = get_args(base)
|
||||
if len(state_manager_args) == 1:
|
||||
# There must only be one arg and it must be a non-empty Literal
|
||||
literal = state_manager_args[0]
|
||||
if str(literal).startswith('typing.Literal'):
|
||||
args = get_args(literal)
|
||||
if len(args) >= 1:
|
||||
return args
|
||||
msg = (
|
||||
'Type argument for subclasses must be a single non-empty Literal with all state '
|
||||
'options provided.'
|
||||
)
|
||||
raise TypeError(msg)
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize context manager."""
|
||||
self._valid_states = self._get_state_options_from_literal()
|
||||
self._original_state: T | None = None
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _state(self) -> T:
|
||||
"""Get the current global state."""
|
||||
|
||||
@_state.setter
|
||||
@abstractmethod
|
||||
def _state(self, state: T) -> None:
|
||||
"""Set the global state."""
|
||||
|
||||
@final
|
||||
def _validate_state(self, state: T) -> T:
|
||||
from pyvista import _validation # noqa: PLC0415
|
||||
|
||||
_validation.check_contains(self._valid_states, must_contain=state, name='state')
|
||||
return state
|
||||
|
||||
def __enter__(self) -> None:
|
||||
"""Enter context manager."""
|
||||
if self._original_state is None:
|
||||
msg = 'State must be set before using it as a context manager.'
|
||||
raise ValueError(msg)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): # noqa: ANN001, ANN204
|
||||
"""Exit context manager and restore original state."""
|
||||
self._state = cast('T', self._original_state)
|
||||
self._original_state = None # Reset
|
||||
|
||||
@overload
|
||||
def __call__(self: Self, state: None) -> T: ...
|
||||
@overload
|
||||
def __call__(self: Self, state: T) -> Self: ...
|
||||
def __call__(self: Self, state: T | None = None) -> Self | T:
|
||||
"""Call the context manager."""
|
||||
if state is None:
|
||||
return self._state
|
||||
|
||||
self._validate_state(state)
|
||||
|
||||
# Create new instance and store the local state to be restored when exiting
|
||||
output = self.__class__()
|
||||
output._original_state = self._state
|
||||
output._state = state
|
||||
return output
|
||||
|
||||
|
||||
_VerbosityOptions = Literal[
|
||||
'off',
|
||||
'error',
|
||||
'warning',
|
||||
'info',
|
||||
'max',
|
||||
]
|
||||
|
||||
|
||||
class _VTKVerbosity(_StateManager[_VerbosityOptions]):
|
||||
"""Context manager to set VTK verbosity level.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
Parameters
|
||||
----------
|
||||
verbosity : str
|
||||
Verbosity of the :vtk:`vtkLogger` to set.
|
||||
|
||||
- ``'off'``: No output.
|
||||
- ``'error'``: Only error messages.
|
||||
- ``'warning'``: Errors and warnings.
|
||||
- ``'info'``: Errors, warnings, and info messages.
|
||||
- ``'max'``: All messages, including debug info.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Get the current vtk verbosity.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pv.vtk_verbosity()
|
||||
'info'
|
||||
|
||||
Set verbosity to max.
|
||||
|
||||
>>> _ = pv.vtk_verbosity('max')
|
||||
>>> pv.vtk_verbosity()
|
||||
'max'
|
||||
|
||||
Create a :func:`~pyvista.Sphere`. Note how many VTK debugging messages are now
|
||||
generated as the sphere is created.
|
||||
|
||||
>>> mesh = pv.Sphere()
|
||||
|
||||
Use it as a context manager to temporarily turn it off.
|
||||
|
||||
>>> with pv.vtk_verbosity('off'):
|
||||
... mesh = mesh.cell_quality('volume')
|
||||
|
||||
The state is restored to its previous value outside the context.
|
||||
|
||||
>>> pv.vtk_verbosity()
|
||||
'max'
|
||||
|
||||
Note that the verbosity state is global and will persist between function
|
||||
calls. If the context manager isn't used, the state needs to be reset explicitly.
|
||||
Here, we set it back to its default value.
|
||||
|
||||
>>> _ = pv.vtk_verbosity('info')
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def _state(self) -> _VerbosityOptions:
|
||||
int_to_string: dict[int, _VerbosityOptions] = {
|
||||
-9: 'off',
|
||||
-2: 'error',
|
||||
-1: 'warning',
|
||||
0: 'info',
|
||||
9: 'max',
|
||||
}
|
||||
state = _vtk.vtkLogger.GetCurrentVerbosityCutoff()
|
||||
try:
|
||||
return int_to_string[state]
|
||||
except KeyError:
|
||||
# Unsupported state, raise error using validation method
|
||||
self._validate_state(state) # type: ignore[arg-type]
|
||||
msg = 'This line should not be reachable.' # pragma: no cover
|
||||
raise RuntimeWarning(msg) # pragma: no cover
|
||||
|
||||
@_state.setter
|
||||
def _state(self, state: _VerbosityOptions) -> None:
|
||||
verbosity_int = _vtk.vtkLogger.ConvertToVerbosity(state.upper())
|
||||
_vtk.vtkLogger.SetStderrVerbosity(verbosity_int)
|
||||
|
||||
|
||||
vtk_verbosity = _VTKVerbosity()
|
||||
|
||||
|
||||
_VtkSnakeCaseOptions = Literal['allow', 'warning', 'error']
|
||||
|
||||
|
||||
class _vtkSnakeCase(_StateManager[_VtkSnakeCaseOptions]): # noqa: N801
|
||||
"""Context manager to control access to VTK's pythonic snake_case API.
|
||||
|
||||
VTK 9.4 introduced pythonic snake_case attributes, e.g. `output_port` instead
|
||||
of `GetOutputPort`. These can easily be confused for PyVista attributes
|
||||
which also use a snake_case convention. This class controls access to vtk's
|
||||
new interface.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
Parameters
|
||||
----------
|
||||
state : 'allow' | 'warning' | 'error'
|
||||
Allow or disallow the use of VTK's pythonic snake_case API with
|
||||
PyVista-wrapped VTK classes.
|
||||
|
||||
- 'allow': Allow accessing VTK-defined snake_case attributes.
|
||||
- 'warning': Print a RuntimeWarning when accessing VTK-defined snake_case
|
||||
attributes.
|
||||
- 'error': Raise a ``PyVistaAttributeError`` when accessing
|
||||
VTK-defined snake_case attributes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Get the current access state for VTK's snake_case api.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pv.vtk_snake_case()
|
||||
'error'
|
||||
|
||||
The following will raise an error because the `information` property is defined
|
||||
by :vtk:`vtkDataObject` and is not part of PyVista's API.
|
||||
|
||||
>>> # pv.PolyData().information
|
||||
|
||||
Allow use of VTK's snake_case attributes. No warning or error is raised.
|
||||
|
||||
>>> _ = pv.vtk_snake_case('allow')
|
||||
>>> pv.PolyData().information
|
||||
<vtkmodules.vtkCommonCore.vtkInformation...
|
||||
|
||||
Note that this state is global and will persist between function calls. Set it
|
||||
back to its original state explicitly.
|
||||
|
||||
>>> _ = pv.vtk_snake_case('error')
|
||||
|
||||
Use it as a context manager instead. This way, the state is only temporarily
|
||||
modified and is automatically restored.
|
||||
|
||||
>>> with pv.vtk_snake_case('allow'):
|
||||
... _ = pv.PolyData().information
|
||||
|
||||
>>> pv.vtk_snake_case()
|
||||
'error'
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def _state(self) -> _VtkSnakeCaseOptions:
|
||||
import pyvista as pv # noqa: PLC0415
|
||||
|
||||
return pv._VTK_SNAKE_CASE_STATE
|
||||
|
||||
@_state.setter
|
||||
def _state(self, state: _VtkSnakeCaseOptions) -> None:
|
||||
import pyvista as pv # noqa: PLC0415
|
||||
|
||||
pv._VTK_SNAKE_CASE_STATE = state
|
||||
|
||||
|
||||
vtk_snake_case = _vtkSnakeCase()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,537 @@
|
||||
"""Module implementing point transformations and their matrices."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
from typing import overload
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _validation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
from pyvista.core._typing_core import TransformLike
|
||||
from pyvista.core._typing_core import VectorLike
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['axis', 'angle'])
|
||||
def axis_angle_rotation( # noqa: PLR0917
|
||||
axis: VectorLike[float],
|
||||
angle: float,
|
||||
point: VectorLike[float] | None = None,
|
||||
deg: bool = True, # noqa: FBT001, FBT002
|
||||
) -> NumpyArray[float]:
|
||||
r"""Return a 4x4 matrix for rotation about any axis by given angle.
|
||||
|
||||
Rotations around an axis that contains the origin can easily be
|
||||
computed using Rodrigues' rotation formula. The key quantity is
|
||||
the ``K`` cross product matrix for the unit vector ``n`` defining
|
||||
the axis of the rotation:
|
||||
|
||||
/ 0 -nz ny \
|
||||
K = | nz 0 -nx |
|
||||
\ -ny nx 0 /
|
||||
|
||||
For a rotation angle ``phi`` around the vector ``n`` the rotation
|
||||
matrix is given by
|
||||
|
||||
R = I + sin(phi) K + (1 - cos(phi)) K^2
|
||||
|
||||
where ``I`` is the 3-by-3 unit matrix and ``K^2`` denotes the matrix
|
||||
square of ``K``.
|
||||
|
||||
If the rotation axis doesn't contain the origin, we have to first
|
||||
shift real space to transform the axis' ``p0`` reference point into
|
||||
the origin, then shift the points back after rotation:
|
||||
|
||||
p' = R @ (p - p0) + p0 = R @ p + (p0 - R @ p0)
|
||||
|
||||
This means that the rotation in general consists of a 3-by-3
|
||||
rotation matrix ``R``, and a translation given by
|
||||
``b = p0 - R @ p0``. These can be encoded in a 4-by-4 transformation
|
||||
matrix by filling the 3-by-3 leading principal submatrix with ``R``,
|
||||
and filling the top 3 values in the last column with ``b``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axis : sequence[float]
|
||||
The direction vector of the rotation axis. It need not be a
|
||||
unit vector, but it must not be a zero vector.
|
||||
|
||||
angle : float
|
||||
Angle of rotation around the axis. The angle is defined as a
|
||||
counterclockwise rotation when facing the normal vector of the
|
||||
rotation axis. Passed either in degrees or radians depending on
|
||||
the value of ``deg``.
|
||||
|
||||
point : sequence[float], optional
|
||||
The origin of the rotation (a reference point through which the
|
||||
rotation axis passes). By default the rotation axis contains the
|
||||
origin.
|
||||
|
||||
deg : bool, default: True
|
||||
Whether the angle is specified in degrees. ``False`` implies
|
||||
radians.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
The ``(4, 4)`` rotation matrix.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Generate a transformation matrix for rotation around a cube's body
|
||||
diagonal by 120 degrees.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> from pyvista import transformations
|
||||
>>> trans = transformations.axis_angle_rotation([1, 1, 1], 120)
|
||||
|
||||
Check that the transformation cycles the cube's three corners.
|
||||
|
||||
>>> corners = np.array(
|
||||
... [
|
||||
... [1, 0, 0],
|
||||
... [0, 1, 0],
|
||||
... [0, 0, 1],
|
||||
... ]
|
||||
... )
|
||||
>>> rotated = transformations.apply_transformation_to_points(
|
||||
... trans, corners
|
||||
... )
|
||||
>>> np.allclose(rotated, corners[[1, 2, 0], :])
|
||||
True
|
||||
|
||||
"""
|
||||
if deg:
|
||||
# convert to radians
|
||||
angle *= np.pi / 180
|
||||
|
||||
# return early for no rotation; play it safe and check only exact equality
|
||||
if angle % (2 * np.pi) == 0:
|
||||
return np.eye(4)
|
||||
|
||||
axis_ = _validation.validate_array3(axis, dtype_out=float, name='axis')
|
||||
if point is not None:
|
||||
point_ = _validation.validate_array3(point, dtype_out=float, name='point')
|
||||
|
||||
# check and normalize
|
||||
axis_norm = np.linalg.norm(axis_)
|
||||
if np.isclose(axis_norm, 0):
|
||||
msg = 'Cannot rotate around zero vector axis.'
|
||||
raise ValueError(msg)
|
||||
if not np.isclose(axis_norm, 1):
|
||||
axis_ = axis_ / axis_norm
|
||||
|
||||
# build Rodrigues' rotation matrix
|
||||
K = np.zeros((3, 3))
|
||||
K[[2, 0, 1], [1, 2, 0]] = axis_
|
||||
K += -K.T
|
||||
|
||||
# the cos and sin functions can introduce some numerical error
|
||||
# round the elements to exact values for special cases where we know
|
||||
# sin/cos should evaluate exactly to 0 or 1
|
||||
sin_angle = np.sin(angle)
|
||||
cos_angle = np.cos(angle)
|
||||
if angle % (np.pi / 2) == 0:
|
||||
cos_angle = round(cos_angle)
|
||||
sin_angle = round(sin_angle)
|
||||
|
||||
R = np.eye(3) + sin_angle * K + (1 - cos_angle) * K @ K
|
||||
|
||||
augmented = np.eye(4)
|
||||
augmented[:-1, :-1] = R
|
||||
|
||||
if point is not None:
|
||||
# rotation of point p would be R @ (p - point) + point
|
||||
# which is R @ p + (point - R @ point)
|
||||
augmented[:-1, -1] = point_ - R @ point_
|
||||
|
||||
return augmented
|
||||
|
||||
|
||||
def reflection(
|
||||
normal: VectorLike[float], point: VectorLike[float] | None = None
|
||||
) -> NumpyArray[float]:
|
||||
"""Return a 4x4 matrix for reflection across a normal about a point.
|
||||
|
||||
Projection to a unit vector ``n`` can be computed using the dyadic
|
||||
product (or outer product) ``P`` of ``n`` with itself, which is a
|
||||
3-by-3 symmetric matrix.
|
||||
|
||||
Reflection across a plane that contains the origin amounts to
|
||||
reversing the components of real space points that are perpendicular
|
||||
to the reflection plane. This gives us the transformation ``R``
|
||||
acting on a point ``p`` as
|
||||
|
||||
p' = R @ p = p - 2 P @ p = (I - 2 P) @ p
|
||||
|
||||
so the reflection's transformation matrix is the unit matrix minus
|
||||
twice the dyadic product ``P``.
|
||||
|
||||
If additionally we want to compute a reflection to a plane that does
|
||||
not contain the origin, we can we can first shift every point in
|
||||
real space by ``-p0`` (if ``p0`` is a point that lies on the plane)
|
||||
|
||||
p' = R @ (p - p0) + p0 = R @ p + (p0 - R @ p0)
|
||||
|
||||
This means that the reflection in general consists of a 3-by-3
|
||||
reflection matrix ``R``, and a translation given by
|
||||
``b = p0 - R @ p0``. These can be encoded in a 4-by-4 transformation
|
||||
matrix by filling the 3-by-3 leading principal submatrix with ``R``,
|
||||
and filling the top 3 values in the last column with ``b``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
normal : sequence[float]
|
||||
The normal vector of the reflection plane. It need not be a unit
|
||||
vector, but it must not be a zero vector.
|
||||
|
||||
point : sequence[float], optional
|
||||
The origin of the reflection (a reference point through which
|
||||
the reflection plane passes). By default the reflection plane
|
||||
contains the origin.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ndarray
|
||||
A ``(4, 4)`` transformation matrix for reflecting points across the
|
||||
plane defined by the given normal and point.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Generate a transformation matrix for reflection over the XZ plane.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> from pyvista import transformations
|
||||
>>> trans = transformations.reflection([0, 1, 0])
|
||||
|
||||
Check that the reflection transforms corners of a cube among one
|
||||
another.
|
||||
|
||||
>>> verts = np.array(
|
||||
... [
|
||||
... [1, -1, 1],
|
||||
... [-1, -1, 1],
|
||||
... [-1, -1, -1],
|
||||
... [-1, -1, 1],
|
||||
... [1, 1, 1],
|
||||
... [-1, 1, 1],
|
||||
... [-1, 1, -1],
|
||||
... [-1, 1, 1],
|
||||
... ]
|
||||
... )
|
||||
>>> mirrored = transformations.apply_transformation_to_points(trans, verts)
|
||||
>>> np.allclose(mirrored, verts[[np.r_[4:8, 0:4]], :])
|
||||
True
|
||||
|
||||
"""
|
||||
normal = np.asarray(normal, dtype='float64')
|
||||
if normal.shape != (3,):
|
||||
msg = 'Normal must be a 3-length array-like.'
|
||||
raise ValueError(msg)
|
||||
if point is not None:
|
||||
point = np.asarray(point)
|
||||
if point.shape != (3,):
|
||||
msg = 'Plane reference point must be a 3-length array-like.'
|
||||
raise ValueError(msg)
|
||||
|
||||
# check and normalize
|
||||
normal_norm = np.linalg.norm(normal)
|
||||
if np.isclose(normal_norm, 0):
|
||||
msg = 'Plane normal cannot be zero.'
|
||||
raise ValueError(msg)
|
||||
if not np.isclose(normal_norm, 1):
|
||||
normal = normal / normal_norm
|
||||
|
||||
# build reflection matrix
|
||||
projection = np.outer(normal, normal)
|
||||
R = np.eye(3) - 2 * projection
|
||||
augmented = np.eye(4)
|
||||
augmented[:-1, :-1] = R
|
||||
|
||||
if point is not None:
|
||||
# reflection of point p would be R @ (p - point) + point
|
||||
# which is R @ p + (point - R @ point)
|
||||
augmented[:-1, -1] = point - R @ point
|
||||
|
||||
return augmented
|
||||
|
||||
|
||||
@overload
|
||||
def apply_transformation_to_points(
|
||||
transformation: NumpyArray[float],
|
||||
points: NumpyArray[float],
|
||||
inplace: Literal[True] = True, # noqa: FBT002
|
||||
) -> None: ...
|
||||
@overload
|
||||
def apply_transformation_to_points(
|
||||
transformation: NumpyArray[float],
|
||||
points: NumpyArray[float],
|
||||
inplace: Literal[False] = False, # noqa: FBT002
|
||||
) -> NumpyArray[float]: ...
|
||||
@overload
|
||||
def apply_transformation_to_points(
|
||||
transformation: NumpyArray[float],
|
||||
points: NumpyArray[float],
|
||||
inplace: bool = ..., # noqa: FBT001
|
||||
) -> NumpyArray[float] | None: ...
|
||||
@_deprecate_positional_args(allowed=['transformation', 'points'])
|
||||
def apply_transformation_to_points(
|
||||
transformation: NumpyArray[float],
|
||||
points: NumpyArray[float],
|
||||
inplace: Literal[True, False] = False, # noqa: FBT002
|
||||
) -> NumpyArray[float] | None:
|
||||
"""Apply a given transformation matrix (3x3 or 4x4) to a set of points.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transformation : np.ndarray
|
||||
Transformation matrix of shape (3, 3) or (4, 4).
|
||||
|
||||
points : np.ndarray
|
||||
Array of points to be transformed of shape (N, 3).
|
||||
|
||||
inplace : bool, default: False
|
||||
Updates points in-place while returning nothing.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Transformed points.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Scale a set of points in-place.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> points = examples.load_airplane().points
|
||||
>>> points_orig = points.copy()
|
||||
>>> scale_factor = 2
|
||||
>>> tf = scale_factor * np.eye(4)
|
||||
>>> tf[3, 3] = 1
|
||||
>>> pv.core.utilities.transformations.apply_transformation_to_points(
|
||||
... tf, points, inplace=True
|
||||
... )
|
||||
>>> assert np.all(np.isclose(points, scale_factor * points_orig))
|
||||
|
||||
"""
|
||||
transformation_shape = transformation.shape
|
||||
if transformation_shape not in ((3, 3), (4, 4)):
|
||||
msg = '`transformation` must be of shape (3, 3) or (4, 4).'
|
||||
raise ValueError(msg)
|
||||
|
||||
if points.shape[1] != 3:
|
||||
msg = '`points` must be of shape (N, 3).'
|
||||
raise ValueError(msg)
|
||||
|
||||
if transformation_shape[0] == 4:
|
||||
# Divide by scale factor when homogeneous
|
||||
transformation /= transformation[3, 3]
|
||||
|
||||
# Add the homogeneous coordinate
|
||||
# `points_2` is a copy of the data, not a view
|
||||
points_2 = np.empty((len(points), 4))
|
||||
points_2[:, :-1] = points
|
||||
points_2[:, -1] = 1
|
||||
else:
|
||||
points_2 = points # type: ignore[assignment]
|
||||
|
||||
# Paged matrix multiplication. For arrays with ndim > 2, matmul assumes
|
||||
# that the matrices to be multiplied lie in the last two dimensions.
|
||||
points_2 = (transformation[np.newaxis, :, :] @ points_2.T)[0, :3, :].T
|
||||
|
||||
# If inplace, set the points
|
||||
if inplace:
|
||||
points[:] = points_2
|
||||
return None
|
||||
else:
|
||||
# otherwise return the new points
|
||||
return points_2
|
||||
|
||||
|
||||
def decomposition(
|
||||
transformation: TransformLike,
|
||||
*,
|
||||
homogeneous: bool = False,
|
||||
) -> tuple[
|
||||
NumpyArray[float], NumpyArray[float], NumpyArray[float], NumpyArray[float], NumpyArray[float]
|
||||
]:
|
||||
"""Decompose a transformation into its components.
|
||||
|
||||
The transformation matrix ``M`` is decomposed into five components:
|
||||
|
||||
- translation ``T``
|
||||
- rotation ``R``
|
||||
- reflection ``N``
|
||||
- scaling ``S``
|
||||
- shearing ``K``
|
||||
|
||||
such that, when represented as 4x4 matrices, ``M = TRNSK``. The decomposition is
|
||||
unique and is computed with polar matrix decomposition.
|
||||
|
||||
By default, compact representations of the transformations are returned (e.g. as a
|
||||
3-element vector or a 3x3 matrix). Optionally, 4x4 matrices may be returned instead.
|
||||
|
||||
.. note::
|
||||
|
||||
- The rotation is orthonormal and right-handed with positive determinant.
|
||||
- The scaling factors are positive.
|
||||
- The reflection is either ``1`` (no reflection) or ``-1`` (has reflection)
|
||||
and can be used like a scaling factor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transformation : TransformLike
|
||||
Array or transform to decompose.
|
||||
|
||||
homogeneous : bool, default: False
|
||||
If ``True``, return the components (translation, rotation, etc.) as 4x4
|
||||
homogeneous matrices. By default, reflection is a scalar, translation and
|
||||
scaling are length-3 vectors, and rotation and shear are 3x3 matrices.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Translation component ``T``. Returned as a 3-element vector (or a 4x4
|
||||
translation matrix if ``homogeneous`` is ``True``).
|
||||
|
||||
numpy.ndarray
|
||||
Rotation component ``R``. Returned as a 3x3 orthonormal rotation matrix of row
|
||||
vectors (or a 4x4 rotation matrix if ``homogeneous`` is ``True``).
|
||||
|
||||
numpy.ndarray
|
||||
Reflection component ``N``. Returned as a NumPy scalar (or a 4x4 reflection
|
||||
matrix if ``homogeneous`` is ``True``).
|
||||
|
||||
numpy.ndarray
|
||||
Scaling component ``S``. Returned as a 3-element vector (or a 4x4 scaling matrix
|
||||
if ``homogeneous`` is ``True``).
|
||||
|
||||
numpy.ndarray
|
||||
Shear component ``K``. Returned as a 3x3 matrix with ones on the diagonal and
|
||||
shear values in the off-diagonals (or as a 4x4 shearing matrix if ``homogeneous``
|
||||
is ``True``).
|
||||
|
||||
Examples
|
||||
--------
|
||||
Decompose a transformation matrix which has scaling, rotation, and translation.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> matrix = [
|
||||
... [0.0, -2.0, 0.0, 4.0],
|
||||
... [1.0, 0.0, 0.0, 5.0],
|
||||
... [0.0, 0.0, 3.0, 6.0],
|
||||
... [0.0, 0.0, 0.0, 1.0],
|
||||
... ]
|
||||
>>> T, R, N, S, K = pv.transformations.decomposition(matrix)
|
||||
|
||||
Since the input has no shear, this component is the identity matrix.
|
||||
|
||||
>>> K # shear
|
||||
array([[1., 0., 0.],
|
||||
[0., 1., 0.],
|
||||
[0., 0., 1.]])
|
||||
|
||||
>>> S # scale
|
||||
array([1., 2., 3.])
|
||||
|
||||
There is no reflection so this component is ``1``.
|
||||
|
||||
>>> N # reflection
|
||||
array(1.)
|
||||
|
||||
>>> R # rotation
|
||||
array([[ 0., -1., 0.],
|
||||
[ 1., 0., 0.],
|
||||
[ 0., 0., 1.]])
|
||||
|
||||
>>> T # translation
|
||||
array([4., 5., 6.])
|
||||
|
||||
Repeat the example, but this time with a small shear component of 0.1. Note how the
|
||||
presence of shear also affects the values of the scaling and rotation components.
|
||||
|
||||
>>> matrix = [
|
||||
... [0.0, -2.0, 0.0, 4.0],
|
||||
... [1.0, 0.1, 0.0, 5.0],
|
||||
... [0.0, 0.0, 3.0, 6.0],
|
||||
... [0.0, 0.0, 0.0, 1.0],
|
||||
... ]
|
||||
>>> T, R, N, S, K = pv.transformations.decomposition(matrix)
|
||||
|
||||
>>> K # shear
|
||||
array([[1. , 0.03333333, 0. ],
|
||||
[0.01663894, 1. , 0. ],
|
||||
[0. , 0. , 1. ]])
|
||||
|
||||
>>> S # scale
|
||||
array([0.99944491, 2.0022213 , 3. ])
|
||||
|
||||
>>> N # reflection
|
||||
array(1.)
|
||||
|
||||
>>> R # rotation
|
||||
array([[ 0.03331483, -0.99944491, 0. ],
|
||||
[ 0.99944491, 0.03331483, 0. ],
|
||||
[ 0. , 0. , 1. ]])
|
||||
|
||||
>>> T # translation
|
||||
array([4., 5., 6.])
|
||||
|
||||
"""
|
||||
matrix4x4 = _validation.validate_transform4x4(transformation)
|
||||
|
||||
dtype_out = matrix4x4.dtype
|
||||
I3 = np.eye(3, dtype=dtype_out)
|
||||
I4 = np.eye(4, dtype=dtype_out)
|
||||
matrix3x3 = matrix4x4[:3, :3]
|
||||
|
||||
T = matrix4x4[:3, 3]
|
||||
RN, SK = _polar_decomposition(matrix3x3)
|
||||
|
||||
# Get scale from diagonals and shear from off-diagonals
|
||||
S = np.diagonal(SK).copy() # Copy since it's read only
|
||||
K = (SK * (I3 == 0.0)) / S[:, np.newaxis] + I3
|
||||
|
||||
# Get reflection and ensure rotation is right-handed
|
||||
if np.linalg.det(RN) < 0:
|
||||
# Reflections are present
|
||||
R = RN * -1
|
||||
N = np.array(-1, dtype=dtype_out)
|
||||
else:
|
||||
R = RN
|
||||
N = np.array(1, dtype=dtype_out)
|
||||
|
||||
if homogeneous:
|
||||
T4 = I4.copy()
|
||||
T4[:3, 3] = T
|
||||
|
||||
R4 = I4.copy()
|
||||
R4[:3, :3] = R
|
||||
|
||||
N4 = I4.copy()
|
||||
N4[:3, :3] = I3 * N
|
||||
|
||||
S4 = I4.copy()
|
||||
S4[:3, :3] = I3 * S
|
||||
|
||||
K4 = I4.copy()
|
||||
K4[:3, :3] = K
|
||||
|
||||
return T4, R4, N4, S4, K4
|
||||
return T, R, N, S, K
|
||||
|
||||
|
||||
def _polar_decomposition(a: NumpyArray[float]) -> tuple[NumpyArray[float], NumpyArray[float]]:
|
||||
# Decompose `a=up` where u is orthonormal and p is positive semi-definite
|
||||
# See scipy.linalg.polar for details
|
||||
w, s, vh = np.linalg.svd(a, full_matrices=False)
|
||||
u = w.dot(vh)
|
||||
p = (vh.T.conj() * s).dot(vh)
|
||||
return u, p
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Wrapper mapping.
|
||||
|
||||
Setting ``pyvista._wrappers`` allows for developers to override the default class used
|
||||
to coerce a :vtk:`vtkDataSet` into a pyvista object. This is useful when creating a
|
||||
subclass of a :class:`pyvista.DataSet` class.
|
||||
|
||||
Examples
|
||||
--------
|
||||
A user-defined Foo class is defined that extends the functionality of
|
||||
:class:`pyvista.PolyData`. This class is set as the default wrapper for
|
||||
:vtk:`vtkPolyData` objects.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> default_wrappers = pv._wrappers.copy()
|
||||
>>> class Foo(pv.PolyData):
|
||||
... pass # Extend PolyData here
|
||||
>>> pv._wrappers['vtkPolyData'] = Foo
|
||||
>>> image = pv.ImageData()
|
||||
>>> surface = image.extract_surface()
|
||||
>>> assert isinstance(surface, Foo)
|
||||
>>> pv._wrappers = default_wrappers # reset back to default
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
from . import _vtk_core as _vtk
|
||||
from .composite import MultiBlock
|
||||
from .grid import ImageData
|
||||
from .grid import RectilinearGrid
|
||||
from .objects import Table
|
||||
from .partitioned import PartitionedDataSet
|
||||
from .pointset import ExplicitStructuredGrid
|
||||
from .pointset import PointSet
|
||||
from .pointset import PolyData
|
||||
from .pointset import StructuredGrid
|
||||
from .pointset import UnstructuredGrid
|
||||
|
||||
_wrappers = {
|
||||
'vtkExplicitStructuredGrid': ExplicitStructuredGrid,
|
||||
'vtkUnstructuredGrid': UnstructuredGrid,
|
||||
'vtkRectilinearGrid': RectilinearGrid,
|
||||
'vtkStructuredGrid': StructuredGrid,
|
||||
'vtkPolyData': PolyData,
|
||||
'vtkImageData': ImageData,
|
||||
'vtkStructuredPoints': ImageData,
|
||||
'vtkMultiBlockDataSet': MultiBlock,
|
||||
'vtkTable': Table,
|
||||
'vtkPointSet': PointSet,
|
||||
'vtkPartitionedDataSet': PartitionedDataSet,
|
||||
# 'vtkParametricSpline': pyvista.Spline,
|
||||
}
|
||||
|
||||
_WrappableVTKDataObjectType = TypeVar( # noqa: PYI018
|
||||
'_WrappableVTKDataObjectType',
|
||||
_vtk.vtkExplicitStructuredGrid,
|
||||
_vtk.vtkUnstructuredGrid,
|
||||
_vtk.vtkRectilinearGrid,
|
||||
_vtk.vtkStructuredGrid,
|
||||
_vtk.vtkPolyData,
|
||||
_vtk.vtkImageData,
|
||||
_vtk.vtkStructuredPoints,
|
||||
_vtk.vtkMultiBlockDataSet,
|
||||
_vtk.vtkTable,
|
||||
_vtk.vtkPoints,
|
||||
_vtk.vtkPartitionedDataSet,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
"""PyVista Demos."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pyvista.demos.demos import glyphs as glyphs
|
||||
from pyvista.demos.demos import orientation_cube as orientation_cube
|
||||
from pyvista.demos.demos import orientation_plotter as orientation_plotter
|
||||
from pyvista.demos.demos import plot_ants_plane as plot_ants_plane
|
||||
from pyvista.demos.demos import plot_beam as plot_beam
|
||||
from pyvista.demos.demos import plot_datasets as plot_datasets
|
||||
from pyvista.demos.demos import plot_glyphs as plot_glyphs
|
||||
from pyvista.demos.demos import plot_wave as plot_wave
|
||||
from pyvista.demos.logo import logo_atomized as logo_atomized
|
||||
from pyvista.demos.logo import logo_basic as logo_basic
|
||||
from pyvista.demos.logo import logo_letters as logo_letters
|
||||
from pyvista.demos.logo import logo_voxel as logo_voxel
|
||||
from pyvista.demos.logo import plot_logo as plot_logo
|
||||
@@ -0,0 +1,575 @@
|
||||
"""Demos to show off the functionality of PyVista."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista import examples
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
from .logo import text_3d
|
||||
|
||||
|
||||
def glyphs(grid_sz=3):
|
||||
"""Create several parametric supertoroids using VTK's glyph table functionality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
grid_sz : int, default: 3
|
||||
Create ``grid_sz x grid_sz`` supertoroids.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Mesh of supertoroids.
|
||||
|
||||
See Also
|
||||
--------
|
||||
plot_glyphs
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> mesh = demos.glyphs()
|
||||
>>> mesh.plot()
|
||||
|
||||
"""
|
||||
# Seed rng for reproducible plots
|
||||
rng = np.random.default_rng(seed=0)
|
||||
|
||||
n = 10
|
||||
values = np.arange(n) # values for scalars to look up glyphs by
|
||||
|
||||
# taken from:
|
||||
params = rng.uniform(0.5, 2, size=(n, 2)) # (n1, n2) parameters for the toroids
|
||||
|
||||
geoms = [pyvista.ParametricSuperToroid(n1=n1, n2=n2) for n1, n2 in params]
|
||||
|
||||
# get dataset where to put glyphs
|
||||
grid_sz = float(grid_sz)
|
||||
x, y, z = np.mgrid[:grid_sz, :grid_sz, :grid_sz]
|
||||
mesh = pyvista.StructuredGrid(x, y, z)
|
||||
|
||||
# add random scalars
|
||||
rng_int = rng.integers(0, n, size=x.size)
|
||||
mesh.point_data['scalars'] = rng_int
|
||||
|
||||
# construct the glyphs on top of the mesh; don't scale by scalars now
|
||||
return mesh.glyph(
|
||||
geom=geoms,
|
||||
indices=values,
|
||||
scale=False,
|
||||
factor=0.3,
|
||||
rng=(0, n - 1),
|
||||
orient=False,
|
||||
)
|
||||
|
||||
|
||||
def plot_glyphs(grid_sz=3, **kwargs):
|
||||
"""Plot several parametric supertoroids using VTK's glyph table functionality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
grid_sz : int, default: 3
|
||||
Create ``grid_sz x grid_sz`` supertoroids.
|
||||
|
||||
**kwargs : dict, optional
|
||||
All additional keyword arguments will be passed to
|
||||
:func:`pyvista.Plotter.add_mesh`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list | np.ndarray | ipywidgets.Widget
|
||||
See :func:`show <pyvista.Plotter.show>`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> demos.plot_glyphs()
|
||||
|
||||
"""
|
||||
# construct the glyphs on top of the mesh; don't scale by scalars now
|
||||
mesh = glyphs(grid_sz)
|
||||
|
||||
kwargs.setdefault('specular', 1)
|
||||
kwargs.setdefault('specular_power', 15)
|
||||
kwargs.setdefault('smooth_shading', True)
|
||||
|
||||
# create plotter and add our glyphs with some nontrivial lighting
|
||||
plotter = pyvista.Plotter()
|
||||
plotter.add_mesh(mesh, show_scalar_bar=False, **kwargs)
|
||||
return plotter.show()
|
||||
|
||||
|
||||
def orientation_cube():
|
||||
"""Return a dictionary containing the meshes composing an orientation cube.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Dictionary containing the meshes composing an orientation cube.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Load the cube mesh and plot it
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import demos
|
||||
>>> ocube = demos.orientation_cube()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(ocube['cube'], show_edges=True)
|
||||
>>> _ = pl.add_mesh(ocube['x_p'], color='blue')
|
||||
>>> _ = pl.add_mesh(ocube['x_n'], color='blue')
|
||||
>>> _ = pl.add_mesh(ocube['y_p'], color='green')
|
||||
>>> _ = pl.add_mesh(ocube['y_n'], color='green')
|
||||
>>> _ = pl.add_mesh(ocube['z_p'], color='red')
|
||||
>>> _ = pl.add_mesh(ocube['z_n'], color='red')
|
||||
>>> pl.show_axes()
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
cube = pyvista.Cube()
|
||||
|
||||
x_p = text_3d('X+', depth=0.2)
|
||||
x_p.points *= 0.45
|
||||
x_p.rotate_y(90, inplace=True)
|
||||
x_p.rotate_x(90, inplace=True)
|
||||
x_p.translate(-np.array(x_p.center), inplace=True)
|
||||
x_p.translate([0.5, 0, 0], inplace=True)
|
||||
# x_p.point_data['mesh'] = 1
|
||||
|
||||
x_n = text_3d('X-', depth=0.2)
|
||||
x_n.points *= 0.45
|
||||
x_n.rotate_y(90, inplace=True)
|
||||
x_n.rotate_x(90, inplace=True)
|
||||
x_n.rotate_z(180, inplace=True)
|
||||
x_n.translate(-np.array(x_n.center), inplace=True)
|
||||
x_n.translate([-0.5, 0, 0], inplace=True)
|
||||
# x_n.point_data['mesh'] = 2
|
||||
|
||||
y_p = text_3d('Y+', depth=0.2)
|
||||
y_p.points *= 0.45
|
||||
y_p.rotate_x(90, inplace=True)
|
||||
y_p.rotate_z(180, inplace=True)
|
||||
y_p.translate(-np.array(y_p.center), inplace=True)
|
||||
y_p.translate([0, 0.5, 0], inplace=True)
|
||||
# y_p.point_data['mesh'] = 3
|
||||
|
||||
y_n = text_3d('Y-', depth=0.2)
|
||||
y_n.points *= 0.45
|
||||
y_n.rotate_x(90, inplace=True)
|
||||
y_n.translate(-np.array(y_n.center), inplace=True)
|
||||
y_n.translate([0, -0.5, 0], inplace=True)
|
||||
# y_n.point_data['mesh'] = 4
|
||||
|
||||
z_p = text_3d('Z+', depth=0.2)
|
||||
z_p.points *= 0.45
|
||||
z_p.rotate_z(90, inplace=True)
|
||||
z_p.translate(-np.array(z_p.center), inplace=True)
|
||||
z_p.translate([0, 0, 0.5], inplace=True)
|
||||
# z_p.point_data['mesh'] = 5
|
||||
|
||||
z_n = text_3d('Z-', depth=0.2)
|
||||
z_n.points *= 0.45
|
||||
z_n.rotate_x(180, inplace=True)
|
||||
z_n.translate(-np.array(z_n.center), inplace=True)
|
||||
z_n.translate([0, 0, -0.5], inplace=True)
|
||||
|
||||
return {
|
||||
'cube': cube,
|
||||
'x_p': x_p,
|
||||
'x_n': x_n,
|
||||
'y_p': y_p,
|
||||
'y_n': y_n,
|
||||
'z_p': z_p,
|
||||
'z_n': z_n,
|
||||
}
|
||||
|
||||
|
||||
def orientation_plotter():
|
||||
"""Return a plotter containing the orientation cube.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Plotter
|
||||
Orientation cube plotter.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> plotter = demos.orientation_plotter()
|
||||
>>> plotter.show()
|
||||
|
||||
"""
|
||||
ocube = orientation_cube()
|
||||
pl = pyvista.Plotter()
|
||||
pl.add_mesh(ocube['cube'], show_edges=True)
|
||||
pl.add_mesh(ocube['x_p'], color='blue')
|
||||
pl.add_mesh(ocube['x_n'], color='blue')
|
||||
pl.add_mesh(ocube['y_p'], color='green')
|
||||
pl.add_mesh(ocube['y_n'], color='green')
|
||||
pl.add_mesh(ocube['z_p'], color='red')
|
||||
pl.add_mesh(ocube['z_n'], color='red')
|
||||
pl.show_axes() # type: ignore[call-arg]
|
||||
return pl
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def plot_wave(fps=30, frequency=1, wavetime=3, notebook=None): # noqa: PLR0917
|
||||
"""Plot a 3D moving wave in a render window.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fps : int, default: 30
|
||||
Maximum frames per second to display.
|
||||
|
||||
frequency : float, default: 1.0
|
||||
Wave cycles per second (Hz).
|
||||
|
||||
wavetime : float, default: 3.0
|
||||
The desired total display time in seconds.
|
||||
|
||||
notebook : bool, optional
|
||||
When ``True``, the resulting plot is placed inline a jupyter
|
||||
notebook. Assumes a jupyter console is active.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Position of points at last frame.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> out = demos.plot_wave()
|
||||
|
||||
"""
|
||||
# camera position
|
||||
cpos = [
|
||||
(6.879481857604187, -32.143727535933195, 23.05622921691103),
|
||||
(-0.2336056403734026, -0.6960083534590372, -0.7226721553894022),
|
||||
(-0.008900669873416645, 0.6018246347860926, 0.7985786667826725),
|
||||
]
|
||||
|
||||
# Make data
|
||||
X = np.arange(-10, 10, 0.25)
|
||||
Y = np.arange(-10, 10, 0.25)
|
||||
X, Y = np.meshgrid(X, Y)
|
||||
R = np.sqrt(X**2 + Y**2)
|
||||
Z = np.sin(R)
|
||||
|
||||
# Create and plot structured grid
|
||||
sgrid = pyvista.StructuredGrid(X, Y, Z)
|
||||
|
||||
mesh = sgrid.extract_surface()
|
||||
mesh['Height'] = Z.ravel()
|
||||
|
||||
# Start a plotter object and set the scalars to the Z height
|
||||
plotter = pyvista.Plotter(notebook=notebook)
|
||||
plotter.add_mesh(mesh, scalars='Height', show_scalar_bar=False, smooth_shading=True)
|
||||
plotter.camera_position = cpos
|
||||
plotter.show(
|
||||
title='Wave Example',
|
||||
window_size=[800, 600],
|
||||
auto_close=False,
|
||||
interactive_update=True,
|
||||
)
|
||||
|
||||
# Update Z and display a frame for each updated position
|
||||
tdelay = 1.0 / fps
|
||||
tlast = time.time()
|
||||
tstart = time.time()
|
||||
while time.time() - tstart < wavetime:
|
||||
# get phase from start
|
||||
telap = time.time() - tstart
|
||||
phase = telap * 2 * np.pi * frequency
|
||||
Z = np.sin(R + phase)
|
||||
mesh.points[:, -1] = Z.ravel()
|
||||
mesh['Height'] = Z.ravel()
|
||||
|
||||
mesh.compute_normals(inplace=True)
|
||||
|
||||
# Render and get time to render
|
||||
plotter.update()
|
||||
|
||||
# time delay
|
||||
tpast = time.time() - tlast
|
||||
if tpast < tdelay and tpast >= 0 and not plotter.off_screen:
|
||||
time.sleep(tdelay - tpast)
|
||||
|
||||
# store when rendering complete
|
||||
tlast = time.time()
|
||||
|
||||
# Close movie and delete object
|
||||
plotter.close()
|
||||
return mesh.points
|
||||
|
||||
|
||||
def plot_ants_plane(notebook=None):
|
||||
"""Plot two ants and airplane.
|
||||
|
||||
Demonstrate how to create a plot class to plot multiple meshes while
|
||||
adding scalars and text.
|
||||
|
||||
This example plots the following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
|
||||
Load and shrink airplane
|
||||
|
||||
>>> airplane = examples.load_airplane()
|
||||
>>> airplane.points /= 10
|
||||
|
||||
Rotate and translate ant so it is on the plane.
|
||||
|
||||
>>> ant = examples.load_ant()
|
||||
>>> _ = ant.rotate_x(90, inplace=True)
|
||||
>>> _ = ant.translate([90, 60, 15], inplace=True)
|
||||
|
||||
Make a copy and add another ant.
|
||||
|
||||
>>> ant_copy = ant.translate([30, 0, -10], inplace=False)
|
||||
|
||||
Create plotting object.
|
||||
|
||||
>>> plotter = pv.Plotter()
|
||||
>>> _ = plotter.add_mesh(ant, color='r')
|
||||
>>> _ = plotter.add_mesh(ant_copy, color='b')
|
||||
|
||||
Add airplane mesh and make the color equal to the Y position.
|
||||
|
||||
>>> plane_scalars = airplane.points[:, 1]
|
||||
>>> _ = plotter.add_mesh(
|
||||
... airplane,
|
||||
... scalars=plane_scalars,
|
||||
... scalar_bar_args={'title': 'Plane Y Location'},
|
||||
... )
|
||||
>>> _ = plotter.add_text('Ants and Plane Example')
|
||||
>>> plotter.show()
|
||||
|
||||
Parameters
|
||||
----------
|
||||
notebook : bool, optional
|
||||
When ``True``, the resulting plot is placed inline a jupyter
|
||||
notebook. Assumes a jupyter console is active.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> demos.plot_ants_plane()
|
||||
|
||||
"""
|
||||
# load and shrink airplane
|
||||
airplane = examples.load_airplane()
|
||||
airplane.points /= 10
|
||||
|
||||
# rotate and translate ant so it is on the plane
|
||||
ant = examples.load_ant()
|
||||
ant.rotate_x(90, inplace=True)
|
||||
ant.translate([90, 60, 15], inplace=True)
|
||||
|
||||
# Make a copy and add another ant
|
||||
ant_copy = ant.copy()
|
||||
ant_copy.translate([30, 0, -10], inplace=True)
|
||||
|
||||
# Create plotting object
|
||||
plotter = pyvista.Plotter(notebook=notebook)
|
||||
plotter.add_mesh(ant, color='r')
|
||||
plotter.add_mesh(ant_copy, color='b')
|
||||
|
||||
# Add airplane mesh and make the color equal to the Y position
|
||||
plane_scalars = airplane.points[:, 1]
|
||||
plotter.add_mesh(
|
||||
airplane,
|
||||
scalars=plane_scalars,
|
||||
scalar_bar_args={'title': 'Plane Y\nLocation'},
|
||||
)
|
||||
plotter.add_text('Ants and Plane Example')
|
||||
plotter.show()
|
||||
|
||||
|
||||
def plot_beam(notebook=None):
|
||||
"""Plot a beam with displacement.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
notebook : bool, optional
|
||||
When ``True``, the resulting plot is placed inline a jupyter
|
||||
notebook. Assumes a jupyter console is active.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> demos.plot_beam()
|
||||
|
||||
"""
|
||||
# Create fiticious displacements as a function of Z location
|
||||
grid = examples.load_hexbeam()
|
||||
d = grid.points[:, 2] ** 3 / 250
|
||||
grid.points[:, 1] += d
|
||||
|
||||
# Camera position
|
||||
cpos = [
|
||||
(11.915126303095157, 6.11392754955802, 3.6124956735471914),
|
||||
(0.0, 0.375, 2.0),
|
||||
(-0.42546442225230097, 0.9024244135964158, -0.06789847673314177),
|
||||
]
|
||||
|
||||
cmap = 'bwr'
|
||||
|
||||
# plot this displaced beam
|
||||
plotter = pyvista.Plotter(notebook=notebook)
|
||||
plotter.add_mesh(
|
||||
grid,
|
||||
scalars=d,
|
||||
scalar_bar_args={'title': 'Y Displacement'},
|
||||
rng=[-d.max(), d.max()],
|
||||
cmap=cmap, # type: ignore[arg-type]
|
||||
)
|
||||
plotter.camera_position = cpos
|
||||
plotter.add_text('Static Beam Example')
|
||||
plotter.show()
|
||||
|
||||
|
||||
def plot_datasets(dataset_type=None):
|
||||
"""Plot the pyvista dataset types.
|
||||
|
||||
This demo plots the following PyVista dataset types:
|
||||
|
||||
* :class:`pyvista.PolyData`
|
||||
* :class:`pyvista.UnstructuredGrid`
|
||||
* :class:`pyvista.ImageData`
|
||||
* :class:`pyvista.RectilinearGrid`
|
||||
* :class:`pyvista.StructuredGrid`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dataset_type : str, optional
|
||||
If set, plot just that dataset. Must be one of the following:
|
||||
|
||||
* ``'PolyData'``
|
||||
* ``'UnstructuredGrid'``
|
||||
* ``'ImageData'``
|
||||
* ``'RectilinearGrid'``
|
||||
* ``'StructuredGrid'``
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> demos.plot_datasets()
|
||||
|
||||
"""
|
||||
allowable_types = [
|
||||
'PolyData',
|
||||
'UnstructuredGrid',
|
||||
'ImageData',
|
||||
'RectilinearGrid',
|
||||
'StructuredGrid',
|
||||
]
|
||||
if dataset_type is not None and dataset_type not in allowable_types:
|
||||
msg = (
|
||||
f'Invalid dataset_type {dataset_type}. '
|
||||
f'Must be one of the following: {allowable_types}'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
###########################################################################
|
||||
# uniform grid
|
||||
image = pyvista.ImageData(dimensions=(6, 6, 1))
|
||||
image.spacing = (3, 2, 1)
|
||||
|
||||
###########################################################################
|
||||
# RectilinearGrid
|
||||
xrng = np.array([0, 0.3, 1, 4, 5, 6, 6.2, 6.6])
|
||||
yrng = np.linspace(-2, 2, 5)
|
||||
zrng = [1]
|
||||
rec_grid = pyvista.RectilinearGrid(xrng, yrng, zrng)
|
||||
|
||||
###########################################################################
|
||||
# structured grid
|
||||
ang = np.linspace(0, np.pi / 2, 10)
|
||||
r = np.linspace(6, 10, 8)
|
||||
z = [0]
|
||||
ang, r, z = np.meshgrid(ang, r, z) # type: ignore[assignment]
|
||||
|
||||
x = r * np.sin(ang)
|
||||
y = r * np.cos(ang)
|
||||
|
||||
struct_grid = pyvista.StructuredGrid(x[::-1], y[::-1], z[::-1])
|
||||
|
||||
###########################################################################
|
||||
# polydata
|
||||
points = pyvista.PolyData([[1.0, 2.0, 2.0], [2.0, 2.0, 2.0]])
|
||||
|
||||
line = pyvista.Line()
|
||||
line.points += np.array((2, 0, 0))
|
||||
line.clear_data()
|
||||
|
||||
tri = pyvista.Triangle()
|
||||
tri.points += np.array([0, 1, 0])
|
||||
circ = pyvista.Circle()
|
||||
circ.points += np.array([1.5, 1.5, 0])
|
||||
|
||||
poly = tri + circ
|
||||
|
||||
###########################################################################
|
||||
# unstructuredgrid
|
||||
pyr = pyvista.Pyramid()
|
||||
pyr.points *= 0.7
|
||||
cube = pyvista.Cube(center=(2, 0, 0))
|
||||
ugrid = circ + pyr + cube + tri
|
||||
|
||||
pl = pyvista.Plotter() if dataset_type is not None else pyvista.Plotter(shape='3/2')
|
||||
|
||||
# polydata
|
||||
if dataset_type is None:
|
||||
pl.subplot(0)
|
||||
pl.add_text('4. PolyData')
|
||||
if dataset_type in [None, 'PolyData']:
|
||||
pl.add_points(points, point_size=20)
|
||||
pl.add_mesh(line, line_width=5)
|
||||
pl.add_mesh(poly)
|
||||
pl.add_mesh(poly.extract_all_edges(), line_width=2, color='k')
|
||||
|
||||
# unstructuredgrid
|
||||
if dataset_type is None:
|
||||
pl.subplot(1)
|
||||
pl.add_text('5. UnstructuredGrid')
|
||||
if dataset_type in [None, 'UnstructuredGrid']:
|
||||
pl.add_mesh(ugrid)
|
||||
pl.add_mesh(ugrid.extract_all_edges(), line_width=2, color='k')
|
||||
|
||||
# ImageData
|
||||
if dataset_type is None:
|
||||
pl.subplot(2)
|
||||
pl.add_text('1. ImageData')
|
||||
if dataset_type in [None, 'ImageData']:
|
||||
pl.add_mesh(image)
|
||||
pl.add_mesh(image.extract_all_edges(), color='k', style='wireframe', line_width=2)
|
||||
pl.camera_position = 'xy'
|
||||
|
||||
# RectilinearGrid
|
||||
if dataset_type is None:
|
||||
pl.subplot(3)
|
||||
pl.add_text('2. RectilinearGrid')
|
||||
if dataset_type in [None, 'RectilinearGrid']:
|
||||
pl.add_mesh(rec_grid)
|
||||
pl.add_mesh(rec_grid.extract_all_edges(), color='k', style='wireframe', line_width=2)
|
||||
pl.camera_position = 'xy'
|
||||
|
||||
# StructuredGrid
|
||||
if dataset_type is None:
|
||||
pl.subplot(4)
|
||||
pl.add_text('3. StructuredGrid')
|
||||
if dataset_type in [None, 'StructuredGrid']:
|
||||
pl.add_mesh(struct_grid)
|
||||
pl.add_mesh(struct_grid.extract_all_edges(), color='k', style='wireframe', line_width=2)
|
||||
pl.camera_position = 'xy'
|
||||
|
||||
pl.show()
|
||||
@@ -0,0 +1,362 @@
|
||||
"""Generate the pyvista logo.
|
||||
|
||||
Logos generated with:
|
||||
plot_logo(screenshot='pyvista_logo.png', window_size=(1920, 1080))
|
||||
plot_logo(screenshot='pyvista_logo_sm.png', window_size=(960, 400), off_screen=True)
|
||||
|
||||
# different camera angle for square plot
|
||||
cpos = [(-0.3654543687422538, 1.1098808905156292, 9.073223697728247),
|
||||
(2.553950615449191, 0.34145688392081264, 0.06127122762851659),
|
||||
(0.019308531920309947, 0.996708840795678, -0.07873161547192065)]
|
||||
|
||||
plot_logo(screenshot='pyvista_logo_sm_sq.png', window_size=(960, 960), cpos=cpos,
|
||||
off_screen=True)
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista import examples
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core import _vtk_core as _vtk
|
||||
from pyvista.core.utilities.features import _voxelize_legacy
|
||||
|
||||
THIS_PATH = str(Path(os.path.realpath(__file__)).parent)
|
||||
|
||||
LOGO_TITLE = 'PyVista'
|
||||
|
||||
|
||||
def atomize(grid, shift_fac=0.1, scale=0.9):
|
||||
"""Break apart and shrink and/or scale the individual cells of a mesh.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
grid : pyvista.UnstructuredGrid
|
||||
The input mesh to atomize.
|
||||
shift_fac : float, default: 0.1
|
||||
Factor by which to shift the individual cells apart.
|
||||
scale : float, default: 0.9
|
||||
Factor by which to scale the individual cells.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
The atomized mesh with individually shifted and scaled cells.
|
||||
|
||||
"""
|
||||
cent = grid.center
|
||||
cells = []
|
||||
for i in range(grid.n_cells):
|
||||
cell = grid.extract_cells(i)
|
||||
ccent = np.array(cell.center)
|
||||
cell.points[:] = (cell.points - ccent) * scale + ccent
|
||||
cell.points += (ccent - np.array(cent)) * shift_fac
|
||||
cells.append(cell)
|
||||
|
||||
return cells[0].merge(cells[1:])
|
||||
|
||||
|
||||
def text_3d(string, depth=0.5):
|
||||
"""Create 3D text from a given string.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
string : str
|
||||
The string of text to convert into 3D text.
|
||||
|
||||
depth : float, default: 0.5
|
||||
The depth of the extrusion used to create the 3D text.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.DataSet
|
||||
The 3D text in the form of a PyVista DataSet.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:ref:`create_pixel_art_example`
|
||||
|
||||
"""
|
||||
from vtkmodules.vtkRenderingFreeType import vtkVectorText # noqa: PLC0415
|
||||
|
||||
vec_text = vtkVectorText()
|
||||
vec_text.SetText(string)
|
||||
|
||||
extrude = _vtk.vtkLinearExtrusionFilter()
|
||||
extrude.SetInputConnection(vec_text.GetOutputPort())
|
||||
extrude.SetExtrusionTypeToNormalExtrusion()
|
||||
extrude.SetVector(0, 0, 1)
|
||||
extrude.SetScaleFactor(depth)
|
||||
|
||||
tri_filter = _vtk.vtkTriangleFilter()
|
||||
tri_filter.SetInputConnection(extrude.GetOutputPort())
|
||||
tri_filter.Update()
|
||||
return pyvista.wrap(tri_filter.GetOutput())
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def logo_letters(merge=False, depth=0.3): # noqa: FBT002
|
||||
"""Generate a mesh for each letter in "PyVista".
|
||||
|
||||
Parameters
|
||||
----------
|
||||
merge : bool, optional
|
||||
If ``True``, merge the meshes of the individual letters into a single
|
||||
mesh. If ``False``, return a dictionary where the keys are the letters
|
||||
and the values are the respective meshes.
|
||||
depth : float, optional
|
||||
The depth of the extrusion for each letter in the mesh.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData or dict[str, pyvista.PolyData]
|
||||
If merge is ``True``, returns a single merged mesh containing all the
|
||||
letters in "PyVista". If merge is ``False``, returns a dictionary where
|
||||
the keys are the letters and the values are the respective meshes.
|
||||
|
||||
"""
|
||||
mesh_letters = pyvista.PolyData() if merge else {} # type: ignore[var-annotated]
|
||||
|
||||
# spacing between letters
|
||||
space_factor = 0.9
|
||||
width = 0
|
||||
for letter in LOGO_TITLE:
|
||||
mesh_letter = text_3d(letter, depth=depth)
|
||||
this_letter_width = mesh_letter.points[:, 0].max()
|
||||
mesh_letter.translate([width * space_factor, 0, 0.0], inplace=True)
|
||||
width += this_letter_width
|
||||
if merge:
|
||||
mesh_letters += mesh_letter
|
||||
else:
|
||||
mesh_letters[letter] = mesh_letter
|
||||
|
||||
return mesh_letters
|
||||
|
||||
|
||||
def logo_voxel(density=0.03):
|
||||
"""Create a voxelized PyVista logo.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
density : float, default: 0.03
|
||||
Density of the voxelization.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
Voxelized PyVista logo as an unstructured grid.
|
||||
|
||||
"""
|
||||
return _voxelize_legacy(text_3d(LOGO_TITLE, depth=0.3), density=density)
|
||||
|
||||
|
||||
def logo_basic():
|
||||
"""Create a basic pyvista logo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
Grid containing the pyvista letters.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Plot the basic pyvista logo.
|
||||
|
||||
>>> from pyvista import demos
|
||||
>>> logo = demos.logo_basic()
|
||||
>>> cpos = logo.plot(smooth_shading=True)
|
||||
|
||||
Add scalars and plot the logo.
|
||||
|
||||
>>> logo['x_coord'] = logo.points[:, 0]
|
||||
>>> cpos = logo.plot(
|
||||
... scalars='x_coord',
|
||||
... cmap='Spectral',
|
||||
... smooth_shading=True,
|
||||
... cpos='xy',
|
||||
... )
|
||||
|
||||
"""
|
||||
return logo_letters(merge=True).compute_normals(split_vertices=True)
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def plot_logo( # noqa: PLR0917
|
||||
window_size=None,
|
||||
off_screen=None,
|
||||
screenshot=None,
|
||||
cpos=None,
|
||||
just_return_plotter=False, # noqa: FBT002
|
||||
show_note=False, # noqa: FBT002
|
||||
**kwargs,
|
||||
):
|
||||
"""Plot the stylized PyVista logo.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
window_size : sequence[int], optional
|
||||
Size of the window in the format ``[width, height]``.
|
||||
off_screen : bool, optional
|
||||
Renders off screen when ``True``.
|
||||
screenshot : str, optional
|
||||
Save screenshot to path when specified.
|
||||
cpos : list or str, optional
|
||||
Camera position to use.
|
||||
just_return_plotter : bool, default: False
|
||||
Return the plotter instance without rendering.
|
||||
show_note : bool, default: False
|
||||
Show a text in the plot when ``True``.
|
||||
**kwargs : dict, optional
|
||||
Additional keyword arguments.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Plotter or camera position
|
||||
Returns the plotter instance if ``just_return_plotter`` is ``True``,
|
||||
otherwise returns the camera position if ``screenshot`` is specified,
|
||||
otherwise shows the plot.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import demos
|
||||
>>> cpos = demos.plot_logo()
|
||||
|
||||
"""
|
||||
# initialize plotter
|
||||
if window_size is None:
|
||||
window_size = [960, 400]
|
||||
plotter = pyvista.Plotter(window_size=window_size, off_screen=off_screen)
|
||||
|
||||
mesh_letters = logo_letters()
|
||||
|
||||
# letter 'P'
|
||||
p_mesh = mesh_letters['P'].compute_normals(split_vertices=True)
|
||||
plotter.add_mesh(p_mesh, color='#376fa0', smooth_shading=True)
|
||||
|
||||
# letter 'y'
|
||||
y_mesh = mesh_letters['y'].compute_normals(split_vertices=True)
|
||||
plotter.add_mesh(y_mesh, color='#ffd040', smooth_shading=True)
|
||||
|
||||
# letter 'V'
|
||||
v_grid = _voxelize_legacy(mesh_letters['V'], density=0.08)
|
||||
v_grid_atom = atomize(v_grid)
|
||||
v_grid_atom['scalars'] = v_grid_atom.points[:, 0]
|
||||
v_grid_atom_surf = v_grid_atom.extract_surface()
|
||||
faces = v_grid_atom_surf.faces.reshape(-1, 5).copy()
|
||||
faces[:, 1:] = faces[:, 1:][:, ::-1]
|
||||
v_grid_atom_surf.faces = faces
|
||||
plotter.add_mesh(
|
||||
v_grid_atom_surf,
|
||||
scalars='scalars',
|
||||
show_edges=True,
|
||||
cmap='winter',
|
||||
show_scalar_bar=False,
|
||||
)
|
||||
|
||||
# letter 'i'
|
||||
i_grid = _voxelize_legacy(mesh_letters['i'], density=0.1)
|
||||
|
||||
plotter.add_mesh(
|
||||
i_grid.extract_surface(),
|
||||
style='points',
|
||||
color='r',
|
||||
render_points_as_spheres=True,
|
||||
point_size=14,
|
||||
)
|
||||
plotter.add_mesh(i_grid, style='wireframe', color='k', line_width=4)
|
||||
|
||||
# letter 's'
|
||||
mesh = mesh_letters['s']
|
||||
mesh['scalars'] = mesh.points[:, 0]
|
||||
plotter.add_mesh(
|
||||
mesh,
|
||||
scalars='scalars',
|
||||
style='wireframe',
|
||||
line_width=2,
|
||||
cmap='gist_heat',
|
||||
backface_culling=True,
|
||||
render_lines_as_tubes=True,
|
||||
show_scalar_bar=False,
|
||||
)
|
||||
|
||||
# letter 't'
|
||||
mesh = mesh_letters['t'].clean().compute_normals()
|
||||
scalars = mesh.points[:, 0]
|
||||
plotter.add_mesh(mesh, scalars=scalars, show_edges=True, cmap='autumn', show_scalar_bar=False)
|
||||
|
||||
# letter 'a'
|
||||
grid = examples.download_letter_a()
|
||||
grid.points[:, 0] += mesh_letters['a'].center[0] - grid.center[0]
|
||||
|
||||
# select some cells from grid
|
||||
cells = grid.cells.reshape(-1, 5)
|
||||
mask = grid.points[cells[:, 1:], 2] < 0.2
|
||||
mask = mask.all(1)
|
||||
|
||||
a_part = grid.extract_cells(mask)
|
||||
|
||||
cells = a_part.cells.reshape(-1, 5)
|
||||
scalars = grid.points[cells[:, 1], 1]
|
||||
plotter.add_mesh(
|
||||
a_part, scalars=scalars, show_edges=True, cmap='Greens', show_scalar_bar=False
|
||||
)
|
||||
|
||||
if show_note:
|
||||
text = text_3d('You can move me!', depth=0.1)
|
||||
text.points *= 0.1
|
||||
text.translate([4.0, -0.3, 0], inplace=True)
|
||||
plotter.add_mesh(text, color='black')
|
||||
|
||||
# finalize plot and show it
|
||||
plotter.set_background(kwargs.pop('background', 'white'))
|
||||
plotter.camera_position = 'xy'
|
||||
if 'zoom' in kwargs:
|
||||
plotter.camera.zoom(kwargs.pop('zoom'))
|
||||
|
||||
# plotter.remove_scalar_bar()
|
||||
plotter.enable_anti_aliasing()
|
||||
|
||||
if just_return_plotter:
|
||||
return plotter
|
||||
|
||||
if screenshot: # pragma: no cover
|
||||
plotter.show(cpos=cpos, auto_close=False)
|
||||
plotter.screenshot(screenshot, True)
|
||||
cpos_final = plotter.camera_position
|
||||
plotter.close()
|
||||
return cpos_final
|
||||
else:
|
||||
return plotter.show(cpos=cpos, **kwargs)
|
||||
|
||||
|
||||
def logo_atomized(density=0.05, scale=0.6, depth=0.05):
|
||||
"""Generate a voxelized pyvista logo with intra-cell spacing.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
density : float, default: 0.05
|
||||
The spacing between voxels in the generated PyVista logo.
|
||||
scale : float, default: 0.6
|
||||
The scaling factor for the generated PyVista logo.
|
||||
depth : float, default: 0.05
|
||||
The depth of the generated PyVista logo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
A merged UnstructuredGrid representing the voxelized PyVista logo.
|
||||
|
||||
"""
|
||||
mesh_letters = logo_letters(depth=depth)
|
||||
grids = []
|
||||
for letter in mesh_letters.values():
|
||||
grid = _voxelize_legacy(letter, density=density)
|
||||
grids.append(atomize(grid, scale=scale))
|
||||
|
||||
return grids[0].merge(grids[1:])
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Deprecated utilities subpackage."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import inspect
|
||||
import warnings
|
||||
|
||||
# Places to look for the utility
|
||||
_MODULES = [
|
||||
'pyvista.core.errors',
|
||||
'pyvista.plotting.errors',
|
||||
]
|
||||
|
||||
|
||||
def _try_import(module, name):
|
||||
"""Attempt to import a module."""
|
||||
_module = importlib.import_module(module)
|
||||
try:
|
||||
feature = inspect.getattr_static(_module, name)
|
||||
import_path = f'from {module} import {name}'
|
||||
except AttributeError:
|
||||
return None, None
|
||||
return feature, import_path
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
"""Fetch an attribute ``name`` from ``globals()`` and warn if it's from a deprecated module.
|
||||
|
||||
Note that ``__getattr__()`` only gets called when ``name`` is missing
|
||||
from the module's globals. The trick is that we want to import this
|
||||
function into other deprecated modules, and we want to carry this
|
||||
subpackage's globals along to prevent some spurious warnings.
|
||||
|
||||
Raises
|
||||
------
|
||||
AttributeError
|
||||
If the attribute is not found in ``globals()`` and also could not be
|
||||
imported from the modules in ``_MODULES``.
|
||||
|
||||
Warns
|
||||
-----
|
||||
PyVistaDeprecationWarning
|
||||
If the attribute has been found via importing from the modules in
|
||||
``_MODULES``, as this implies that the feature has been moved from
|
||||
``pyvista.utilities``.
|
||||
|
||||
"""
|
||||
from pyvista.core.errors import PyVistaDeprecationWarning # noqa: PLC0415
|
||||
|
||||
for module in _MODULES:
|
||||
feature, import_path = _try_import(module, name)
|
||||
if feature is not None:
|
||||
break
|
||||
else: # pragma: no cover
|
||||
msg = (
|
||||
f'Module `pyvista.errors` has been deprecated and we could not automatically '
|
||||
f'find `{name}`. This feature has moved.'
|
||||
)
|
||||
raise AttributeError(msg) from None
|
||||
|
||||
message = (
|
||||
f'The `pyvista.errors` module has been deprecated. '
|
||||
f'`{name}` is now imported as: `{import_path}`.'
|
||||
)
|
||||
|
||||
warnings.warn(
|
||||
message,
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
|
||||
return feature
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 452 KiB |
@@ -0,0 +1,11 @@
|
||||
"""Examples module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from . import download_3ds as download_3ds
|
||||
from . import gltf as gltf
|
||||
from . import planets as planets
|
||||
from . import vrml as vrml
|
||||
from .cells import plot_cell as plot_cell
|
||||
from .downloads import *
|
||||
from .examples import *
|
||||
@@ -0,0 +1,843 @@
|
||||
"""Abstraction layer for downloading, reading, and loading dataset files.
|
||||
|
||||
The classes and methods in this module define an API for working with either
|
||||
a single file or multiple files which may be downloaded and/or loaded as an
|
||||
example dataset.
|
||||
|
||||
Many datasets have a straightforward input to output mapping:
|
||||
file -> read -> dataset
|
||||
|
||||
However, some file formats require multiple input files for reading (e.g.
|
||||
separate data and header files):
|
||||
(file1, file1) -> read -> dataset
|
||||
|
||||
Or, a dataset may be combination of two separate datasets:
|
||||
file1 -> read -> dataset1 ┬─> combined_dataset
|
||||
file2 -> read -> dataset2 ┘
|
||||
|
||||
In some cases, the input may be a folder instead of a file (e.g. DICOM):
|
||||
folder -> read -> dataset
|
||||
|
||||
In addition, there may be a need to customize the reading function to read
|
||||
files with specific options enabled (e.g. set a time value), or perform
|
||||
post-read processing to modify the dataset (e.g. set active scalars).
|
||||
|
||||
This module aims to serve these use cases and provide a flexible way of
|
||||
downloading, reading, and processing files with a generic mapping:
|
||||
file or files or folder -> fully processed dataset(s) in any form
|
||||
|
||||
"""
|
||||
|
||||
# mypy: disable-error-code="redundant-expr"
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Sequence
|
||||
import functools
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
from typing import Generic
|
||||
from typing import Protocol
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
from typing import cast
|
||||
from typing import final
|
||||
from typing import runtime_checkable
|
||||
|
||||
import pyvista as pv
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
from pyvista.core.utilities.fileio import get_ext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
# Define TypeVars for two main class definitions used by this module:
|
||||
# 1. classes for single file inputs: T -> T
|
||||
# 2. classes for multi-file inputs: (T, ...) -> (T, ...)
|
||||
# Any properties with these typevars should have a one-to-one mapping for all files
|
||||
_FilePropStrType_co = TypeVar(
|
||||
'_FilePropStrType_co',
|
||||
str,
|
||||
tuple[str, ...],
|
||||
covariant=True,
|
||||
)
|
||||
_FilePropIntType_co = TypeVar(
|
||||
'_FilePropIntType_co',
|
||||
int,
|
||||
tuple[int, ...],
|
||||
covariant=True,
|
||||
)
|
||||
|
||||
DatasetObject = Union[pv.DataSet, pv.Texture, NumpyArray[Any], pv.MultiBlock]
|
||||
DatasetType = Union[
|
||||
type[pv.DataSet],
|
||||
type[pv.Texture],
|
||||
type[NumpyArray[Any]],
|
||||
type[pv.MultiBlock],
|
||||
]
|
||||
|
||||
|
||||
class _BaseFilePropsProtocol(Generic[_FilePropStrType_co, _FilePropIntType_co]):
|
||||
@property
|
||||
@abstractmethod
|
||||
def path(self) -> _FilePropStrType_co:
|
||||
"""Return the path(s) of all files."""
|
||||
|
||||
@property
|
||||
def num_files(self) -> int:
|
||||
"""Return the number of files from path or paths.
|
||||
|
||||
If a path is a folder, the number of files contained in the folder is returned.
|
||||
"""
|
||||
path = self.path
|
||||
paths = [path] if isinstance(path, str) else path
|
||||
return sum(1 if os.path.isfile(p) else len(_get_all_nested_filepaths(p)) for p in paths)
|
||||
|
||||
@property
|
||||
def unique_extension(self) -> str | tuple[str, ...]:
|
||||
"""Return the unique file extension(s) from all files."""
|
||||
return _get_unique_extension(self.path)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _filesize_bytes(self) -> _FilePropIntType_co:
|
||||
"""Return the file size(s) of all files in bytes."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _filesize_format(self) -> _FilePropStrType_co:
|
||||
"""Return the formatted size of all file(s)."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _total_size_bytes(self) -> int:
|
||||
"""Return the total size of all files in bytes."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def total_size(self) -> str:
|
||||
"""Return the total size of all files formatted as a string."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def _reader(
|
||||
self,
|
||||
) -> pv.BaseReader | tuple[pv.BaseReader | None, ...] | None:
|
||||
"""Return the base file reader(s) used to read the files."""
|
||||
|
||||
@property
|
||||
def unique_reader_type(
|
||||
self,
|
||||
) -> type[pv.BaseReader] | tuple[type[pv.BaseReader], ...] | None:
|
||||
"""Return unique reader type(s) from all file readers."""
|
||||
return _get_unique_reader_type(self._reader)
|
||||
|
||||
|
||||
class _SingleFilePropsProtocol(_BaseFilePropsProtocol[str, int]):
|
||||
"""Define file properties of a single file."""
|
||||
|
||||
|
||||
class _MultiFilePropsProtocol(
|
||||
_BaseFilePropsProtocol[tuple[str, ...], tuple[int, ...]],
|
||||
):
|
||||
"""Define file properties of multiple files."""
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class _Downloadable(Protocol[_FilePropStrType_co]):
|
||||
"""Class which downloads file(s) from a source."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def source_name(self) -> _FilePropStrType_co:
|
||||
"""Return the name of the download relative to the base url."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def base_url(self) -> _FilePropStrType_co:
|
||||
"""Return the base url of the download."""
|
||||
|
||||
@property
|
||||
def source_url_raw(self) -> _FilePropStrType_co:
|
||||
"""Return the raw source of the download.
|
||||
|
||||
This is the full URL used to download the data directly.
|
||||
"""
|
||||
name = self.source_name
|
||||
name_iter = [name] if isinstance(name, str) else name
|
||||
url = self.base_url
|
||||
base_url_iter = [url] if isinstance(url, str) else url
|
||||
url_raw = list(map(os.path.join, base_url_iter, name_iter))
|
||||
return url_raw[0] if isinstance(name, str) else tuple(url_raw)
|
||||
|
||||
@property
|
||||
def source_url_blob(self) -> _FilePropStrType_co:
|
||||
"""Return the blob source of the download.
|
||||
|
||||
This URL is useful for linking to the source webpage for
|
||||
a human to open on a browser.
|
||||
"""
|
||||
# Make single urls iterable and replace 'raw' with 'blob'
|
||||
url_raw = self.source_url_raw
|
||||
url_iter = [url_raw] if isinstance(url_raw, str) else url_raw
|
||||
url_blob = [url.replace('/raw/', '/blob/') for url in url_iter]
|
||||
return url_blob[0] if isinstance(url_raw, str) else tuple(url_blob)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def path(self) -> _FilePropStrType_co:
|
||||
"""Return the file path of downloaded file."""
|
||||
|
||||
@abstractmethod
|
||||
def download(self) -> _FilePropStrType_co:
|
||||
"""Download and return the file path(s)."""
|
||||
|
||||
|
||||
class _DatasetLoader:
|
||||
"""Load a dataset."""
|
||||
|
||||
def __init__(self, load_func: Callable[..., DatasetObject]):
|
||||
self._load_func = load_func
|
||||
self._dataset: DatasetObject | None = None
|
||||
|
||||
@property
|
||||
@final
|
||||
def dataset(self) -> DatasetObject | None:
|
||||
"""Return the loaded dataset object(s)."""
|
||||
return self._dataset
|
||||
|
||||
def load(self, *args, **kwargs) -> DatasetObject:
|
||||
"""Load and return the dataset."""
|
||||
# Subclasses should override this as needed
|
||||
return self._load_func(*args, **kwargs)
|
||||
|
||||
@final
|
||||
def load_and_store_dataset(self) -> DatasetObject:
|
||||
"""Load the dataset and store it."""
|
||||
dataset = self.load()
|
||||
self._dataset = dataset
|
||||
return dataset
|
||||
|
||||
@final
|
||||
def clear_dataset(self):
|
||||
"""Clear the stored dataset object from memory."""
|
||||
del self._dataset
|
||||
|
||||
@property
|
||||
@final
|
||||
def dataset_iterable(self) -> tuple[DatasetObject, ...]:
|
||||
"""Return a tuple of all dataset object(s), including any nested objects.
|
||||
|
||||
If the dataset is a MultiBlock, the MultiBlock itself is also returned as the first
|
||||
item. Any nested MultiBlocks are not included, only their datasets.
|
||||
|
||||
E.g. for a composite dataset:
|
||||
MultiBlock -> (MultiBlock, Block0, Block1, ...)
|
||||
"""
|
||||
dataset = self.dataset
|
||||
|
||||
def _flat(obj):
|
||||
if isinstance(obj, Sequence):
|
||||
output_list = [] # type: ignore[var-annotated]
|
||||
for item in obj:
|
||||
(
|
||||
output_list.extend(item)
|
||||
if isinstance(item, Sequence)
|
||||
else output_list.append(item)
|
||||
)
|
||||
if any(isinstance(item, Sequence) for item in output_list):
|
||||
return _flat(output_list)
|
||||
return output_list
|
||||
else:
|
||||
return [obj]
|
||||
|
||||
flat = _flat(dataset)
|
||||
if isinstance(dataset, pv.MultiBlock):
|
||||
flat.insert(0, dataset)
|
||||
return tuple(flat)
|
||||
|
||||
@property
|
||||
@final
|
||||
def unique_dataset_type(
|
||||
self,
|
||||
) -> DatasetType | tuple[DatasetType, ...] | None:
|
||||
"""Return unique dataset type(s) from all datasets."""
|
||||
return _get_unique_dataset_type(self.dataset_iterable)
|
||||
|
||||
@property
|
||||
@final
|
||||
def unique_cell_types(
|
||||
self,
|
||||
) -> tuple[pv.CellType, ...]:
|
||||
"""Return unique cell types from all datasets."""
|
||||
cell_types: dict[pv.CellType, None] = {}
|
||||
for data in self.dataset_iterable:
|
||||
# Get the underlying dataset for the texture
|
||||
dataset = (
|
||||
cast('pv.ImageData', pv.wrap(data.GetInput()))
|
||||
if isinstance(data, pv.Texture)
|
||||
else data
|
||||
)
|
||||
try:
|
||||
if isinstance(dataset, pv.ExplicitStructuredGrid):
|
||||
# extract_cells_by_type does not support this datatype
|
||||
# so get cells manually
|
||||
cells = (c.type for c in dataset.cell)
|
||||
[cell_types.update({cell_type: None}) for cell_type in cells]
|
||||
else:
|
||||
for cell_type in pv.CellType:
|
||||
extracted = dataset.extract_cells_by_type(cell_type) # type: ignore[union-attr]
|
||||
if extracted.n_cells > 0:
|
||||
cell_types[cell_type] = None
|
||||
except AttributeError:
|
||||
continue
|
||||
return tuple(sorted(cell_types.keys()))
|
||||
|
||||
|
||||
class _SingleFile(_SingleFilePropsProtocol):
|
||||
"""Wrap a single file."""
|
||||
|
||||
def __init__(self, path):
|
||||
from pyvista.examples.downloads import USER_DATA_PATH # noqa: PLC0415
|
||||
|
||||
self._path = (
|
||||
path if path is None or os.path.isabs(path) else os.path.join(USER_DATA_PATH, path)
|
||||
)
|
||||
|
||||
@property
|
||||
def path(self) -> str:
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def _filesize_bytes(self) -> int:
|
||||
return _get_file_or_folder_size(self.path)
|
||||
|
||||
@property
|
||||
def _filesize_format(self) -> str:
|
||||
return _format_file_size(self._filesize_bytes)
|
||||
|
||||
@property
|
||||
def _total_size_bytes(self) -> int:
|
||||
return self._filesize_bytes
|
||||
|
||||
@property
|
||||
def total_size(self) -> str:
|
||||
return self._filesize_format
|
||||
|
||||
@property
|
||||
def _reader(self) -> pv.BaseReader | None:
|
||||
return None
|
||||
|
||||
|
||||
class _SingleFileDatasetLoader(_SingleFile, _DatasetLoader):
|
||||
"""Wrap a single file for loading.
|
||||
|
||||
Specify the read function and/or load functions for reading and processing the
|
||||
dataset. The read function is called on the file path first, then, if a load
|
||||
function is specified, the load function is called on the output from the read
|
||||
function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path
|
||||
Path of the file to be loaded.
|
||||
|
||||
read_func
|
||||
Specify the function used to read the file. Defaults to :func:`pyvista.read`.
|
||||
This can be used for customizing the reader's properties, or using another
|
||||
read function (e.g. :func:`pyvista.read_texture` for textures). The function
|
||||
must have the file path as the first argument and should return a dataset.
|
||||
If default arguments are required by your desired read function, consider
|
||||
using :class:`functools.partial` to pre-set the arguments before passing it
|
||||
as an argument to the loader.
|
||||
|
||||
load_func
|
||||
Specify the function used to load the file. Defaults to `None`. This is typically
|
||||
used to specify any processing of the dataset after reading. The load function
|
||||
typically will accept a dataset as an input and return a dataset.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: str,
|
||||
read_func: Callable[[str], DatasetType] | None = None,
|
||||
load_func: Callable[[DatasetType], Any] | None = None,
|
||||
):
|
||||
_SingleFile.__init__(self, path)
|
||||
_DatasetLoader.__init__(self, load_func) # type: ignore[arg-type]
|
||||
self._read_func = pv.read if path and read_func is None else read_func
|
||||
|
||||
@property
|
||||
def _reader(self) -> pv.BaseReader | None:
|
||||
# TODO: return the actual reader used, and not just a lookup
|
||||
# (this will require an update to the 'read_func' API)
|
||||
try:
|
||||
return pv.get_reader(self.path)
|
||||
except ValueError:
|
||||
# Cannot be read directly (requires custom reader)
|
||||
return None
|
||||
|
||||
@property
|
||||
def path_loadable(self) -> str:
|
||||
return self.path
|
||||
|
||||
def load(self):
|
||||
path = self.path
|
||||
read_func = self._read_func
|
||||
load_func = self._load_func
|
||||
try:
|
||||
# Read and load normally
|
||||
return read_func(path) if load_func is None else load_func(read_func(path)) # type: ignore[misc]
|
||||
except OSError:
|
||||
# Handle error generated by pv.read if reading a directory
|
||||
if read_func is pv.read and Path(path).is_dir():
|
||||
# Re-define read function to read all files in a directory as a multiblock
|
||||
read_func = lambda path: _load_as_multiblock( # type: ignore[assignment]
|
||||
[
|
||||
_SingleFileDatasetLoader(str(Path(path, fname)))
|
||||
for fname in sorted(os.listdir(path)) # noqa: PTH208
|
||||
],
|
||||
)
|
||||
return read_func(path) if load_func is None else load_func(read_func(path))
|
||||
else:
|
||||
msg = f'Error loading dataset from path:\n\t{self.path}'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
class _DownloadableFile(_SingleFile, _Downloadable[str]):
|
||||
"""Wrap a single file which must be downloaded.
|
||||
|
||||
If downloading a file from an archive, set the filepath of the zip as
|
||||
``path`` and set ``target_file`` as the file to extract. If the path is
|
||||
a zip file and no target file is specified, the entire archive is downloaded
|
||||
and extracted and the root directory of the path is returned.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: str,
|
||||
target_file: str | None = None,
|
||||
):
|
||||
_SingleFile.__init__(self, path)
|
||||
|
||||
from pyvista.examples.downloads import SOURCE # noqa: PLC0415
|
||||
from pyvista.examples.downloads import USER_DATA_PATH # noqa: PLC0415
|
||||
from pyvista.examples.downloads import _download_archive_file_or_folder # noqa: PLC0415
|
||||
from pyvista.examples.downloads import download_file # noqa: PLC0415
|
||||
from pyvista.examples.downloads import file_from_files # noqa: PLC0415
|
||||
from pyvista.examples.examples import dir_path # noqa: PLC0415
|
||||
|
||||
if Path(path).is_absolute():
|
||||
# Absolute path must point to a built-in dataset
|
||||
assert Path(path).parent == Path(
|
||||
dir_path,
|
||||
), 'Absolute path must point to a built-in dataset.'
|
||||
self._base_url = 'https://github.com/pyvista/pyvista/raw/main/pyvista/examples/'
|
||||
self._source_name = Path(path).name
|
||||
# the dataset is already downloaded (it's built-in)
|
||||
# so make download() simply return the local filepath
|
||||
self._download_func = lambda _: path
|
||||
else:
|
||||
# Relative path, use vars from downloads.py
|
||||
self._base_url = SOURCE
|
||||
self._download_func = download_file
|
||||
self._source_name = Path(path).name if Path(path).is_absolute() else path
|
||||
|
||||
target_file = '' if target_file is None and (get_ext(path) == '.zip') else target_file
|
||||
if target_file is not None:
|
||||
# download from archive
|
||||
self._download_func = functools.partial(
|
||||
_download_archive_file_or_folder,
|
||||
target_file=target_file,
|
||||
)
|
||||
# The file path currently points to the archive, not the target file itself
|
||||
# Try to resolve the full path to the target file (without downloading) if
|
||||
# the archive already exists in the cache
|
||||
fullpath = None
|
||||
if os.path.isfile(self.path):
|
||||
try:
|
||||
# Get file path
|
||||
fullpath = file_from_files(target_file, self.path)
|
||||
except (FileNotFoundError, RuntimeError):
|
||||
# Get folder path
|
||||
fullpath = os.path.join(USER_DATA_PATH, path + '.unzip', target_file)
|
||||
fullpath = fullpath if os.path.isdir(fullpath) else None
|
||||
# set the file path as the relative path of the target file if
|
||||
# the fullpath could not be resolved (i.e. not yet downloaded)
|
||||
self._path = target_file if fullpath is None else fullpath
|
||||
|
||||
@property
|
||||
def source_name(self) -> str:
|
||||
return self._source_name
|
||||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
return self._base_url
|
||||
|
||||
def download(self) -> str:
|
||||
path = self._download_func(self._source_name)
|
||||
assert os.path.isfile(path) or os.path.isdir(path)
|
||||
# Reset the path since the full path for archive files
|
||||
# isn't known until after downloading
|
||||
self._path = path
|
||||
return path
|
||||
|
||||
|
||||
class _SingleFileDownloadableDatasetLoader(_SingleFileDatasetLoader, _DownloadableFile):
|
||||
"""Wrap a single file which must first be downloaded and which can also be loaded.
|
||||
|
||||
.. warning::
|
||||
|
||||
``download()`` should be called before accessing other attributes. Otherwise,
|
||||
calling ``load()`` or ``path`` may fail or produce unexpected results.
|
||||
|
||||
"""
|
||||
|
||||
def __init__( # noqa: PLR0917
|
||||
self,
|
||||
path: str,
|
||||
read_func: Callable[[str], DatasetType] | None = None,
|
||||
load_func: Callable[[DatasetType], DatasetType] | None = None,
|
||||
target_file: str | None = None,
|
||||
):
|
||||
_SingleFileDatasetLoader.__init__(self, path, read_func=read_func, load_func=load_func)
|
||||
_DownloadableFile.__init__(self, path, target_file=target_file)
|
||||
|
||||
|
||||
class _MultiFileDatasetLoader(_DatasetLoader, _MultiFilePropsProtocol):
|
||||
"""Wrap multiple files for loading.
|
||||
|
||||
Some use cases for loading multi-file examples include:
|
||||
|
||||
1. Multiple input files, and each file is read/loaded independently
|
||||
E.g.: loading two separate datasets for the example
|
||||
See ``download_bolt_nut`` for a reference implementation.
|
||||
|
||||
2. Multiple input files, but only one is read or loaded directly
|
||||
E.g.: loading a single dataset from a file format where data and metadata are
|
||||
stored in separate files, such as ``.raw`` and ``.mhd``.
|
||||
See ``download_head`` for a reference implementation.
|
||||
|
||||
3. Multiple input files, all of which make up part of the loaded dataset
|
||||
E.g.: loading six separate image files for cubemaps
|
||||
See ``download_sky_box_cube_map`` for a reference implementation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
files_func
|
||||
Specify the function which will return a sequence of :class:`_SingleFile`
|
||||
objects required for loading the dataset. Alternatively, a directory can be
|
||||
specified, in which case a separate single-file dataset loader is created
|
||||
for each file with a default reader.
|
||||
|
||||
load_func
|
||||
Specify the function used to load the files. By default, :meth:`load()` is called
|
||||
on all the files (if loadable) and a tuple containing the loaded datasets is returned.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
files_func: str | Callable[[], Sequence[_SingleFileDatasetLoader | _DownloadableFile]],
|
||||
load_func: Callable[[Sequence[_SingleFileDatasetLoader]], Any] | None = None,
|
||||
):
|
||||
self._files_func = files_func
|
||||
self._file_loaders_ = None
|
||||
if load_func is None:
|
||||
load_func = _load_as_dataset_or_multiblock
|
||||
|
||||
_DatasetLoader.__init__(self, load_func)
|
||||
|
||||
@property
|
||||
def _file_objects(self):
|
||||
if self._file_loaders_ is None and not isinstance(self._files_func, str):
|
||||
self._file_loaders_ = self._files_func() # type: ignore[assignment]
|
||||
return self._file_loaders_
|
||||
|
||||
@property
|
||||
def path(self) -> tuple[str, ...]:
|
||||
return tuple(_flatten_nested_sequence([file.path for file in self._file_objects]))
|
||||
|
||||
@property
|
||||
def path_loadable(self) -> tuple[str, ...]:
|
||||
return tuple(
|
||||
file.path for file in self._file_objects if isinstance(file, _SingleFileDatasetLoader)
|
||||
)
|
||||
|
||||
@property
|
||||
def _filesize_bytes(self) -> tuple[int, ...]:
|
||||
return tuple(
|
||||
_flatten_nested_sequence([file._filesize_bytes for file in self._file_objects]),
|
||||
)
|
||||
|
||||
@property
|
||||
def _filesize_format(self) -> tuple[str, ...]:
|
||||
return tuple(_format_file_size(size) for size in self._filesize_bytes)
|
||||
|
||||
@property
|
||||
def _total_size_bytes(self) -> int:
|
||||
return sum(file._total_size_bytes for file in self._file_objects)
|
||||
|
||||
@property
|
||||
def total_size(self) -> str:
|
||||
return _format_file_size(self._total_size_bytes)
|
||||
|
||||
@property
|
||||
def _reader(
|
||||
self,
|
||||
) -> pv.BaseReader | tuple[pv.BaseReader | None, ...] | None:
|
||||
# TODO: return the actual reader used, and not just a lookup
|
||||
# (this will require an update to the 'read_func' API)
|
||||
reader = _flatten_nested_sequence([file._reader for file in self._file_objects])
|
||||
# flatten in case any file objects themselves are multifiles
|
||||
reader_out: list[pv.BaseReader] = []
|
||||
for r in reader:
|
||||
reader_out.extend(r) if isinstance(r, Sequence) else reader_out.append(r)
|
||||
return tuple(reader_out)
|
||||
|
||||
def load(self):
|
||||
return self._load_func(self._file_objects)
|
||||
|
||||
|
||||
class _MultiFileDownloadableDatasetLoader(
|
||||
_MultiFileDatasetLoader,
|
||||
_Downloadable[tuple[str, ...]],
|
||||
):
|
||||
"""Wrap multiple files for downloading and loading."""
|
||||
|
||||
@property
|
||||
def source_name(self) -> tuple[str, ...]:
|
||||
name = [file.source_name for file in self._file_objects if isinstance(file, _Downloadable)]
|
||||
return tuple(_flatten_nested_sequence(name))
|
||||
|
||||
@property
|
||||
def base_url(self) -> tuple[str, ...]:
|
||||
url = [file.base_url for file in self._file_objects if isinstance(file, _Downloadable)]
|
||||
return tuple(_flatten_nested_sequence(url))
|
||||
|
||||
def download(self) -> tuple[str, ...]:
|
||||
path = [file.download() for file in self._file_objects if isinstance(file, _Downloadable)]
|
||||
# flatten paths in case any loaders have multiple files
|
||||
path_out = _flatten_nested_sequence(path)
|
||||
assert all(os.path.isfile(p) or os.path.isdir(p) for p in path_out)
|
||||
return tuple(path_out)
|
||||
|
||||
|
||||
_ScalarType = TypeVar('_ScalarType', int, str, pv.BaseReader)
|
||||
|
||||
|
||||
def _flatten_nested_sequence(nested: Sequence[_ScalarType | Sequence[_ScalarType]]):
|
||||
"""Flatten nested sequences of objects."""
|
||||
flat: list[_ScalarType] = []
|
||||
for item in nested:
|
||||
if isinstance(item, Sequence) and not isinstance(item, str):
|
||||
flat.extend(item)
|
||||
else:
|
||||
flat.append(item)
|
||||
return flat
|
||||
|
||||
|
||||
def _download_dataset(
|
||||
dataset_loader: _SingleFileDownloadableDatasetLoader | _MultiFileDownloadableDatasetLoader,
|
||||
*,
|
||||
load: bool = True,
|
||||
metafiles: bool = False,
|
||||
):
|
||||
"""Download and load a dataset file or files.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dataset_loader
|
||||
SingleFile or MultiFile object(s) of the dataset(s) to download or load.
|
||||
|
||||
load
|
||||
Read and load the file after downloading. When ``False``,
|
||||
return the path or paths to the example's file(s).
|
||||
|
||||
metafiles
|
||||
When ``load`` is ``False``, set this value to ``True`` to
|
||||
return all files required to load the example, including any metafiles.
|
||||
If ``False``, only the paths of files which are explicitly loaded are
|
||||
returned. E.g if a file format uses two files to specify the header info
|
||||
and file data separately, setting ``metafiles=True`` will return a tuple
|
||||
with both file paths, whereas setting ``metafiles=False`` will only return
|
||||
the single path of the header file as a string.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
Loaded dataset or path(s) to the example's files depending on the ``load``
|
||||
parameter. Dataset may be a texture, mesh, multiblock, array, tuple of meshes,
|
||||
or any other output loaded by the example.
|
||||
|
||||
"""
|
||||
# Download all files for the dataset, include any metafiles
|
||||
path = dataset_loader.download()
|
||||
|
||||
# Exclude non-loadable metafiles from result (if any)
|
||||
if not metafiles and isinstance(dataset_loader, _MultiFileDownloadableDatasetLoader):
|
||||
path = dataset_loader.path_loadable
|
||||
# Return scalar if only one loadable file
|
||||
path = path[0] if len(path) == 1 else path
|
||||
|
||||
return dataset_loader.load() if load else path
|
||||
|
||||
|
||||
def _load_as_multiblock(
|
||||
files: Sequence[_SingleFileDatasetLoader | _MultiFileDatasetLoader],
|
||||
names: Sequence[str] | None = None,
|
||||
) -> pv.MultiBlock:
|
||||
"""Load multiple files as a MultiBlock.
|
||||
|
||||
This function can be used as a loading function for :class:`MultiFileLoadable`
|
||||
If the use of the ``names`` parameter is needed, use :class:`functools.partial`
|
||||
to partially specify the names parameter before passing it as loading function.
|
||||
"""
|
||||
multi = pv.MultiBlock()
|
||||
if names is None:
|
||||
# set names, use filename without ext by default or dirname
|
||||
paths = _flatten_nested_sequence(
|
||||
[file.path_loadable for file in files if isinstance(file, _DatasetLoader)],
|
||||
)
|
||||
paths = [Path(path) for path in paths]
|
||||
names = [
|
||||
path.name[: -len(get_ext(path.name))] if path.is_file() else path.name
|
||||
for path in paths
|
||||
]
|
||||
|
||||
for file, name in zip(files, names):
|
||||
if not isinstance(file, _DatasetLoader):
|
||||
continue # type: ignore[unreachable]
|
||||
loaded = file.load()
|
||||
assert isinstance(
|
||||
loaded,
|
||||
(pv.MultiBlock, pv.DataSet),
|
||||
), (
|
||||
f'Only MultiBlock or DataSet objects can be loaded as a MultiBlock. '
|
||||
f"Got {type(loaded)}.'"
|
||||
)
|
||||
multi.append(loaded, name)
|
||||
return multi
|
||||
|
||||
|
||||
def _load_as_cubemap(files: str | _SingleFile | Sequence[_SingleFile]) -> pv.Texture:
|
||||
"""Load multiple files as a cubemap.
|
||||
|
||||
Input may be a single directory with 6 cubemap files, or a sequence
|
||||
of 6 files
|
||||
"""
|
||||
path = (
|
||||
files
|
||||
if isinstance(files, str)
|
||||
else (files.path if isinstance(files, _SingleFile) else [file.path for file in files])
|
||||
)
|
||||
|
||||
return (
|
||||
pv.cubemap(path)
|
||||
if isinstance(files, str) and os.path.isdir(files)
|
||||
else pv.cubemap_from_filenames(path)
|
||||
)
|
||||
|
||||
|
||||
def _load_as_dataset_or_multiblock(files):
|
||||
multiblock = _load_as_multiblock(files)
|
||||
return multiblock[0] if len(multiblock) == 1 else multiblock
|
||||
|
||||
|
||||
def _load_and_merge(files: Sequence[_SingleFile]):
|
||||
"""Load all loadable files as separate datasets and merge them."""
|
||||
loaded = [file.load() for file in files if isinstance(file, _DatasetLoader)]
|
||||
assert len(loaded) > 0
|
||||
return pv.merge(loaded)
|
||||
|
||||
|
||||
def _get_file_or_folder_size(filepath) -> int:
|
||||
if os.path.isfile(filepath):
|
||||
return os.path.getsize(filepath)
|
||||
assert os.path.isdir(filepath), 'Expected a file or folder path.'
|
||||
all_filepaths = _get_all_nested_filepaths(filepath)
|
||||
return sum(os.path.getsize(file) for file in all_filepaths)
|
||||
|
||||
|
||||
def _format_file_size(size: int) -> str:
|
||||
size_flt = float(size)
|
||||
for unit in ('B', 'KB', 'MB'):
|
||||
if round(size_flt * 10) / 10 < 1000.0:
|
||||
return f'{int(size_flt)} {unit}' if unit == 'B' else f'{size_flt:3.1f} {unit}'
|
||||
size_flt /= 1000.0
|
||||
return f'{size_flt:.1f} GB'
|
||||
|
||||
|
||||
def _get_file_or_folder_ext(path: str):
|
||||
"""Wrap the `get_ext` function to handle special cases for directories."""
|
||||
if os.path.isfile(path):
|
||||
return get_ext(path)
|
||||
assert os.path.isdir(path), 'Expected a file or folder path.'
|
||||
all_paths = _get_all_nested_filepaths(path)
|
||||
ext = [get_ext(file) for file in all_paths]
|
||||
assert len(ext) != 0, f'No files with extensions were found in"\n\t{path}'
|
||||
return ext
|
||||
|
||||
|
||||
def _get_all_nested_filepaths(filepath, *, exclude_readme=True):
|
||||
"""Walk through directory and get all file paths.
|
||||
|
||||
Optionally exclude any readme files (if any).
|
||||
"""
|
||||
assert os.path.isfile(filepath) or os.path.isdir(filepath)
|
||||
condition = lambda name: True if not exclude_readme else not name.lower().startswith('readme')
|
||||
return next(
|
||||
[os.path.join(path, name) for name in files if condition(name)]
|
||||
for path, _, files in os.walk(filepath)
|
||||
)
|
||||
|
||||
|
||||
def _get_unique_extension(path: str | Sequence[str]):
|
||||
"""Return a file extension or unique set of file extensions from a path or paths."""
|
||||
ext_set = set()
|
||||
fname_sequence = [path] if isinstance(path, str) else path
|
||||
|
||||
# Add all file extensions to the set
|
||||
for file in fname_sequence:
|
||||
ext = _get_file_or_folder_ext(file)
|
||||
ext_set.add(ext) if isinstance(ext, str) else ext_set.update(ext)
|
||||
|
||||
# Format output
|
||||
ext_output = tuple(ext_set)
|
||||
return ext_output[0] if len(ext_output) == 1 else tuple(sorted(ext_output))
|
||||
|
||||
|
||||
def _get_unique_reader_type(
|
||||
reader: pv.BaseReader | tuple[pv.BaseReader | None, ...] | None,
|
||||
) -> type[pv.BaseReader] | tuple[type[pv.BaseReader], ...] | None:
|
||||
"""Return a reader type or tuple of unique reader types."""
|
||||
if reader is None or (isinstance(reader, Sequence) and all(r is None for r in reader)):
|
||||
return None
|
||||
reader_set: set[type[pv.BaseReader]] = set()
|
||||
reader_type = (
|
||||
[type(reader)]
|
||||
if not isinstance(reader, Sequence)
|
||||
else [type(r) for r in reader if r is not None]
|
||||
)
|
||||
|
||||
# Add all reader types to the set
|
||||
reader_set.update(reader_type)
|
||||
|
||||
# Format output
|
||||
reader_output = tuple(reader_set)
|
||||
return reader_output[0] if len(reader_output) == 1 else tuple(reader_output)
|
||||
|
||||
|
||||
def _get_unique_dataset_type(
|
||||
dataset_iterable: tuple[DatasetObject, ...],
|
||||
) -> DatasetType | tuple[DatasetType, ...]:
|
||||
"""Return a dataset type or tuple of unique dataset types."""
|
||||
dataset_types: dict[DatasetType, None] = {} # use dict as an ordered set
|
||||
for dataset in dataset_iterable:
|
||||
dataset_types[type(dataset)] = None
|
||||
output = tuple(dataset_types.keys())
|
||||
return output[0] if len(output) == 1 else output
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,28 @@
|
||||
"""Contains 3ds examples."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .downloads import download_file
|
||||
|
||||
|
||||
def download_iflamigm(): # pragma: no cover
|
||||
"""Download a iflamigm image.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the 3DS file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> download_3ds_file = examples.download_3ds.download_iflamigm()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_3ds(download_3ds_file)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return download_file('iflamigm.3ds')
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,798 @@
|
||||
"""Built-in examples that ship with PyVista and do not need to be downloaded.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_ant()
|
||||
>>> mesh.plot()
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista.examples._dataset_loader import _DatasetLoader
|
||||
from pyvista.examples._dataset_loader import _SingleFileDownloadableDatasetLoader
|
||||
|
||||
# get location of this folder and the example files
|
||||
dir_path = str(Path(os.path.realpath(__file__)).parent)
|
||||
antfile = str(Path(dir_path) / 'ant.ply')
|
||||
planefile = str(Path(dir_path) / 'airplane.ply')
|
||||
hexbeamfile = str(Path(dir_path) / 'hexbeam.vtk')
|
||||
spherefile = str(Path(dir_path) / 'sphere.ply')
|
||||
uniformfile = str(Path(dir_path) / 'uniform.vtk')
|
||||
rectfile = str(Path(dir_path) / 'rectilinear.vtk')
|
||||
globefile = str(Path(dir_path) / 'globe.vtk')
|
||||
mapfile = str(Path(dir_path) / '2k_earth_daymap.jpg')
|
||||
channelsfile = str(Path(dir_path) / 'channels.vti')
|
||||
logofile = str(Path(dir_path) / 'pyvista_logo.png')
|
||||
nutfile = str(Path(dir_path) / 'nut.ply')
|
||||
frogtissuesfile = str(Path(dir_path) / 'frog_tissues.vti')
|
||||
|
||||
|
||||
def load_ant():
|
||||
"""Load ply ant mesh.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_ant()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Ant Dataset <ant_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_ant.load()
|
||||
|
||||
|
||||
_dataset_ant = _SingleFileDownloadableDatasetLoader(antfile, read_func=pyvista.PolyData) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def load_airplane():
|
||||
"""Load ply airplane mesh.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_airplane()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Airplane Dataset <airplane_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_airplane.load()
|
||||
|
||||
|
||||
_dataset_airplane = _SingleFileDownloadableDatasetLoader(planefile, read_func=pyvista.PolyData) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def load_sphere():
|
||||
"""Load sphere ply mesh.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_sphere()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Sphere Dataset <sphere_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_sphere.load()
|
||||
|
||||
|
||||
_dataset_sphere = _SingleFileDownloadableDatasetLoader(spherefile, read_func=pyvista.PolyData) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def load_uniform():
|
||||
"""Load a sample uniform grid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_uniform()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Uniform Dataset <uniform_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_uniform.load()
|
||||
|
||||
|
||||
_dataset_uniform = _SingleFileDownloadableDatasetLoader(uniformfile, read_func=pyvista.ImageData) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def load_rectilinear():
|
||||
"""Load a sample uniform grid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.RectilinearGrid
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_rectilinear()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Rectilinear Dataset <rectilinear_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_rectilinear.load()
|
||||
|
||||
|
||||
_dataset_rectilinear = _SingleFileDownloadableDatasetLoader(
|
||||
rectfile,
|
||||
read_func=pyvista.RectilinearGrid, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
def load_hexbeam():
|
||||
"""Load a sample UnstructuredGrid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_hexbeam()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Hexbeam Dataset <hexbeam_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_hexbeam.load()
|
||||
|
||||
|
||||
_dataset_hexbeam = _SingleFileDownloadableDatasetLoader(
|
||||
hexbeamfile,
|
||||
read_func=pyvista.UnstructuredGrid, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
def load_tetbeam():
|
||||
"""Load a sample UnstructuredGrid containing only tetrahedral cells.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.UnstructuredGrid
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_tetbeam()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Tetbeam Dataset <tetbeam_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_tetbeam.load()
|
||||
|
||||
|
||||
def _tetbeam_load_func():
|
||||
# make the geometry identical to the hexbeam
|
||||
xrng = np.linspace(0, 1, 3)
|
||||
yrng = np.linspace(0, 1, 3)
|
||||
zrng = np.linspace(0, 5, 11)
|
||||
grid = pyvista.RectilinearGrid(xrng, yrng, zrng)
|
||||
return grid.to_tetrahedra()
|
||||
|
||||
|
||||
_dataset_tetbeam = _DatasetLoader(_tetbeam_load_func)
|
||||
|
||||
|
||||
def load_structured():
|
||||
"""Load a simple StructuredGrid.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.StructuredGrid
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_structured()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Structured Dataset <structured_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_structured.load()
|
||||
|
||||
|
||||
def _structured_load_func():
|
||||
x = np.arange(-10, 10, 0.25)
|
||||
y = np.arange(-10, 10, 0.25)
|
||||
x, y = np.meshgrid(x, y)
|
||||
r = np.sqrt(x**2 + y**2)
|
||||
z = np.sin(r)
|
||||
return pyvista.StructuredGrid(x, y, z)
|
||||
|
||||
|
||||
_dataset_structured = _DatasetLoader(_structured_load_func)
|
||||
|
||||
|
||||
def load_globe():
|
||||
"""Load a globe source.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Globe dataset with earth texture.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_globe()
|
||||
>>> texture = examples.load_globe_texture()
|
||||
>>> dataset.plot(texture=texture)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Globe Dataset <globe_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_globe.load()
|
||||
|
||||
|
||||
_dataset_globe = _SingleFileDownloadableDatasetLoader(globefile, read_func=pyvista.PolyData) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def load_globe_texture():
|
||||
"""Load a pyvista.Texture that can be applied to the globe source.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Texture
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_globe_texture()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Globe Texture Dataset <globe_texture_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_globe_texture.load()
|
||||
|
||||
|
||||
_dataset_globe_texture = _SingleFileDownloadableDatasetLoader(
|
||||
mapfile,
|
||||
read_func=pyvista.read_texture, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
def load_channels():
|
||||
"""Load a uniform grid of fluvial channels in the subsurface.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> dataset = examples.load_channels()
|
||||
>>> dataset.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Channels Dataset <channels_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_channels.load()
|
||||
|
||||
|
||||
_dataset_channels = _SingleFileDownloadableDatasetLoader(channelsfile)
|
||||
|
||||
|
||||
def load_spline():
|
||||
"""Load an example spline mesh.
|
||||
|
||||
This example data was created with:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
|
||||
>>> z = np.linspace(-2, 2, 100)
|
||||
>>> r = z**2 + 1
|
||||
>>> x = r * np.sin(theta)
|
||||
>>> y = r * np.cos(theta)
|
||||
>>> points = np.column_stack((x, y, z))
|
||||
>>> mesh = pv.Spline(points, 1000)
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Spline mesh.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> spline = examples.load_spline()
|
||||
>>> spline.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Spline Dataset <spline_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_spline.load()
|
||||
|
||||
|
||||
def _spline_load_func():
|
||||
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
|
||||
z = np.linspace(-2, 2, 100)
|
||||
r = z**2 + 1
|
||||
x = r * np.sin(theta)
|
||||
y = r * np.cos(theta)
|
||||
points = np.column_stack((x, y, z))
|
||||
return pyvista.Spline(points, 1000)
|
||||
|
||||
|
||||
_dataset_spline = _DatasetLoader(_spline_load_func)
|
||||
|
||||
|
||||
def load_random_hills():
|
||||
"""Create random hills toy example.
|
||||
|
||||
Uses the parametric random hill function to create hills oriented
|
||||
like topography and adds an elevation array.
|
||||
|
||||
This example dataset was created with:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> mesh = pv.ParametricRandomHills() # doctest:+SKIP
|
||||
>>> mesh = mesh.elevation() # doctest:+SKIP
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Random hills mesh.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_random_hills()
|
||||
>>> mesh.plot()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Random Hills Dataset <random_hills_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_random_hills.load()
|
||||
|
||||
|
||||
def _random_hills_load_func():
|
||||
mesh = pyvista.ParametricRandomHills()
|
||||
return mesh.elevation()
|
||||
|
||||
|
||||
_dataset_random_hills = _DatasetLoader(_random_hills_load_func)
|
||||
|
||||
|
||||
def load_sphere_vectors():
|
||||
"""Create example sphere with a swirly vector field defined on nodes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Mesh containing vectors.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_sphere_vectors()
|
||||
>>> mesh.point_data
|
||||
pyvista DataSetAttributes
|
||||
Association : POINT
|
||||
Active Scalars : vectors
|
||||
Active Vectors : vectors
|
||||
Active Texture : None
|
||||
Active Normals : Normals
|
||||
Contains arrays :
|
||||
Normals float32 (842, 3) NORMALS
|
||||
vectors float32 (842, 3) VECTORS
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Sphere Vectors Dataset <sphere_vectors_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_sphere_vectors.load()
|
||||
|
||||
|
||||
def _sphere_vectors_load_func() -> pyvista.PolyData:
|
||||
sphere = pyvista.Sphere(radius=math.pi)
|
||||
|
||||
# make cool swirly pattern
|
||||
vectors = np.vstack(
|
||||
(
|
||||
np.sin(sphere.points[:, 0]),
|
||||
np.cos(sphere.points[:, 1]),
|
||||
np.cos(sphere.points[:, 2]),
|
||||
),
|
||||
).T
|
||||
|
||||
# add and scale
|
||||
sphere['vectors'] = vectors * 0.3
|
||||
sphere.set_active_vectors('vectors')
|
||||
return sphere
|
||||
|
||||
|
||||
_dataset_sphere_vectors = _DatasetLoader(_sphere_vectors_load_func)
|
||||
|
||||
|
||||
def load_explicit_structured(dimensions=(5, 6, 7), spacing=(20, 10, 1)):
|
||||
"""Load a simple explicit structured grid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dimensions : tuple(int), optional
|
||||
Grid dimensions. Default is (5, 6, 7).
|
||||
spacing : tuple(int), optional
|
||||
Grid spacing. Default is (20, 10, 1).
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ExplicitStructuredGrid
|
||||
An explicit structured grid.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> grid = examples.load_explicit_structured()
|
||||
>>> grid.plot(show_edges=True)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Explicit Structured Dataset <explicit_structured_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_explicit_structured.load(dimensions=dimensions, spacing=spacing)
|
||||
|
||||
|
||||
def _explicit_structured_load_func(dimensions=(5, 6, 7), spacing=(20, 10, 1)):
|
||||
ni, nj, nk = np.asarray(dimensions) - 1
|
||||
si, sj, sk = spacing
|
||||
xi = np.arange(0.0, (ni + 1) * si, si)
|
||||
yi = np.arange(0.0, (nj + 1) * sj, sj)
|
||||
zi = np.arange(0.0, (nk + 1) * sk, sk)
|
||||
|
||||
return pyvista.StructuredGrid(
|
||||
*np.meshgrid(xi, yi, zi, indexing='ij')
|
||||
).cast_to_explicit_structured_grid()
|
||||
|
||||
|
||||
_dataset_explicit_structured = _DatasetLoader(_explicit_structured_load_func)
|
||||
|
||||
|
||||
def load_nut():
|
||||
"""Load an example nut mesh.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
A sample nut surface dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Load an example nut and plot with smooth shading.
|
||||
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_nut()
|
||||
>>> mesh.plot(smooth_shading=True, split_sharp_edges=True)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Nut Dataset <nut_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_nut.load()
|
||||
|
||||
|
||||
_dataset_nut = _SingleFileDownloadableDatasetLoader(nutfile)
|
||||
|
||||
|
||||
def load_hydrogen_orbital(n=1, l=0, m=0, zoom_fac=1.0): # noqa: PLR0917
|
||||
"""Load the hydrogen wave function for a :class:`pyvista.ImageData`.
|
||||
|
||||
This is the solution to the Schrödinger equation for hydrogen
|
||||
evaluated in three-dimensional Cartesian space.
|
||||
|
||||
Inspired by `Hydrogen Wave Function
|
||||
<http://staff.ustc.edu.cn/~zqj/posts/Hydrogen-Wavefunction/>`_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : int, default: 1
|
||||
Principal quantum number. Must be a positive integer. This is often
|
||||
referred to as the "energy level" or "shell".
|
||||
|
||||
l : int, default: 0
|
||||
Azimuthal quantum number. Must be a non-negative integer strictly
|
||||
smaller than ``n``. By convention this value is represented by the
|
||||
letters s, p, d, f, etc.
|
||||
|
||||
m : int, default: 0
|
||||
Magnetic quantum number. Must be an integer ranging from ``-l`` to
|
||||
``l`` (inclusive). This is the orientation of the angular momentum in
|
||||
space.
|
||||
|
||||
zoom_fac : float, default: 1.0
|
||||
Zoom factor for the electron cloud. Increase this value to focus on the
|
||||
center of the electron cloud.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
ImageData containing two ``point_data`` arrays:
|
||||
|
||||
* ``'real_wf'`` - Real part of the wave function.
|
||||
* ``'wf'`` - Complex wave function.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This example requires `sympy <https://www.sympy.org/>`_.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Plot the 3dxy orbital of a hydrogen atom. This corresponds to the quantum
|
||||
numbers ``n=3``, ``l=2``, and ``m=-2``.
|
||||
|
||||
>>> from pyvista import examples
|
||||
>>> grid = examples.load_hydrogen_orbital(3, 2, -2)
|
||||
>>> grid.plot(volume=True, opacity=[1, 0, 1], cmap='magma')
|
||||
|
||||
See :ref:`atomic_orbitals_example` for additional examples using
|
||||
this function.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Hydrogen Orbital Dataset <hydrogen_orbital_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_hydrogen_orbital.load(n=n, l=l, m=m, zoom_fac=zoom_fac)
|
||||
|
||||
|
||||
def _hydrogen_orbital_load_func(n=1, l=0, m=0, zoom_fac=1.0): # noqa: PLR0917
|
||||
try:
|
||||
from sympy import lambdify
|
||||
from sympy.abc import phi
|
||||
from sympy.abc import r
|
||||
from sympy.abc import theta
|
||||
from sympy.physics.hydrogen import Psi_nlm
|
||||
except ImportError: # pragma: no cover
|
||||
msg = '\n\nInstall sympy to run this example. Run:\n\n pip install sympy\n'
|
||||
raise ImportError(msg) from None
|
||||
|
||||
if n < 1:
|
||||
msg = '`n` must be at least 1'
|
||||
raise ValueError(msg)
|
||||
if l not in range(n):
|
||||
msg = f'`l` must be one of: {list(range(n))}'
|
||||
raise ValueError(msg)
|
||||
if m not in range(-l, l + 1):
|
||||
msg = f'`m` must be one of: {list(range(-l, l + 1))}'
|
||||
raise ValueError(msg)
|
||||
|
||||
psi = lambdify((r, phi, theta), Psi_nlm(n, l, m, r, phi, theta, 1), 'numpy')
|
||||
|
||||
org = 1.5 * n**2 + 1.0 if n == 1 else 1.5 * n**2 + 10.0
|
||||
|
||||
org /= zoom_fac
|
||||
|
||||
dim = 100
|
||||
sp = (org * 2) / (dim - 1)
|
||||
grid = pyvista.ImageData(
|
||||
dimensions=(dim, dim, dim),
|
||||
spacing=(sp, sp, sp),
|
||||
origin=(-org, -org, -org),
|
||||
)
|
||||
|
||||
r, theta, phi = pyvista.cartesian_to_spherical(grid.x, grid.y, grid.z)
|
||||
wfc = psi(r, phi, theta).reshape(grid.dimensions)
|
||||
|
||||
grid['real_wf'] = np.real(wfc.ravel())
|
||||
grid['wf'] = wfc.ravel()
|
||||
return grid
|
||||
|
||||
|
||||
_dataset_hydrogen_orbital = _DatasetLoader(_hydrogen_orbital_load_func)
|
||||
|
||||
|
||||
def load_logo():
|
||||
"""Load the PyVista logo as a :class:`pyvista.ImageData`.
|
||||
|
||||
.. note::
|
||||
|
||||
Alternative versions of the logo file are also available from the ``logo``
|
||||
directory at https://github.com/pyvista/pyvista/. This includes
|
||||
higher-resolution ``.png`` files and a vectorized ``.svg`` version.
|
||||
|
||||
.. versionchanged:: 0.45
|
||||
|
||||
The dimensions of the image is now ``1389 x 592``.
|
||||
Previously, it was ``1920 x 718``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
ImageData of the PyVista logo.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from pyvista import examples
|
||||
>>> image = examples.load_logo()
|
||||
>>> image.dimensions
|
||||
(1389, 592, 1)
|
||||
|
||||
>>> image.plot(cpos='xy', zoom='tight', rgb=True, show_axes=False)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Logo Dataset <logo_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
"""
|
||||
return _dataset_logo.load()
|
||||
|
||||
|
||||
_dataset_logo = _SingleFileDownloadableDatasetLoader(logofile)
|
||||
|
||||
|
||||
def load_frog_tissues():
|
||||
"""Load frog tissues dataset.
|
||||
|
||||
This dataset contains tissue segmentation labels for the frog dataset.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.ImageData
|
||||
Dataset.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Load data
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> data = examples.load_frog_tissues()
|
||||
|
||||
Plot tissue labels as a volume
|
||||
|
||||
First, define plotting parameters
|
||||
|
||||
>>> # Configure colors / color bar
|
||||
>>> clim = data.get_data_range() # Set color bar limits to match data
|
||||
>>> cmap = 'glasbey' # Use a categorical colormap
|
||||
>>> categories = True # Ensure n_colors matches number of labels
|
||||
>>> opacity = 'foreground' # Make foreground opaque, background transparent
|
||||
>>> opacity_unit_distance = 1
|
||||
|
||||
Set plotting resolution to half the image's spacing
|
||||
|
||||
>>> res = np.array(data.spacing) / 2
|
||||
|
||||
Define rendering parameters
|
||||
|
||||
>>> mapper = 'gpu'
|
||||
>>> shade = True
|
||||
>>> ambient = 0.3
|
||||
>>> diffuse = 0.6
|
||||
>>> specular = 0.5
|
||||
>>> specular_power = 40
|
||||
|
||||
Make and show plot
|
||||
|
||||
>>> p = pv.Plotter()
|
||||
>>> _ = p.add_volume(
|
||||
... data,
|
||||
... clim=clim,
|
||||
... ambient=ambient,
|
||||
... shade=shade,
|
||||
... diffuse=diffuse,
|
||||
... specular=specular,
|
||||
... specular_power=specular_power,
|
||||
... mapper=mapper,
|
||||
... opacity=opacity,
|
||||
... opacity_unit_distance=opacity_unit_distance,
|
||||
... categories=categories,
|
||||
... cmap=cmap,
|
||||
... resolution=res,
|
||||
... )
|
||||
>>> p.camera_position = 'yx' # Set camera to provide a dorsal view
|
||||
>>> p.show()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`Frog Tissues Dataset <frog_tissues_dataset>`
|
||||
See this dataset in the Dataset Gallery for more info.
|
||||
|
||||
:ref:`Frog Dataset <frog_dataset>`
|
||||
|
||||
:ref:`medical_dataset_gallery`
|
||||
Browse other medical datasets.
|
||||
|
||||
"""
|
||||
return _dataset_frog_tissues.load()
|
||||
|
||||
|
||||
_dataset_frog_tissues = _SingleFileDownloadableDatasetLoader(frogtissuesfile)
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,139 @@
|
||||
"""Contains glTF examples."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pooch
|
||||
|
||||
from .downloads import USER_DATA_PATH
|
||||
|
||||
GLTF_FETCHER = pooch.create(
|
||||
path=USER_DATA_PATH,
|
||||
base_url='https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/',
|
||||
registry={
|
||||
'Avocado/glTF-Binary/Avocado.glb': None,
|
||||
'CesiumMilkTruck/glTF-Binary/CesiumMilkTruck.glb': None,
|
||||
'DamagedHelmet/glTF-Embedded/DamagedHelmet.gltf': None,
|
||||
'GearboxAssy/glTF-Binary/GearboxAssy.glb': None,
|
||||
'SheenChair/glTF-Binary/SheenChair.glb': None,
|
||||
},
|
||||
retry_if_failed=3,
|
||||
)
|
||||
|
||||
|
||||
def download_damaged_helmet(): # pragma: no cover
|
||||
"""Download the damaged helmet example.
|
||||
|
||||
Files hosted at https://github.com/KhronosGroup/glTF-Sample-Models
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the gltf file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> gltf_file = examples.gltf.download_damaged_helmet()
|
||||
>>> cubemap = examples.download_sky_box_cube_map()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_gltf(gltf_file)
|
||||
>>> pl.set_environment_texture(cubemap)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return GLTF_FETCHER.fetch('DamagedHelmet/glTF-Embedded/DamagedHelmet.gltf')
|
||||
|
||||
|
||||
def download_sheen_chair(): # pragma: no cover
|
||||
"""Download the sheen chair example.
|
||||
|
||||
Files hosted at https://github.com/KhronosGroup/glTF-Sample-Models
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the gltf file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> gltf_file = examples.gltf.download_sheen_chair()
|
||||
>>> cubemap = examples.download_sky_box_cube_map()
|
||||
>>> pl = pv.Plotter() # doctest:+SKIP
|
||||
>>> pl.import_gltf(gltf_file) # doctest:+SKIP
|
||||
>>> pl.set_environment_texture(cubemap) # doctest:+SKIP
|
||||
>>> pl.show() # doctest:+SKIP
|
||||
|
||||
"""
|
||||
return GLTF_FETCHER.fetch('SheenChair/glTF-Binary/SheenChair.glb')
|
||||
|
||||
|
||||
def download_gearbox(): # pragma: no cover
|
||||
"""Download the gearbox example.
|
||||
|
||||
Files hosted at https://github.com/KhronosGroup/glTF-Sample-Models
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the gltf file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> gltf_file = examples.gltf.download_gearbox()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_gltf(gltf_file)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return GLTF_FETCHER.fetch('GearboxAssy/glTF-Binary/GearboxAssy.glb')
|
||||
|
||||
|
||||
def download_avocado(): # pragma: no cover
|
||||
"""Download the avocado example.
|
||||
|
||||
Files hosted at https://github.com/KhronosGroup/glTF-Sample-Models
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the gltf file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> gltf_file = examples.gltf.download_avocado()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_gltf(gltf_file)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return GLTF_FETCHER.fetch('Avocado/glTF-Binary/Avocado.glb')
|
||||
|
||||
|
||||
def download_milk_truck(): # pragma: no cover
|
||||
"""Download the milk truck example.
|
||||
|
||||
Files hosted at https://github.com/KhronosGroup/glTF-Sample-Models
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the gltf file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> gltf_file = examples.gltf.download_milk_truck()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_gltf(gltf_file)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return GLTF_FETCHER.fetch('CesiumMilkTruck/glTF-Binary/CesiumMilkTruck.glb')
|
||||
@@ -0,0 +1,159 @@
|
||||
# vtk DataFile Version 4.1
|
||||
vtk output
|
||||
ASCII
|
||||
DATASET UNSTRUCTURED_GRID
|
||||
POINTS 99 double
|
||||
0 0 0 1 0 0 0.5 0 0
|
||||
1 1 0 1 0.5 0 0 1 0
|
||||
0.5 1 0 0 0.5 0 0.5 0.5 0
|
||||
0 0 5 1 0 5 0.5 0 5
|
||||
1 1 5 1 0.5 5 0 1 5
|
||||
0.5 1 5 0 0.5 5 0.5 0.5 5
|
||||
1 0 0.5 1 0 1 1 0 1.5
|
||||
1 0 2 1 0 2.5 1 0 3
|
||||
1 0 3.5 1 0 4 1 0 4.5
|
||||
0 0 0.5 0 0 1 0 0 1.5
|
||||
0 0 2 0 0 2.5 0 0 3
|
||||
0 0 3.5 0 0 4 0 0 4.5
|
||||
0.5 0 0.5 0.5 0 1 0.5 0 1.5
|
||||
0.5 0 2 0.5 0 2.5 0.5 0 3
|
||||
0.5 0 3.5 0.5 0 4 0.5 0 4.5
|
||||
1 1 0.5 1 1 1 1 1 1.5
|
||||
1 1 2 1 1 2.5 1 1 3
|
||||
1 1 3.5 1 1 4 1 1 4.5
|
||||
1 0.5 0.5 1 0.5 1 1 0.5 1.5
|
||||
1 0.5 2 1 0.5 2.5 1 0.5 3
|
||||
1 0.5 3.5 1 0.5 4 1 0.5 4.5
|
||||
0 1 0.5 0 1 1 0 1 1.5
|
||||
0 1 2 0 1 2.5 0 1 3
|
||||
0 1 3.5 0 1 4 0 1 4.5
|
||||
0.5 1 0.5 0.5 1 1 0.5 1 1.5
|
||||
0.5 1 2 0.5 1 2.5 0.5 1 3
|
||||
0.5 1 3.5 0.5 1 4 0.5 1 4.5
|
||||
0 0.5 0.5 0 0.5 1 0 0.5 1.5
|
||||
0 0.5 2 0 0.5 2.5 0 0.5 3
|
||||
0 0.5 3.5 0 0.5 4 0 0.5 4.5
|
||||
0.5 0.5 0.5 0.5 0.5 1 0.5 0.5 1.5
|
||||
0.5 0.5 2 0.5 0.5 2.5 0.5 0.5 3
|
||||
0.5 0.5 3.5 0.5 0.5 4 0.5 0.5 4.5
|
||||
|
||||
CELLS 40 360
|
||||
8 0 2 8 7 27 36 90 81
|
||||
8 2 1 4 8 36 18 54 90
|
||||
8 7 8 6 5 81 90 72 63
|
||||
8 8 4 3 6 90 54 45 72
|
||||
8 27 36 90 81 28 37 91 82
|
||||
8 36 18 54 90 37 19 55 91
|
||||
8 81 90 72 63 82 91 73 64
|
||||
8 90 54 45 72 91 55 46 73
|
||||
8 28 37 91 82 29 38 92 83
|
||||
8 37 19 55 91 38 20 56 92
|
||||
8 82 91 73 64 83 92 74 65
|
||||
8 91 55 46 73 92 56 47 74
|
||||
8 29 38 92 83 30 39 93 84
|
||||
8 38 20 56 92 39 21 57 93
|
||||
8 83 92 74 65 84 93 75 66
|
||||
8 92 56 47 74 93 57 48 75
|
||||
8 30 39 93 84 31 40 94 85
|
||||
8 39 21 57 93 40 22 58 94
|
||||
8 84 93 75 66 85 94 76 67
|
||||
8 93 57 48 75 94 58 49 76
|
||||
8 31 40 94 85 32 41 95 86
|
||||
8 40 22 58 94 41 23 59 95
|
||||
8 85 94 76 67 86 95 77 68
|
||||
8 94 58 49 76 95 59 50 77
|
||||
8 32 41 95 86 33 42 96 87
|
||||
8 41 23 59 95 42 24 60 96
|
||||
8 86 95 77 68 87 96 78 69
|
||||
8 95 59 50 77 96 60 51 78
|
||||
8 33 42 96 87 34 43 97 88
|
||||
8 42 24 60 96 43 25 61 97
|
||||
8 87 96 78 69 88 97 79 70
|
||||
8 96 60 51 78 97 61 52 79
|
||||
8 34 43 97 88 35 44 98 89
|
||||
8 43 25 61 97 44 26 62 98
|
||||
8 88 97 79 70 89 98 80 71
|
||||
8 97 61 52 79 98 62 53 80
|
||||
8 35 44 98 89 9 11 17 16
|
||||
8 44 26 62 98 11 10 13 17
|
||||
8 89 98 80 71 16 17 15 14
|
||||
8 98 62 53 80 17 13 12 15
|
||||
|
||||
CELL_TYPES 40
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
12
|
||||
|
||||
CELL_DATA 40
|
||||
SCALARS sample_cell_scalars int
|
||||
LOOKUP_TABLE default
|
||||
1 2 3 4 5 6 7 8 9
|
||||
10 11 12 13 14 15 16 17 18
|
||||
19 20 21 22 23 24 25 26 27
|
||||
28 29 30 31 32 33 34 35 36
|
||||
37 38 39 40
|
||||
POINT_DATA 99
|
||||
FIELD FieldData 2
|
||||
sample_point_scalars 1 99 vtktypeint64
|
||||
1 2 4 6 8 10 12 15 19
|
||||
22 23 25 27 29 31 33 36 40
|
||||
44 46 48 50 52 54 56 58 60
|
||||
63 65 67 69 71 73 75 77 79
|
||||
91 93 95 97 99 101 103 105 107
|
||||
119 121 123 125 127 129 131 133 135
|
||||
147 149 151 153 155 157 159 161 163
|
||||
175 177 179 181 183 185 187 189 191
|
||||
203 205 207 209 211 213 215 217 219
|
||||
240 242 244 246 248 250 252 254 256
|
||||
286 288 290 292 294 296 298 300 302
|
||||
|
||||
VTKorigID 1 99 vtktypeint64
|
||||
0 1 2 3 4 5 6 7 8
|
||||
9 10 11 12 13 14 15 16 17
|
||||
18 19 20 21 22 23 24 25 26
|
||||
27 28 29 30 31 32 33 34 35
|
||||
36 37 38 39 40 41 42 43 44
|
||||
45 46 47 48 49 50 51 52 53
|
||||
54 55 56 57 58 59 60 61 62
|
||||
63 64 65 66 67 68 69 70 71
|
||||
72 73 74 75 76 77 78 79 80
|
||||
81 82 83 84 85 86 87 88 89
|
||||
90 91 92 93 94 95 96 97 98
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 251 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
"""Contains vrml examples."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pyvista.examples.downloads import download_file
|
||||
|
||||
|
||||
def download_teapot(): # pragma: no cover
|
||||
"""Download the a 2-manifold solid version of the famous teapot example.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the VRML file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> vrml_file = examples.vrml.download_teapot()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_vrml(vrml_file)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return download_file('vrml/teapot.wrl')
|
||||
|
||||
|
||||
def download_sextant(): # pragma: no cover
|
||||
"""Download the sextant example.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the VRML file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> vrml_file = examples.vrml.download_sextant()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_vrml(vrml_file)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return download_file('vrml/sextant.wrl')
|
||||
|
||||
|
||||
def download_grasshopper(): # pragma: no cover
|
||||
"""Download the grasshoper example.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Filename of the VRML file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> vrml_file = examples.vrml.download_grasshopper()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.import_vrml(vrml_file)
|
||||
>>> pl.camera_position = [
|
||||
... (25.0, 32.0, 44.0),
|
||||
... (0.0, 0.931, -6.68),
|
||||
... (-0.20, 0.90, -0.44),
|
||||
... ]
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return download_file('grasshopper/grasshop.wrl')
|
||||
@@ -0,0 +1 @@
|
||||
"""Module level init for ``pyvista.ext``."""
|
||||
@@ -0,0 +1,736 @@
|
||||
"""Plot directive module.
|
||||
|
||||
A directive for including a PyVista plot in a Sphinx document.
|
||||
|
||||
The ``.. pyvista-plot::`` sphinx directive will include an inline
|
||||
``.png`` image.
|
||||
|
||||
The source code for the plot may be included in one of two ways:
|
||||
|
||||
1. Using **doctest** syntax::
|
||||
|
||||
.. pyvista-plot::
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> sphere = pv.Sphere()
|
||||
>>> out = sphere.plot()
|
||||
|
||||
2. **A path to a source file** as the argument to the directive::
|
||||
|
||||
.. pyvista-plot:: path/to/plot.py
|
||||
|
||||
When a path to a source file is given, the content of the
|
||||
directive may optionally contain a caption for the plot::
|
||||
|
||||
.. pyvista-plot:: path/to/plot.py
|
||||
|
||||
The plot's caption.
|
||||
|
||||
Additionally, one may specify the name of a function to call (with
|
||||
no arguments) immediately after importing the module::
|
||||
|
||||
.. pyvista-plot:: path/to/plot.py plot_function1
|
||||
|
||||
.. note::
|
||||
Code blocks containing ``doctest:+SKIP`` will be skipped.
|
||||
|
||||
.. note::
|
||||
Animations will not be saved, only the last frame will be shown.
|
||||
|
||||
|
||||
**Options**
|
||||
The ``pyvista-plot`` directive supports the following options:
|
||||
|
||||
include-source : bool
|
||||
Whether to display the source code. The default can be changed
|
||||
using the ``pyvista_plot_include_source`` variable in :file:`conf.py`.
|
||||
|
||||
encoding : str
|
||||
If this source file is in a non-UTF8 or non-ASCII encoding, the
|
||||
encoding must be specified using the ``:encoding:`` option. The
|
||||
encoding will not be inferred using the ``-*- coding -*-`` metacomment.
|
||||
|
||||
context : None
|
||||
If provided, the code will be run in the context of all previous plot
|
||||
directives for which the ``:context:`` option was specified. This only
|
||||
applies to inline code plot directives, not those run from files.
|
||||
|
||||
nofigs : None
|
||||
When setting this flag, the code block will be run but no figures will be
|
||||
inserted. This is usually useful with the ``:context:`` option.
|
||||
|
||||
caption : str
|
||||
If specified, the option's argument will be used as a caption for the
|
||||
figure. This overwrites the caption given in the content, when the plot
|
||||
is generated from a file.
|
||||
|
||||
force_static : None
|
||||
When setting this flag, static images will be used instead of an
|
||||
interactive scene.
|
||||
|
||||
skip : bool, default: True
|
||||
Whether to skip execution of this directive. If no argument is provided
|
||||
i.e., ``:skip:``, then it defaults to ``:skip: true``. Default
|
||||
behaviour is controlled by the ``plot_skip`` boolean variable in
|
||||
:file:`conf.py`. Note that, if specified, this option overrides the
|
||||
``plot_skip`` configuration.
|
||||
|
||||
optional : None
|
||||
This flag marks the directive for *conditional* execution. Whether the
|
||||
directive is executed is controlled by the ``plot_skip_optional``
|
||||
boolean variable in :file:`conf.py`.
|
||||
|
||||
Additionally, this directive supports all the options of the `image`
|
||||
directive, except for *target* (since plot will add its own target). These
|
||||
include *alt*, *height*, *width*, *scale*, *align*.
|
||||
|
||||
|
||||
**Configuration options**
|
||||
|
||||
.. versionchanged:: 0.45
|
||||
Prior to v0.45, these directives conflicted with ``matplotlib``. All
|
||||
directives have been prepended with ``pyvista_``.
|
||||
|
||||
The plot directive has the following configuration options:
|
||||
|
||||
pyvista_plot_include_source : bool, default: True
|
||||
Default value for the ``include-source`` directive option.
|
||||
Default is ``True``.
|
||||
|
||||
pyvista_plot_basedir : str
|
||||
Base directory, to which ``plot::`` file names are relative
|
||||
to. If ``None`` or unset, file names are relative to the
|
||||
directory where the file containing the directive is.
|
||||
|
||||
pyvista_plot_html_show_formats : bool, default: True
|
||||
Whether to show links to the files in HTML. Default ``True``.
|
||||
|
||||
pyvista_plot_template : str
|
||||
Provide a customized Jinja2 template for preparing restructured text.
|
||||
|
||||
pyvista_plot_setup : str
|
||||
Python code to be run before every plot directive block.
|
||||
|
||||
pyvista_plot_cleanup : str
|
||||
Python code to be run after every plot directive block.
|
||||
|
||||
pyvista_plot_skip : bool, default: False
|
||||
Default value for the ``skip`` directive option.
|
||||
|
||||
pyvista_plot_skip_optional : bool, default: False
|
||||
Whether to skip execution of ``optional`` directives.
|
||||
|
||||
These options can be set by defining global variables of the same name in
|
||||
:file:`conf.py`.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import doctest
|
||||
import os
|
||||
from os.path import relpath
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import textwrap
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import ClassVar
|
||||
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.parsers.rst import directives
|
||||
from docutils.parsers.rst.directives.images import Image
|
||||
import jinja2 # Sphinx dependency.
|
||||
|
||||
# must enable BUILDING_GALLERY to keep windows active
|
||||
# enable offscreen to hide figures when generating them.
|
||||
import pyvista
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
pyvista.BUILDING_GALLERY = True
|
||||
pyvista.OFF_SCREEN = True
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Registration hook
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _option_boolean(arg) -> bool:
|
||||
if not arg or not arg.strip():
|
||||
# no argument given, assume used as a flag
|
||||
return True
|
||||
elif arg.strip().lower() in ('no', '0', 'false'):
|
||||
return False
|
||||
elif arg.strip().lower() in ('yes', '1', 'true'):
|
||||
return True
|
||||
else: # pragma: no cover
|
||||
msg = f'"{arg}" unknown boolean'
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def _option_context(arg):
|
||||
if arg is not None: # pragma: no cover
|
||||
msg = 'No arguments allowed for ``:context:``'
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def _option_format(arg):
|
||||
return directives.choice(arg, ('python', 'doctest'))
|
||||
|
||||
|
||||
class PlotDirective(Directive):
|
||||
"""The ``.. pyvista-plot::`` directive, as documented in the module's docstring."""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 2
|
||||
final_argument_whitespace = False
|
||||
option_spec: ClassVar[dict[str, Callable]] = {
|
||||
'alt': directives.unchanged,
|
||||
'height': directives.length_or_unitless,
|
||||
'width': directives.length_or_percentage_or_unitless,
|
||||
'scale': directives.nonnegative_int,
|
||||
'align': Image.align,
|
||||
'include-source': _option_boolean,
|
||||
'format': _option_format,
|
||||
'context': _option_context,
|
||||
'nofigs': directives.flag,
|
||||
'encoding': directives.encoding,
|
||||
'caption': directives.unchanged,
|
||||
'force_static': directives.flag,
|
||||
'skip': _option_boolean,
|
||||
'optional': directives.flag,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
"""Run the plot directive."""
|
||||
try:
|
||||
return run(
|
||||
self.arguments,
|
||||
self.content,
|
||||
self.options,
|
||||
self.state_machine,
|
||||
self.state,
|
||||
self.lineno,
|
||||
)
|
||||
except Exception as e: # noqa: BLE001 # pragma: no cover
|
||||
raise self.error(str(e))
|
||||
|
||||
|
||||
def setup(app):
|
||||
"""Set up the plot directive."""
|
||||
setup.app = app
|
||||
setup.config = app.config
|
||||
setup.confdir = app.confdir
|
||||
app.add_directive('pyvista-plot', PlotDirective)
|
||||
|
||||
legacy_keys = [
|
||||
'plot_include_source',
|
||||
'plot_basedir',
|
||||
'plot_html_show_formats',
|
||||
'plot_template',
|
||||
'plot_setup',
|
||||
'plot_cleanup',
|
||||
'plot_skip',
|
||||
'plot_skip_optional',
|
||||
]
|
||||
|
||||
def raise_on_legacy_config(app, config):
|
||||
"""Raise a RuntimeError when using legacy configuration parameters.
|
||||
|
||||
These parameters conflict with matplotlib's ``plot_directive``.
|
||||
|
||||
"""
|
||||
uses_matplotlib = 'matplotlib.sphinxext.plot_directive' in app.extensions
|
||||
|
||||
if not uses_matplotlib: # pragma: no cover
|
||||
for key in legacy_keys:
|
||||
if getattr(config, key, None) is not None:
|
||||
msg = (
|
||||
f"Sphinx config uses deprecated '{key}' without 'pyvista_' prefix. "
|
||||
f"Rename it to 'pyvista_{key}"
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
app.connect('config-inited', raise_on_legacy_config)
|
||||
|
||||
app.add_config_value('pyvista_plot_include_source', True, False)
|
||||
app.add_config_value('pyvista_plot_basedir', None, True)
|
||||
app.add_config_value('pyvista_plot_html_show_formats', True, True)
|
||||
app.add_config_value('pyvista_plot_template', None, True)
|
||||
app.add_config_value('pyvista_plot_setup', None, True)
|
||||
app.add_config_value('pyvista_plot_cleanup', None, True)
|
||||
app.add_config_value(name='pyvista_plot_skip', default=False, rebuild='html')
|
||||
app.add_config_value(name='pyvista_plot_skip_optional', default=False, rebuild='html')
|
||||
return {
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
'version': pyvista.__version__,
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Doctest handling
|
||||
# -----------------------------------------------------------------------------
|
||||
def _contains_doctest(text):
|
||||
try:
|
||||
# check if it's valid Python as-is
|
||||
compile(text, '<string>', 'exec')
|
||||
except SyntaxError:
|
||||
pass
|
||||
else:
|
||||
return False
|
||||
r = re.compile(r'^\s*>>>', re.MULTILINE)
|
||||
m = r.search(text)
|
||||
return bool(m)
|
||||
|
||||
|
||||
def _contains_pyvista_plot(text) -> bool:
|
||||
return '.. pyvista-plot::' in text
|
||||
|
||||
|
||||
def _strip_comments(code):
|
||||
"""Remove comments from a line of python code."""
|
||||
return re.sub(r'(?m)^ *#.*\n?', '', code)
|
||||
|
||||
|
||||
def _split_code_at_show(text):
|
||||
"""Split code at plt.show() or plt.plot().
|
||||
|
||||
Includes logic to deal with edge cases like:
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pv.Sphere().plot(color='blue', cpos='xy')
|
||||
|
||||
>>> pv.Sphere().plot(color='red', cpos='xy')
|
||||
|
||||
"""
|
||||
parts = []
|
||||
is_doctest = _contains_doctest(text)
|
||||
part = []
|
||||
|
||||
within_plot = False
|
||||
for line in text.split('\n'):
|
||||
part.append(line)
|
||||
|
||||
# check if show(...) or plot(...) is within the line
|
||||
line_no_comments = _strip_comments(line)
|
||||
if within_plot: # allow for multi-line plot(...
|
||||
if line_no_comments.endswith(')'):
|
||||
parts.append('\n'.join(part))
|
||||
part = []
|
||||
within_plot = False
|
||||
|
||||
elif _show_or_plot_in_string(line_no_comments):
|
||||
if line_no_comments.endswith(')'):
|
||||
parts.append('\n'.join(part))
|
||||
part = []
|
||||
else: # allow for multi-line plot(...
|
||||
within_plot = True
|
||||
|
||||
if '\n'.join(part).strip():
|
||||
parts.append('\n'.join(part))
|
||||
return is_doctest, parts
|
||||
|
||||
|
||||
def _show_or_plot_in_string(string):
|
||||
# string contains `.show(`, `.plot(`, or `plot_xyz(` where `xyz` is one
|
||||
# or more lower-case letters or underscore, e.g. `plot_cell(`, `plot_datasets(`
|
||||
pattern = r'(?:\.plot\(|\.show\(|(?:[ \t\n.]plot_[a-z_]+?)\()'
|
||||
return bool(re.search(pattern, string))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Template
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE = """
|
||||
{{ source_code }}
|
||||
|
||||
.. only:: html
|
||||
|
||||
{% for img in images %}
|
||||
{% if img.extension == 'vtksz' %}
|
||||
|
||||
.. tab-set::
|
||||
|
||||
.. tab-item:: Static Scene
|
||||
|
||||
.. figure:: {{ build_dir }}/{{ img.stem }}.png
|
||||
{% for option in options -%}
|
||||
{{ option }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
.. tab-item:: Interactive Scene
|
||||
|
||||
.. offlineviewer:: {{ build_dir }}/{{ img.stem }}.vtksz
|
||||
|
||||
{{ caption }} {# appropriate leading whitespace added beforehand #}
|
||||
{% else %}
|
||||
.. figure:: {{ build_dir }}/{{ img.basename }}
|
||||
{% for option in options -%}
|
||||
{{ option }}
|
||||
{% endfor %}
|
||||
|
||||
{{ caption }} {# appropriate leading whitespace added beforehand #}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
"""
|
||||
|
||||
exception_template = """
|
||||
.. only:: html
|
||||
|
||||
[`source code <%(linkdir)s/%(basename)s.py>`__]
|
||||
|
||||
Exception occurred rendering plot.
|
||||
|
||||
"""
|
||||
|
||||
# the context of the plot for all directives specified with the
|
||||
# :context: option
|
||||
plot_context = {}
|
||||
|
||||
|
||||
class ImageFile:
|
||||
"""Simple representation of an image file path."""
|
||||
|
||||
def __init__(self, dirname, basename):
|
||||
"""Construct ImageFile."""
|
||||
self.basename = basename
|
||||
self.dirname = dirname
|
||||
self.extension = Path(basename).suffix[1:]
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
"""Return the filename of this image."""
|
||||
return str(Path(self.dirname) / self.basename)
|
||||
|
||||
@property
|
||||
def stem(self):
|
||||
"""Return the basename without the suffix."""
|
||||
return Path(self.basename).stem
|
||||
|
||||
def __repr__(self) -> str: # pragma no cover
|
||||
return self.filename
|
||||
|
||||
|
||||
class PlotError(RuntimeError):
|
||||
"""More descriptive plot error."""
|
||||
|
||||
|
||||
def _run_code(*, code, code_path, ns=None, function_name=None): # noqa: ARG001
|
||||
"""Run a docstring example.
|
||||
|
||||
Run the example if it does not contain ``'doctest:+SKIP'``, or a
|
||||
```pyvista-plot::`` directive. In the later case, the doctest parser will
|
||||
present the code-block again with the ```pyvista-plot::`` directive
|
||||
and its options removed.
|
||||
|
||||
Import a Python module from a path, and run the function given by
|
||||
name, if function_name is not None.
|
||||
"""
|
||||
# do not execute code containing any SKIP directives
|
||||
if 'doctest:+SKIP' in code:
|
||||
return ns
|
||||
|
||||
if 'pyvista-plot::' in code:
|
||||
return ns
|
||||
|
||||
try:
|
||||
if pyvista.PLOT_DIRECTIVE_THEME is not None:
|
||||
pyvista.set_plot_theme(pyvista.PLOT_DIRECTIVE_THEME) # pragma: no cover
|
||||
exec(code, ns)
|
||||
except (Exception, SystemExit) as err: # pragma: no cover
|
||||
raise PlotError(traceback.format_exc()) from err
|
||||
|
||||
return ns
|
||||
|
||||
|
||||
def render_figures(
|
||||
*,
|
||||
code,
|
||||
code_path,
|
||||
output_dir,
|
||||
output_base,
|
||||
context,
|
||||
function_name,
|
||||
config,
|
||||
force_static,
|
||||
):
|
||||
"""Run a pyplot script and save the images in *output_dir*.
|
||||
|
||||
Save the images under *output_dir* with file names derived from
|
||||
*output_base*. Closed plotters are ignored if they were never
|
||||
rendered.
|
||||
"""
|
||||
# We skip snippets that contain the ```pyvista-plot::`` directive as part of their code.
|
||||
# The doctest parser will present the code-block once again with the ```pyvista-plot::``
|
||||
# directive and its options properly parsed.
|
||||
if _contains_pyvista_plot(code):
|
||||
is_doctest = True
|
||||
code_pieces = [code]
|
||||
else:
|
||||
# Try to determine if all images already exist
|
||||
is_doctest, code_pieces = _split_code_at_show(code)
|
||||
|
||||
# Otherwise, we didn't find the files, so build them
|
||||
results = []
|
||||
ns = plot_context if context else {}
|
||||
|
||||
# Check for setup and teardown code for plots
|
||||
code_setup = config.pyvista_plot_setup
|
||||
code_cleanup = config.pyvista_plot_cleanup
|
||||
|
||||
if code_setup:
|
||||
_run_code(code=code_setup, code_path=code_path, ns=ns, function_name=function_name)
|
||||
|
||||
try:
|
||||
for i, code_piece in enumerate(code_pieces):
|
||||
# generate the plot
|
||||
_run_code(
|
||||
code=doctest.script_from_examples(code_piece) if is_doctest else code_piece,
|
||||
code_path=code_path,
|
||||
ns=ns,
|
||||
function_name=function_name,
|
||||
)
|
||||
|
||||
images = []
|
||||
|
||||
if (
|
||||
_show_or_plot_in_string(code_piece)
|
||||
or '.open_gif' in code_piece
|
||||
or 'plot=True' in code_piece
|
||||
):
|
||||
figures = pyvista.plotting.plotter._ALL_PLOTTERS
|
||||
|
||||
for j, (_, plotter) in enumerate(figures.items()):
|
||||
if plotter._gif_filename is not None:
|
||||
image_file = ImageFile(output_dir, f'{output_base}_{i:02d}_{j:02d}.gif')
|
||||
shutil.move(plotter._gif_filename, image_file.filename)
|
||||
else:
|
||||
image_file = ImageFile(output_dir, f'{output_base}_{i:02d}_{j:02d}.png')
|
||||
try:
|
||||
plotter.screenshot(image_file.filename)
|
||||
except RuntimeError: # pragma no cover
|
||||
# ignore closed, unrendered plotters
|
||||
continue
|
||||
if force_static or (plotter.last_vtksz is None):
|
||||
images.append(image_file)
|
||||
continue
|
||||
else:
|
||||
image_file = ImageFile(
|
||||
output_dir, f'{output_base}_{i:02d}_{j:02d}.vtksz'
|
||||
)
|
||||
with Path(image_file.filename).open('wb') as f:
|
||||
f.write(plotter.last_vtksz)
|
||||
images.append(image_file)
|
||||
|
||||
pyvista.close_all() # close and clear all plotters
|
||||
|
||||
results.append((code_piece, images))
|
||||
finally:
|
||||
if code_cleanup:
|
||||
_run_code(code=code_cleanup, code_path=code_path, ns=ns, function_name=function_name)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def run(arguments, content, options, state_machine, state, lineno): # noqa: PLR0917
|
||||
"""Run the plot directive."""
|
||||
document = state_machine.document
|
||||
config = document.settings.env.config
|
||||
nofigs = 'nofigs' in options
|
||||
optional = 'optional' in options
|
||||
force_static = 'force_static' in options
|
||||
|
||||
default_fmt = 'png'
|
||||
|
||||
options.setdefault('include-source', config.pyvista_plot_include_source)
|
||||
options.setdefault('skip', config.pyvista_plot_skip)
|
||||
|
||||
skip = options['skip'] or (optional and config.pyvista_plot_skip_optional)
|
||||
|
||||
keep_context = 'context' in options
|
||||
_ = None if not keep_context else options['context']
|
||||
|
||||
rst_file = document.attributes['source']
|
||||
rst_dir = str(Path(rst_file).parent)
|
||||
|
||||
if len(arguments):
|
||||
if not config.pyvista_plot_basedir:
|
||||
source_file_name = str(Path(setup.app.builder.srcdir) / directives.uri(arguments[0]))
|
||||
else:
|
||||
source_file_name = str(
|
||||
Path(setup.confdir) / config.pyvista_plot_basedir / directives.uri(arguments[0]),
|
||||
)
|
||||
|
||||
# If there is content, it will be passed as a caption.
|
||||
caption = '\n'.join(content)
|
||||
|
||||
# Enforce unambiguous use of captions.
|
||||
if 'caption' in options:
|
||||
if caption: # pragma: no cover
|
||||
msg = 'Caption specified in both content and options. Please remove ambiguity.'
|
||||
raise ValueError(msg)
|
||||
# Use caption option
|
||||
caption = options['caption']
|
||||
|
||||
# If the optional function name is provided, use it
|
||||
function_name = arguments[1] if len(arguments) == 2 else None
|
||||
|
||||
code = Path(source_file_name).read_text(encoding='utf-8')
|
||||
output_base = Path(source_file_name).name
|
||||
else:
|
||||
source_file_name = rst_file
|
||||
code = textwrap.dedent('\n'.join(map(str, content)))
|
||||
|
||||
# note: this reuses the existing matplotlib plot counter if available
|
||||
counter = document.attributes.get('_plot_counter', 0) + 1
|
||||
document.attributes['_plot_counter'] = counter
|
||||
base = Path(source_file_name).stem
|
||||
ext = Path(source_file_name).suffix
|
||||
output_base = f'{base}-{counter}{ext}'
|
||||
function_name = None
|
||||
caption = options.get('caption', '')
|
||||
|
||||
base = Path(output_base).stem
|
||||
source_ext = Path(output_base).suffix
|
||||
if source_ext in ('.py', '.rst', '.txt'):
|
||||
output_base = base
|
||||
else:
|
||||
source_ext = ''
|
||||
|
||||
# ensure that LaTeX includegraphics doesn't choke in foo.bar.pdf filenames
|
||||
output_base = output_base.replace('.', '-')
|
||||
|
||||
# is it in doctest format?
|
||||
is_doctest = _contains_doctest(code)
|
||||
if 'format' in options:
|
||||
is_doctest = options['format'] != 'python'
|
||||
|
||||
# determine output directory name fragment
|
||||
source_rel_name = relpath(source_file_name, setup.confdir)
|
||||
source_rel_dir = str(Path(source_rel_name).parent).lstrip(os.path.sep)
|
||||
|
||||
# build_dir: where to place output files (temporarily)
|
||||
build_dir = str(Path(setup.app.doctreedir).parent / 'plot_directive' / source_rel_dir)
|
||||
# get rid of .. in paths, also changes pathsep
|
||||
# see note in Python docs for warning about symbolic links on Windows.
|
||||
# need to compare source and dest paths at end
|
||||
build_dir = os.path.normpath(build_dir)
|
||||
Path(build_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# output_dir: final location in the builder's directory
|
||||
dest_dir = str((Path(setup.app.builder.outdir) / source_rel_dir).resolve())
|
||||
Path(dest_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# how to link to files from the RST file
|
||||
dest_dir_link = os.path.join( # noqa: PTH118
|
||||
relpath(setup.confdir, rst_dir),
|
||||
source_rel_dir,
|
||||
).replace(os.path.sep, '/')
|
||||
try:
|
||||
build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/')
|
||||
except ValueError: # pragma: no cover
|
||||
# on Windows, relpath raises ValueError when path and start are on
|
||||
# different mounts/drives
|
||||
build_dir_link = build_dir
|
||||
_ = dest_dir_link + '/' + output_base + source_ext
|
||||
|
||||
# make figures
|
||||
errors = []
|
||||
if skip:
|
||||
results = [(code, [])]
|
||||
else:
|
||||
try:
|
||||
results = render_figures(
|
||||
code=code,
|
||||
code_path=source_file_name,
|
||||
output_dir=build_dir,
|
||||
output_base=output_base,
|
||||
context=keep_context,
|
||||
function_name=function_name,
|
||||
config=config,
|
||||
force_static=force_static,
|
||||
)
|
||||
except PlotError as err: # pragma: no cover
|
||||
reporter = state.memo.reporter
|
||||
sm = reporter.system_message(
|
||||
2,
|
||||
f'Exception occurred in plotting {output_base}\n from {source_file_name}:\n{err}',
|
||||
line=lineno,
|
||||
)
|
||||
results = [(code, [])]
|
||||
errors.append([sm])
|
||||
|
||||
# Properly indent the caption
|
||||
caption = (
|
||||
'' if skip else '\n' + '\n'.join(' ' + line.strip() for line in caption.split('\n'))
|
||||
)
|
||||
|
||||
# generate output restructuredtext
|
||||
total_lines = []
|
||||
for _, (code_piece, images) in enumerate(results):
|
||||
if options['include-source']:
|
||||
if is_doctest:
|
||||
lines = ['', *code_piece.splitlines()]
|
||||
else:
|
||||
lines = [
|
||||
'.. code-block:: python',
|
||||
'',
|
||||
*textwrap.indent(code_piece, ' ').splitlines(),
|
||||
]
|
||||
source_code = '\n'.join(lines)
|
||||
else:
|
||||
source_code = ''
|
||||
|
||||
images_input = [] if nofigs else images
|
||||
|
||||
opts = [
|
||||
f':{key}: {val}'
|
||||
for key, val in options.items()
|
||||
if key in ('alt', 'height', 'width', 'scale', 'align')
|
||||
]
|
||||
|
||||
result = jinja2.Template(config.pyvista_plot_template or TEMPLATE).render(
|
||||
default_fmt=default_fmt,
|
||||
dest_dir=dest_dir_link,
|
||||
build_dir=build_dir_link,
|
||||
source_link=None,
|
||||
multi_image=len(images_input) > 1,
|
||||
options=opts,
|
||||
images=images_input,
|
||||
source_code=source_code,
|
||||
html_show_formats=config.pyvista_plot_html_show_formats and len(images_input),
|
||||
caption=caption,
|
||||
)
|
||||
|
||||
total_lines.extend(result.split('\n'))
|
||||
total_lines.extend('\n')
|
||||
|
||||
if total_lines:
|
||||
state_machine.insert_input(total_lines, source=source_file_name)
|
||||
|
||||
# copy image files to builder's output directory, if necessary
|
||||
Path(dest_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for _, images in results:
|
||||
for image in images:
|
||||
destimg = str(Path(dest_dir) / image.basename)
|
||||
if image.filename != destimg:
|
||||
shutil.copyfile(image.filename, destimg)
|
||||
|
||||
# copy script (if necessary)
|
||||
Path(dest_dir, output_base + source_ext).write_text(
|
||||
doctest.script_from_examples(code)
|
||||
if source_file_name == rst_file and is_doctest
|
||||
else code,
|
||||
encoding='utf-8',
|
||||
)
|
||||
|
||||
return errors
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Viewer directive module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.utils import relative_path # pragma: no cover
|
||||
from sphinx.util import logging
|
||||
from trame_vtk.tools.vtksz2html import HTML_VIEWER_PATH
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_path_relative_to(path, other):
|
||||
"""Path.is_relative_to was introduced in Python 3.9 [1].
|
||||
|
||||
Provide a replacement that works for all supported versions
|
||||
|
||||
[1] https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to.
|
||||
"""
|
||||
return path.is_relative_to(other)
|
||||
|
||||
|
||||
class OfflineViewerDirective(Directive):
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
has_content = True
|
||||
|
||||
def run(self): # pragma: no cover
|
||||
source_dir = Path(self.state.document.settings.env.app.srcdir)
|
||||
output_dir = Path(self.state.document.settings.env.app.outdir)
|
||||
# _build directory
|
||||
build_dir = Path(self.state.document.settings.env.app.outdir).parent
|
||||
|
||||
# this is the path passed to 'offlineviewer:: <path>` directive
|
||||
source_file = str(Path(self.state.document.current_source).parent / self.arguments[0])
|
||||
source_file = Path(source_file).absolute().resolve()
|
||||
if not Path(source_file).is_file():
|
||||
logger.warning(f'Source file {source_file} does not exist.')
|
||||
return []
|
||||
|
||||
# copy viewer HTML to _static
|
||||
static_path = Path(output_dir) / '_static'
|
||||
static_path.mkdir(exist_ok=True)
|
||||
if not Path(static_path, Path(HTML_VIEWER_PATH).name).exists():
|
||||
shutil.copy(HTML_VIEWER_PATH, static_path)
|
||||
|
||||
# calculate the scene asset path relative to the build directory and
|
||||
# recreate the directory structure under output_dir/_images. This
|
||||
# avoids overriding files with the same name e.g. index-x_yy_zz.vtksz will
|
||||
# be generated by any index.rst file and we have a number of them.
|
||||
# Example:
|
||||
# source_file ${HOME}/pyvista/pyvista/doc/_build/plot_directive/getting-started/index-2_00_00.vtksz # noqa:E501
|
||||
# dest_partial_path: plot_directive/getting-started
|
||||
# dest_path: ${HOME}/pyvista/pyvista/doc/_build/html/_images/plot_directive/getting-started/index-2_00_00.vtksz # noqa: E501
|
||||
|
||||
if is_path_relative_to(source_file, build_dir):
|
||||
dest_partial_path = Path(source_file.parent).relative_to(build_dir)
|
||||
elif is_path_relative_to(source_file, source_dir):
|
||||
dest_partial_path = Path(source_file.parent).relative_to(source_dir)
|
||||
else:
|
||||
logger.warning(
|
||||
f'Source file {source_file} is not a subpath of either the build directory of the '
|
||||
f'source directory. Cannot extract base path',
|
||||
)
|
||||
return []
|
||||
|
||||
dest_path = Path(output_dir).joinpath('_images').joinpath(dest_partial_path)
|
||||
dest_path.mkdir(parents=True, exist_ok=True)
|
||||
dest_file = dest_path.joinpath(source_file.name).resolve()
|
||||
if source_file != dest_file:
|
||||
try:
|
||||
shutil.copy(source_file, dest_file)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning(f'Failed to copy file from {source_file} to {dest_file}: {e}')
|
||||
|
||||
# Compute the relative path of the current source to the source directory,
|
||||
# which is the same as the relative path of the '_static' directory to the
|
||||
# generated HTML file.
|
||||
relpath_to_source_root = relative_path(self.state.document.current_source, source_dir)
|
||||
rel_viewer_path = (
|
||||
Path() / relpath_to_source_root / '_static' / Path(HTML_VIEWER_PATH).name
|
||||
).as_posix()
|
||||
rel_asset_path = Path(os.path.relpath(dest_file, static_path)).as_posix()
|
||||
html = (
|
||||
f"<iframe src='{rel_viewer_path}?fileURL={rel_asset_path}' "
|
||||
"width='100%%' height='400px' frameborder='0'></iframe>"
|
||||
)
|
||||
|
||||
raw_node = nodes.raw('', html, format='html')
|
||||
|
||||
return [raw_node]
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive('offlineviewer', OfflineViewerDirective)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Jupyter notebook plotting module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from typing import Literal
|
||||
from typing import get_args
|
||||
|
||||
from typing_extensions import TypeIs
|
||||
|
||||
import pyvista
|
||||
from pyvista.core.errors import PyVistaDeprecationWarning as PyVistaDeprecationWarning
|
||||
|
||||
JupyterBackendOptions = Literal['static', 'client', 'server', 'trame', 'html', 'none']
|
||||
ALLOWED_BACKENDS = get_args(JupyterBackendOptions)
|
||||
|
||||
|
||||
def _is_jupyter_backend(backend: str) -> TypeIs[JupyterBackendOptions]:
|
||||
"""Return True if backend is allowed jupyter backend."""
|
||||
return backend in ALLOWED_BACKENDS
|
||||
|
||||
|
||||
def _validate_jupyter_backend(
|
||||
backend: str | None,
|
||||
) -> JupyterBackendOptions:
|
||||
"""Validate that a jupyter backend is valid.
|
||||
|
||||
Returns the normalized name of the backend. Raises if the backend is invalid.
|
||||
|
||||
"""
|
||||
# Must be a string
|
||||
if backend is None:
|
||||
backend = 'none'
|
||||
backend = backend.lower()
|
||||
|
||||
if not importlib.util.find_spec('IPython'):
|
||||
msg = 'Install IPython to display with pyvista in a notebook.'
|
||||
raise ImportError(msg)
|
||||
|
||||
if not _is_jupyter_backend(backend):
|
||||
backend_list_str = ', '.join([f'"{item}"' for item in ALLOWED_BACKENDS])
|
||||
msg = (
|
||||
f'Invalid Jupyter notebook plotting backend "{backend}".\n'
|
||||
f'Use one of the following:\n{backend_list_str}'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
if backend in ['server', 'client', 'trame', 'html']:
|
||||
try:
|
||||
from pyvista.trame.jupyter import show_trame as show_trame # noqa: PLC0415
|
||||
except ImportError: # pragma: no cover
|
||||
msg = 'Please install trame dependencies: pip install "pyvista[jupyter]"'
|
||||
raise ImportError(msg)
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
def set_jupyter_backend(backend, name=None, **kwargs): # noqa: ARG001
|
||||
"""Set the plotting backend for a jupyter notebook.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
backend : str
|
||||
Jupyter backend to use when plotting. Must be one of the following:
|
||||
|
||||
* ``'static'`` : Display a single static image within the
|
||||
Jupyterlab environment. Still requires that a virtual
|
||||
framebuffer be set up when displaying on a headless server,
|
||||
but does not require any additional modules to be installed.
|
||||
|
||||
* ``'client'`` : Export/serialize the scene graph to be rendered
|
||||
with VTK.js client-side through ``trame``. Requires ``trame``
|
||||
and ``jupyter-server-proxy`` to be installed.
|
||||
|
||||
* ``'server'``: Render remotely and stream the resulting VTK
|
||||
images back to the client using ``trame``. This replaces the
|
||||
``'ipyvtklink'`` backend with better performance.
|
||||
Supports the most VTK features, but suffers from minor lag due
|
||||
to remote rendering. Requires that a virtual framebuffer be set
|
||||
up when displaying on a headless server. Must have at least ``trame``
|
||||
and ``jupyter-server-proxy`` installed for cloud/remote Jupyter
|
||||
instances. This mode is also aliased by ``'trame'``.
|
||||
|
||||
* ``'trame'``: The full Trame-based backend that combines both
|
||||
``'server'`` and ``'client'`` into one backend. This requires a
|
||||
virtual frame buffer.
|
||||
|
||||
* ``'html'`` : Export/serialize the scene graph to be rendered
|
||||
with the Trame client backend but in a static HTML file.
|
||||
|
||||
* ``'none'`` : Do not display any plots within jupyterlab,
|
||||
instead display using dedicated VTK render windows. This
|
||||
will generate nothing on headless servers even with a
|
||||
virtual framebuffer.
|
||||
name : str, optional
|
||||
The unique name identifier for the server.
|
||||
**kwargs : dict, optional
|
||||
Any additional keyword arguments to pass to the server launch.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Enable the trame Trame backend.
|
||||
|
||||
>>> pv.set_jupyter_backend('trame') # doctest:+SKIP
|
||||
|
||||
Just show static images.
|
||||
|
||||
>>> pv.set_jupyter_backend('static') # doctest:+SKIP
|
||||
|
||||
Disable all plotting within JupyterLab and display using a
|
||||
standard desktop VTK render window.
|
||||
|
||||
>>> pv.set_jupyter_backend(None) # doctest:+SKIP
|
||||
|
||||
"""
|
||||
pyvista.global_theme._jupyter_backend = _validate_jupyter_backend(backend)
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Support dynamic or static jupyter notebook plotting.
|
||||
|
||||
Includes:
|
||||
|
||||
* ``trame``
|
||||
* ``client``
|
||||
* ``server``
|
||||
* ``html``
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
from IPython.lib.display import IFrame
|
||||
from PIL.Image import Image
|
||||
|
||||
from pyvista.jupyter import JupyterBackendOptions
|
||||
from pyvista.plotting.plotter import Plotter
|
||||
from pyvista.trame.jupyter import EmbeddableWidget
|
||||
from pyvista.trame.jupyter import Widget
|
||||
|
||||
|
||||
def handle_plotter(
|
||||
plotter: Plotter,
|
||||
backend: JupyterBackendOptions | None = None,
|
||||
screenshot: str | Path | io.BytesIO | bool | None = None, # noqa: FBT001
|
||||
**kwargs,
|
||||
) -> EmbeddableWidget | IFrame | Widget | Image:
|
||||
"""Show the ``pyvista`` plot in a jupyter environment.
|
||||
|
||||
Returns
|
||||
-------
|
||||
IPython Widget
|
||||
IPython widget or image.
|
||||
|
||||
"""
|
||||
if screenshot is False:
|
||||
screenshot = None
|
||||
|
||||
try:
|
||||
if backend in ['server', 'client', 'trame', 'html']:
|
||||
from pyvista.trame.jupyter import show_trame # noqa: PLC0415
|
||||
|
||||
return show_trame(plotter, mode=backend, **kwargs)
|
||||
|
||||
except ImportError as e:
|
||||
warnings.warn(
|
||||
f'Failed to use notebook backend: \n\n{e}\n\nFalling back to a static output.',
|
||||
)
|
||||
|
||||
return show_static_image(plotter, screenshot)
|
||||
|
||||
|
||||
def show_static_image(
|
||||
plotter: Plotter,
|
||||
screenshot: str | Path | io.BytesIO | bool | None, # noqa: FBT001
|
||||
) -> Image: # numpydoc ignore=RT01
|
||||
"""Display a static image to be displayed within a jupyter notebook."""
|
||||
import PIL.Image # noqa: PLC0415
|
||||
|
||||
if plotter.last_image is None:
|
||||
# Must render here, otherwise plotter will segfault.
|
||||
plotter.render()
|
||||
plotter.last_image = plotter.screenshot(screenshot, return_img=True)
|
||||
assert isinstance(plotter.last_image, np.ndarray)
|
||||
return PIL.Image.fromarray(plotter.last_image)
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Plotting routines."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pyvista import MAX_N_COLOR_BARS as MAX_N_COLOR_BARS
|
||||
from pyvista._plot import plot as plot
|
||||
|
||||
from . import _vtk as _vtk
|
||||
from ._property import Property as Property
|
||||
from ._typing import Chart as Chart
|
||||
from ._typing import ColorLike as ColorLike
|
||||
from .actor import Actor as Actor
|
||||
from .actor_properties import ActorProperties as ActorProperties
|
||||
from .affine_widget import AffineWidget3D as AffineWidget3D
|
||||
from .axes import Axes as Axes
|
||||
from .axes_actor import AxesActor as AxesActor
|
||||
from .axes_assembly import AxesAssembly as AxesAssembly
|
||||
from .axes_assembly import AxesAssemblySymmetric as AxesAssemblySymmetric
|
||||
from .axes_assembly import PlanesAssembly as PlanesAssembly
|
||||
from .camera import Camera as Camera
|
||||
from .charts import Chart2D as Chart2D
|
||||
from .charts import ChartBox as ChartBox
|
||||
from .charts import ChartMPL as ChartMPL
|
||||
from .charts import ChartPie as ChartPie
|
||||
from .colors import PARAVIEW_BACKGROUND as PARAVIEW_BACKGROUND
|
||||
from .colors import Color as Color
|
||||
from .colors import color_char_to_word as color_char_to_word
|
||||
from .colors import get_cmap_safe as get_cmap_safe
|
||||
from .colors import hexcolors as hexcolors
|
||||
from .composite_mapper import BlockAttributes as BlockAttributes
|
||||
from .composite_mapper import CompositeAttributes as CompositeAttributes
|
||||
from .composite_mapper import CompositePolyDataMapper as CompositePolyDataMapper
|
||||
from .cube_axes_actor import CubeAxesActor as CubeAxesActor
|
||||
from .errors import InvalidCameraError as InvalidCameraError
|
||||
from .errors import RenderWindowUnavailable as RenderWindowUnavailable
|
||||
from .follower import Follower as Follower
|
||||
from .helpers import plot_arrows as plot_arrows
|
||||
from .helpers import plot_compare_four as plot_compare_four
|
||||
from .lights import Light as Light
|
||||
from .lookup_table import LookupTable as LookupTable
|
||||
from .mapper import DataSetMapper as DataSetMapper
|
||||
from .mapper import FixedPointVolumeRayCastMapper as FixedPointVolumeRayCastMapper
|
||||
from .mapper import GPUVolumeRayCastMapper as GPUVolumeRayCastMapper
|
||||
from .mapper import OpenGLGPUVolumeRayCastMapper as OpenGLGPUVolumeRayCastMapper
|
||||
from .mapper import PointGaussianMapper as PointGaussianMapper
|
||||
from .mapper import SmartVolumeMapper as SmartVolumeMapper
|
||||
from .mapper import UnstructuredGridVolumeRayCastMapper as UnstructuredGridVolumeRayCastMapper
|
||||
from .picking import PickingHelper as PickingHelper
|
||||
from .plotter import _ALL_PLOTTERS as _ALL_PLOTTERS
|
||||
from .plotter import BasePlotter as BasePlotter
|
||||
from .plotter import Plotter as Plotter
|
||||
from .plotter import close_all as close_all
|
||||
from .prop3d import Prop3D as Prop3D
|
||||
from .render_window_interactor import RenderWindowInteractor as RenderWindowInteractor
|
||||
from .render_window_interactor import Timer as Timer
|
||||
from .renderer import CameraPosition as CameraPosition
|
||||
from .renderer import Renderer as Renderer
|
||||
from .renderer import scale_point as scale_point
|
||||
from .text import CornerAnnotation as CornerAnnotation
|
||||
from .text import Label as Label
|
||||
from .text import Text as Text
|
||||
from .text import TextProperty as TextProperty
|
||||
from .texture import Texture as Texture
|
||||
from .texture import image_to_texture as image_to_texture
|
||||
from .texture import numpy_to_texture as numpy_to_texture
|
||||
from .themes import DocumentTheme as _GlobalTheme
|
||||
from .themes import _set_plot_theme_from_env
|
||||
from .themes import load_theme as load_theme
|
||||
from .themes import set_plot_theme as set_plot_theme
|
||||
from .tools import FONTS as FONTS
|
||||
from .tools import check_math_text_support as check_math_text_support
|
||||
from .tools import check_matplotlib_vtk_compatibility as check_matplotlib_vtk_compatibility
|
||||
from .tools import create_axes_marker as create_axes_marker
|
||||
from .tools import create_axes_orientation_box as create_axes_orientation_box
|
||||
from .tools import normalize as normalize
|
||||
from .tools import opacity_transfer_function as opacity_transfer_function
|
||||
from .tools import parse_font_family as parse_font_family
|
||||
from .tools import system_supports_plotting as system_supports_plotting
|
||||
from .utilities import *
|
||||
from .utilities.sphinx_gallery import _get_sg_image_scraper as _get_sg_image_scraper
|
||||
from .volume import Volume as Volume
|
||||
from .volume_property import VolumeProperty as VolumeProperty
|
||||
from .widgets import WidgetHelper as WidgetHelper
|
||||
|
||||
|
||||
class QtDeprecationError(Exception): # numpydoc ignore=PR01
|
||||
"""Deprecation Error for features that moved to `pyvistaqt`."""
|
||||
|
||||
message = """`{}` has moved to pyvistaqt.
|
||||
You can install this from PyPI with: `pip install pyvistaqt`
|
||||
Then import it via: `from pyvistaqt import {}`
|
||||
`{}` is no longer accessible by `pyvista.{}`
|
||||
See https://github.com/pyvista/pyvistaqt
|
||||
"""
|
||||
|
||||
def __init__(self, feature_name: str) -> None:
|
||||
"""Empty init."""
|
||||
Exception.__init__(self, self.message.format(*[feature_name] * 4))
|
||||
|
||||
|
||||
class BackgroundPlotter: # numpydoc ignore=PR01
|
||||
"""This class has been moved to pyvistaqt.""" # noqa: D404
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None: # noqa: ARG002
|
||||
"""Empty init."""
|
||||
msg = 'BackgroundPlotter'
|
||||
raise QtDeprecationError(msg)
|
||||
|
||||
|
||||
class QtInteractor: # numpydoc ignore=PR01
|
||||
"""This class has been moved to pyvistaqt.""" # noqa: D404
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None: # noqa: ARG002
|
||||
"""Empty init."""
|
||||
msg = 'QtInteractor'
|
||||
raise QtDeprecationError(msg)
|
||||
|
||||
|
||||
global_theme: _GlobalTheme = _GlobalTheme()
|
||||
|
||||
# Set preferred plot theme
|
||||
_set_plot_theme_from_env()
|
||||
@@ -0,0 +1,296 @@
|
||||
"""These are private methods we keep out of plotting.py to simplify the module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core.utilities.arrays import get_array
|
||||
from pyvista.core.utilities.misc import assert_empty_kwargs
|
||||
|
||||
from .colors import Color
|
||||
from .opts import InterpolationType
|
||||
from .tools import opacity_transfer_function
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def prepare_smooth_shading( # noqa: PLR0917
|
||||
mesh: pyvista.DataSet, scalars, texture, split_sharp_edges, feature_angle, preference
|
||||
) -> tuple[pyvista.PolyData, NumpyArray[float]]:
|
||||
"""Prepare a dataset for smooth shading.
|
||||
|
||||
VTK requires datasets with Phong shading to have active normals.
|
||||
This requires extracting the external surfaces from non-polydata
|
||||
datasets and computing the point normals.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : pyvista.DataSet
|
||||
Dataset to prepare smooth shading for.
|
||||
|
||||
scalars : sequence
|
||||
Sequence of scalars.
|
||||
|
||||
texture : pyvista.Texture or np.ndarray, optional
|
||||
A texture to apply to the mesh.
|
||||
|
||||
split_sharp_edges : bool
|
||||
Split sharp edges exceeding 30 degrees when plotting with
|
||||
smooth shading. Control the angle with the optional
|
||||
keyword argument ``feature_angle``. By default this is
|
||||
``False``. Note that enabling this will create a copy of
|
||||
the input mesh within the plotter. See
|
||||
:ref:`shading_example`.
|
||||
|
||||
feature_angle : float
|
||||
Angle to consider an edge a sharp edge.
|
||||
|
||||
preference : str
|
||||
If the number of points is identical to the number of cells.
|
||||
Either ``'point'`` or ``'cell'``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
Always a surface as we need to compute point normals.
|
||||
|
||||
"""
|
||||
is_polydata = isinstance(mesh, pyvista.PolyData)
|
||||
indices_array = None
|
||||
|
||||
has_scalars = scalars is not None
|
||||
use_points = False
|
||||
if has_scalars:
|
||||
if not isinstance(scalars, np.ndarray):
|
||||
scalars = np.array(scalars)
|
||||
if scalars.shape[0] == mesh.n_points and scalars.shape[0] == mesh.n_cells:
|
||||
use_points = preference == 'point'
|
||||
else:
|
||||
use_points = scalars.shape[0] == mesh.n_points
|
||||
|
||||
# extract surface if not already a surface
|
||||
if not is_polydata:
|
||||
mesh = mesh.extract_surface(
|
||||
pass_pointid=use_points or texture is not None,
|
||||
pass_cellid=not use_points,
|
||||
)
|
||||
indices_array = 'vtkOriginalPointIds' if use_points else 'vtkOriginalCellIds'
|
||||
|
||||
try:
|
||||
if split_sharp_edges:
|
||||
mesh = mesh.compute_normals(
|
||||
cell_normals=False,
|
||||
split_vertices=True,
|
||||
feature_angle=feature_angle,
|
||||
)
|
||||
if is_polydata:
|
||||
if has_scalars and use_points:
|
||||
# we must track the original IDs with our own array from compute_normals
|
||||
indices_array = 'pyvistaOriginalPointIds'
|
||||
elif mesh.point_data.active_normals is None:
|
||||
mesh.compute_normals(cell_normals=False, inplace=True)
|
||||
except TypeError as e:
|
||||
if 'Normals cannot be computed' in repr(e):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
if has_scalars and indices_array is not None:
|
||||
ind = mesh[indices_array]
|
||||
scalars = np.asarray(scalars)[ind]
|
||||
|
||||
return mesh, scalars # type: ignore[return-value]
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def process_opacity(mesh, opacity, preference, n_colors, scalars, use_transparency): # noqa: PLR0917
|
||||
"""Process opacity.
|
||||
|
||||
This function accepts an opacity string or array and always
|
||||
returns an array that can be applied to a dataset for plotting.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : pyvista.DataSet
|
||||
Dataset to process the opacity for.
|
||||
|
||||
opacity : str, sequence
|
||||
String or array. If string, can be a ``str`` name of a
|
||||
predefined mapping such as ``'linear'``, ``'geom'``,
|
||||
``'sigmoid'``, ``'sigmoid3-10'``, or the key of a cell or
|
||||
point data array.
|
||||
|
||||
preference : str
|
||||
When ``mesh.n_points == mesh.n_cells``, this parameter
|
||||
sets how the scalars will be mapped to the mesh. If
|
||||
``'point'``, causes the scalars will be associated with
|
||||
the mesh points. Can be either ``'point'`` or
|
||||
``'cell'``.
|
||||
|
||||
n_colors : int
|
||||
Number of colors to use when displaying the opacity.
|
||||
|
||||
scalars : numpy.ndarray
|
||||
Dataset scalars.
|
||||
|
||||
use_transparency : bool
|
||||
Invert the opacity mappings and make the values correspond
|
||||
to transparency.
|
||||
|
||||
Returns
|
||||
-------
|
||||
custom_opac : bool
|
||||
If using custom opacity.
|
||||
|
||||
opacity : numpy.ndarray
|
||||
Array containing the opacity.
|
||||
|
||||
"""
|
||||
custom_opac = False
|
||||
if isinstance(opacity, str):
|
||||
try:
|
||||
# Get array from mesh
|
||||
opacity = get_array(mesh, opacity, preference=preference, err=True)
|
||||
if np.any(opacity > 1):
|
||||
warnings.warn('Opacity scalars contain values over 1')
|
||||
if np.any(opacity < 0):
|
||||
warnings.warn('Opacity scalars contain values less than 0')
|
||||
custom_opac = True
|
||||
except KeyError:
|
||||
# Or get opacity transfer function (e.g. "linear")
|
||||
opacity = opacity_transfer_function(opacity, n_colors)
|
||||
else:
|
||||
if scalars.shape[0] != opacity.shape[0]:
|
||||
msg = 'Opacity array and scalars array must have the same number of elements.'
|
||||
raise ValueError(msg)
|
||||
elif isinstance(opacity, (np.ndarray, list, tuple)):
|
||||
opacity = np.asanyarray(opacity)
|
||||
if opacity.shape[0] in [mesh.n_cells, mesh.n_points]:
|
||||
# User could pass an array of opacities for every point/cell
|
||||
custom_opac = True
|
||||
else:
|
||||
opacity = opacity_transfer_function(opacity, n_colors)
|
||||
|
||||
if use_transparency:
|
||||
if np.max(opacity) <= 1.0:
|
||||
opacity = 1 - opacity
|
||||
elif isinstance(opacity, np.ndarray):
|
||||
opacity = 255 - opacity
|
||||
|
||||
return custom_opac, opacity
|
||||
|
||||
|
||||
def _common_arg_parser(
|
||||
*,
|
||||
dataset,
|
||||
theme,
|
||||
n_colors,
|
||||
scalar_bar_args,
|
||||
split_sharp_edges,
|
||||
show_scalar_bar,
|
||||
render_points_as_spheres,
|
||||
smooth_shading,
|
||||
pbr,
|
||||
clim,
|
||||
cmap,
|
||||
culling,
|
||||
name,
|
||||
nan_color,
|
||||
nan_opacity,
|
||||
texture,
|
||||
rgb,
|
||||
style,
|
||||
**kwargs,
|
||||
):
|
||||
"""Parse arguments in common between add_volume, composite, and mesh."""
|
||||
# supported aliases
|
||||
clim = kwargs.pop('rng', clim)
|
||||
cmap = kwargs.pop('colormap', cmap)
|
||||
culling = kwargs.pop('backface_culling', culling)
|
||||
rgb = kwargs.pop('rgba', rgb)
|
||||
vertex_color = kwargs.pop('vertex_color', theme.edge_color)
|
||||
vertex_style = kwargs.pop('vertex_style', 'points')
|
||||
vertex_opacity = kwargs.pop('vertex_opacity', 1.0)
|
||||
|
||||
# Support aliases for 'back', 'front', or 'none'. Consider deprecating
|
||||
if culling is False:
|
||||
culling = 'none'
|
||||
elif culling in ['b', 'backface', True]:
|
||||
culling = 'back'
|
||||
elif culling in ['f', 'frontface']:
|
||||
culling = 'front'
|
||||
|
||||
if show_scalar_bar is None:
|
||||
# use theme unless plotting RGB
|
||||
_default = theme.show_scalar_bar or scalar_bar_args
|
||||
show_scalar_bar = False if rgb else _default
|
||||
# Avoid mutating input
|
||||
scalar_bar_args = {'n_colors': n_colors} if scalar_bar_args is None else scalar_bar_args.copy()
|
||||
|
||||
# theme based parameters
|
||||
if split_sharp_edges is None:
|
||||
split_sharp_edges = theme.split_sharp_edges
|
||||
feature_angle = kwargs.pop('feature_angle', theme.sharp_edges_feature_angle)
|
||||
if render_points_as_spheres is None:
|
||||
if style == 'points_gaussian':
|
||||
render_points_as_spheres = False
|
||||
else:
|
||||
render_points_as_spheres = theme.render_points_as_spheres
|
||||
|
||||
if smooth_shading is None:
|
||||
smooth_shading = True if pbr else theme.smooth_shading
|
||||
|
||||
if name is None:
|
||||
name = f'{type(dataset).__name__}({dataset.memory_address})'
|
||||
remove_existing_actor = False
|
||||
else:
|
||||
# check if this actor already exists
|
||||
remove_existing_actor = True
|
||||
|
||||
nan_color = Color(nan_color, opacity=nan_opacity, default_color=theme.nan_color)
|
||||
|
||||
if texture is False:
|
||||
texture = None
|
||||
|
||||
# allow directly specifying interpolation (potential future feature)
|
||||
if 'interpolation' in kwargs:
|
||||
interpolation = kwargs.pop('interpolation') # pragma: no cover:
|
||||
elif pbr:
|
||||
interpolation = InterpolationType.PBR
|
||||
elif smooth_shading:
|
||||
interpolation = InterpolationType.PHONG
|
||||
else:
|
||||
interpolation = theme.lighting_params.interpolation
|
||||
|
||||
if 'scalar' in kwargs:
|
||||
msg = '`scalar` is an invalid keyword argument. Perhaps you mean `scalars` with an s?'
|
||||
raise TypeError(msg)
|
||||
|
||||
assert_empty_kwargs(**kwargs)
|
||||
return (
|
||||
scalar_bar_args,
|
||||
split_sharp_edges,
|
||||
show_scalar_bar,
|
||||
feature_angle,
|
||||
render_points_as_spheres,
|
||||
smooth_shading,
|
||||
clim,
|
||||
cmap,
|
||||
culling,
|
||||
name,
|
||||
nan_color,
|
||||
texture,
|
||||
rgb,
|
||||
interpolation,
|
||||
remove_existing_actor,
|
||||
vertex_color,
|
||||
vertex_style,
|
||||
vertex_opacity,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
"""Type aliases for type hints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
from typing import TypedDict
|
||||
from typing import Union
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
from pyvista.core._typing_core import BoundsTuple as BoundsTuple
|
||||
from pyvista.core._typing_core import MatrixLike
|
||||
from pyvista.core._typing_core import Number as Number
|
||||
from pyvista.core._typing_core import NumpyArray
|
||||
from pyvista.core._typing_core import VectorLike
|
||||
|
||||
from . import _vtk
|
||||
from .renderer import CameraPosition
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyvista.plotting.themes import Theme
|
||||
|
||||
from .charts import Chart2D as Chart2D
|
||||
from .charts import ChartBox as ChartBox
|
||||
from .charts import ChartMPL as ChartMPL
|
||||
from .charts import ChartPie as ChartPie
|
||||
from .colors import _CMCRAMERI_CMAPS_LITERAL
|
||||
from .colors import _CMOCEAN_CMAPS_LITERAL
|
||||
from .colors import _COLORCET_CMAPS_LITERAL
|
||||
from .colors import _MATPLOTLIB_CMAPS_LITERAL
|
||||
from .colors import Color as Color
|
||||
|
||||
NamedColormaps = Union[
|
||||
'_MATPLOTLIB_CMAPS_LITERAL',
|
||||
'_CMOCEAN_CMAPS_LITERAL',
|
||||
'_COLORCET_CMAPS_LITERAL',
|
||||
'_CMCRAMERI_CMAPS_LITERAL',
|
||||
]
|
||||
|
||||
ColormapOptions = Union[NamedColormaps, list[str], mpl.colors.Colormap]
|
||||
|
||||
ColorLike = Union[
|
||||
tuple[int, int, int],
|
||||
tuple[int, int, int, int],
|
||||
tuple[float, float, float],
|
||||
tuple[float, float, float, float],
|
||||
Sequence[int],
|
||||
Sequence[float],
|
||||
NumpyArray[float],
|
||||
dict[str, Union[int, float, str]],
|
||||
str,
|
||||
'Color',
|
||||
_vtk.vtkColor3ub,
|
||||
]
|
||||
# Overwrite default docstring, as sphinx is not able to capture the docstring
|
||||
# when it is put beneath the definition somehow?
|
||||
ColorLike.__doc__ = 'Any object convertible to a :class:`Color`.'
|
||||
Chart = Union['Chart2D', 'ChartBox', 'ChartPie', 'ChartMPL']
|
||||
FontFamilyOptions = Literal['courier', 'times', 'arial']
|
||||
OpacityOptions = Literal[
|
||||
'linear',
|
||||
'linear_r',
|
||||
'geom',
|
||||
'geom_r',
|
||||
'sigmoid',
|
||||
'sigmoid_1',
|
||||
'sigmoid_2',
|
||||
'sigmoid_3',
|
||||
'sigmoid_4',
|
||||
'sigmoid_5',
|
||||
'sigmoid_6',
|
||||
'sigmoid_7',
|
||||
'sigmoid_8',
|
||||
'sigmoid_9',
|
||||
'sigmoid_10',
|
||||
'sigmoid_15',
|
||||
'sigmoid_20',
|
||||
'foreground',
|
||||
]
|
||||
CullingOptions = Literal['front', 'back', 'frontface', 'backface', 'f', 'b']
|
||||
StyleOptions = Literal['surface', 'wireframe', 'points', 'points_gaussian']
|
||||
LightingOptions = Literal['light kit', 'three lights', 'none']
|
||||
CameraPositionOptions = Union[
|
||||
Literal['xy', 'xz', 'yz', 'yx', 'zx', 'zy', 'iso'],
|
||||
VectorLike[float],
|
||||
MatrixLike[float],
|
||||
CameraPosition,
|
||||
]
|
||||
|
||||
|
||||
class BackfaceArgs(TypedDict, total=False):
|
||||
theme: Theme
|
||||
interpolation: Literal['Physically based rendering', 'pbr', 'Phong', 'Gouraud', 'Flat']
|
||||
color: ColorLike
|
||||
style: StyleOptions
|
||||
metallic: float
|
||||
roughness: float
|
||||
point_size: float
|
||||
opacity: float
|
||||
ambient: float
|
||||
diffuse: float
|
||||
specular: float
|
||||
specular_power: float
|
||||
show_edges: bool
|
||||
edge_color: ColorLike
|
||||
render_points_as_spheres: bool
|
||||
render_lines_as_tubes: bool
|
||||
lighting: bool
|
||||
line_width: float
|
||||
culling: CullingOptions | bool
|
||||
edge_opacity: float
|
||||
|
||||
|
||||
class ScalarBarArgs(TypedDict, total=False):
|
||||
title: str
|
||||
mapper: _vtk.vtkMapper
|
||||
n_labels: int
|
||||
italic: bool
|
||||
bold: bool
|
||||
title_font_size: float
|
||||
label_font_size: float
|
||||
color: ColorLike
|
||||
font_family: FontFamilyOptions
|
||||
shadow: bool
|
||||
width: float
|
||||
height: float
|
||||
position_x: float
|
||||
position_y: float
|
||||
vertical: bool
|
||||
interactive: bool
|
||||
fmt: str
|
||||
use_opacity: bool
|
||||
outline: bool
|
||||
nan_annotation: bool
|
||||
below_label: str
|
||||
above_label: str
|
||||
background_color: ColorLike
|
||||
n_colors: int
|
||||
fill: bool
|
||||
render: bool
|
||||
theme: Theme
|
||||
unconstrained_font_size: bool
|
||||
|
||||
|
||||
class SilhouetteArgs(TypedDict, total=False):
|
||||
color: ColorLike
|
||||
line_width: float
|
||||
opacity: float
|
||||
feature_angle: float
|
||||
decimate: float
|
||||
@@ -0,0 +1,168 @@
|
||||
"""All imports from VTK (including GL-dependent).
|
||||
|
||||
These are the modules within VTK that must be loaded across pyvista's
|
||||
plotting API. Here, we attempt to import modules using the ``vtkmodules``
|
||||
package, which lets us only have to import from select modules and not
|
||||
the entire library.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from vtkmodules.vtkChartsCore import vtkAxis as vtkAxis
|
||||
from vtkmodules.vtkChartsCore import vtkChart as vtkChart
|
||||
from vtkmodules.vtkChartsCore import vtkChartBox as vtkChartBox
|
||||
from vtkmodules.vtkChartsCore import vtkChartPie as vtkChartPie
|
||||
from vtkmodules.vtkChartsCore import vtkChartXY as vtkChartXY
|
||||
from vtkmodules.vtkChartsCore import vtkChartXYZ as vtkChartXYZ
|
||||
from vtkmodules.vtkChartsCore import vtkPlotArea as vtkPlotArea
|
||||
from vtkmodules.vtkChartsCore import vtkPlotBar as vtkPlotBar
|
||||
from vtkmodules.vtkChartsCore import vtkPlotBox as vtkPlotBox
|
||||
from vtkmodules.vtkChartsCore import vtkPlotLine as vtkPlotLine
|
||||
from vtkmodules.vtkChartsCore import vtkPlotLine3D as vtkPlotLine3D
|
||||
from vtkmodules.vtkChartsCore import vtkPlotPie as vtkPlotPie
|
||||
from vtkmodules.vtkChartsCore import vtkPlotPoints as vtkPlotPoints
|
||||
from vtkmodules.vtkChartsCore import vtkPlotPoints3D as vtkPlotPoints3D
|
||||
from vtkmodules.vtkChartsCore import vtkPlotStacked as vtkPlotStacked
|
||||
from vtkmodules.vtkChartsCore import vtkPlotSurface as vtkPlotSurface
|
||||
from vtkmodules.vtkCommonColor import vtkColorSeries as vtkColorSeries
|
||||
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage as vtkInteractorStyleImage
|
||||
from vtkmodules.vtkInteractionStyle import (
|
||||
vtkInteractorStyleJoystickActor as vtkInteractorStyleJoystickActor,
|
||||
)
|
||||
from vtkmodules.vtkInteractionStyle import (
|
||||
vtkInteractorStyleJoystickCamera as vtkInteractorStyleJoystickCamera,
|
||||
)
|
||||
from vtkmodules.vtkInteractionStyle import (
|
||||
vtkInteractorStyleRubberBand2D as vtkInteractorStyleRubberBand2D,
|
||||
)
|
||||
from vtkmodules.vtkInteractionStyle import (
|
||||
vtkInteractorStyleRubberBandPick as vtkInteractorStyleRubberBandPick,
|
||||
)
|
||||
from vtkmodules.vtkInteractionStyle import (
|
||||
vtkInteractorStyleRubberBandZoom as vtkInteractorStyleRubberBandZoom,
|
||||
)
|
||||
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTerrain as vtkInteractorStyleTerrain
|
||||
from vtkmodules.vtkInteractionStyle import (
|
||||
vtkInteractorStyleTrackballActor as vtkInteractorStyleTrackballActor,
|
||||
)
|
||||
from vtkmodules.vtkInteractionStyle import (
|
||||
vtkInteractorStyleTrackballCamera as vtkInteractorStyleTrackballCamera,
|
||||
)
|
||||
from vtkmodules.vtkInteractionWidgets import vtkBoxWidget as vtkBoxWidget
|
||||
from vtkmodules.vtkInteractionWidgets import vtkButtonWidget as vtkButtonWidget
|
||||
from vtkmodules.vtkInteractionWidgets import (
|
||||
vtkDistanceRepresentation3D as vtkDistanceRepresentation3D,
|
||||
)
|
||||
from vtkmodules.vtkInteractionWidgets import vtkDistanceWidget as vtkDistanceWidget
|
||||
from vtkmodules.vtkInteractionWidgets import vtkImplicitPlaneWidget as vtkImplicitPlaneWidget
|
||||
from vtkmodules.vtkInteractionWidgets import vtkLineWidget as vtkLineWidget
|
||||
from vtkmodules.vtkInteractionWidgets import vtkLogoRepresentation as vtkLogoRepresentation
|
||||
from vtkmodules.vtkInteractionWidgets import vtkLogoWidget as vtkLogoWidget
|
||||
from vtkmodules.vtkInteractionWidgets import (
|
||||
vtkOrientationMarkerWidget as vtkOrientationMarkerWidget,
|
||||
)
|
||||
from vtkmodules.vtkInteractionWidgets import vtkPlaneWidget as vtkPlaneWidget
|
||||
from vtkmodules.vtkInteractionWidgets import (
|
||||
vtkPointHandleRepresentation3D as vtkPointHandleRepresentation3D,
|
||||
)
|
||||
from vtkmodules.vtkInteractionWidgets import vtkResliceCursorPicker as vtkResliceCursorPicker
|
||||
from vtkmodules.vtkInteractionWidgets import vtkScalarBarWidget as vtkScalarBarWidget
|
||||
from vtkmodules.vtkInteractionWidgets import vtkSliderRepresentation2D as vtkSliderRepresentation2D
|
||||
from vtkmodules.vtkInteractionWidgets import vtkSliderWidget as vtkSliderWidget
|
||||
from vtkmodules.vtkInteractionWidgets import vtkSphereWidget as vtkSphereWidget
|
||||
from vtkmodules.vtkInteractionWidgets import vtkSplineWidget as vtkSplineWidget
|
||||
from vtkmodules.vtkInteractionWidgets import (
|
||||
vtkTexturedButtonRepresentation2D as vtkTexturedButtonRepresentation2D,
|
||||
)
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkAnnotatedCubeActor as vtkAnnotatedCubeActor
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkAxesActor as vtkAxesActor
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkAxisActor as vtkAxisActor
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkAxisActor2D as vtkAxisActor2D
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkCornerAnnotation as vtkCornerAnnotation
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkCubeAxesActor as vtkCubeAxesActor
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkLegendBoxActor as vtkLegendBoxActor
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkLegendScaleActor as vtkLegendScaleActor
|
||||
from vtkmodules.vtkRenderingAnnotation import vtkScalarBarActor as vtkScalarBarActor
|
||||
from vtkmodules.vtkRenderingContext2D import vtkBlockItem as vtkBlockItem
|
||||
from vtkmodules.vtkRenderingContext2D import vtkBrush as vtkBrush
|
||||
from vtkmodules.vtkRenderingContext2D import vtkContext2D as vtkContext2D
|
||||
from vtkmodules.vtkRenderingContext2D import vtkContextActor as vtkContextActor
|
||||
from vtkmodules.vtkRenderingContext2D import vtkContextScene as vtkContextScene
|
||||
from vtkmodules.vtkRenderingContext2D import vtkImageItem as vtkImageItem
|
||||
from vtkmodules.vtkRenderingContext2D import vtkPen as vtkPen
|
||||
|
||||
try:
|
||||
from vtkmodules.vtkRenderingCore import vtkHardwarePicker as vtkHardwarePicker
|
||||
except ImportError: # pragma: no cover
|
||||
# VTK < 9.2 is missing this class
|
||||
vtkHardwarePicker = None # type: ignore[assignment, misc] # noqa: N816
|
||||
from vtkmodules.vtkRenderingCore import VTK_RESOLVE_OFF as VTK_RESOLVE_OFF
|
||||
from vtkmodules.vtkRenderingCore import VTK_RESOLVE_POLYGON_OFFSET as VTK_RESOLVE_POLYGON_OFFSET
|
||||
from vtkmodules.vtkRenderingCore import VTK_RESOLVE_SHIFT_ZBUFFER as VTK_RESOLVE_SHIFT_ZBUFFER
|
||||
from vtkmodules.vtkRenderingCore import vtkAbstractMapper as vtkAbstractMapper
|
||||
from vtkmodules.vtkRenderingCore import vtkActor as vtkActor
|
||||
from vtkmodules.vtkRenderingCore import vtkActor2D as vtkActor2D
|
||||
from vtkmodules.vtkRenderingCore import vtkAreaPicker as vtkAreaPicker
|
||||
from vtkmodules.vtkRenderingCore import vtkCamera as vtkCamera
|
||||
from vtkmodules.vtkRenderingCore import vtkCellPicker as vtkCellPicker
|
||||
from vtkmodules.vtkRenderingCore import vtkColorTransferFunction as vtkColorTransferFunction
|
||||
from vtkmodules.vtkRenderingCore import (
|
||||
vtkCompositeDataDisplayAttributes as vtkCompositeDataDisplayAttributes,
|
||||
)
|
||||
from vtkmodules.vtkRenderingCore import vtkCompositePolyDataMapper as vtkCompositePolyDataMapper
|
||||
from vtkmodules.vtkRenderingCore import vtkCoordinate as vtkCoordinate
|
||||
from vtkmodules.vtkRenderingCore import vtkDataSetMapper as vtkDataSetMapper
|
||||
from vtkmodules.vtkRenderingCore import vtkFollower as vtkFollower
|
||||
from vtkmodules.vtkRenderingCore import vtkImageActor as vtkImageActor
|
||||
from vtkmodules.vtkRenderingCore import vtkInteractorStyle as vtkInteractorStyle
|
||||
from vtkmodules.vtkRenderingCore import vtkLight as vtkLight
|
||||
from vtkmodules.vtkRenderingCore import vtkLightActor as vtkLightActor
|
||||
from vtkmodules.vtkRenderingCore import vtkLightKit as vtkLightKit
|
||||
from vtkmodules.vtkRenderingCore import vtkMapper as vtkMapper
|
||||
from vtkmodules.vtkRenderingCore import vtkPointGaussianMapper as vtkPointGaussianMapper
|
||||
from vtkmodules.vtkRenderingCore import vtkPointPicker as vtkPointPicker
|
||||
from vtkmodules.vtkRenderingCore import vtkPolyDataMapper as vtkPolyDataMapper
|
||||
from vtkmodules.vtkRenderingCore import vtkPolyDataMapper2D as vtkPolyDataMapper2D
|
||||
from vtkmodules.vtkRenderingCore import vtkProp as vtkProp
|
||||
from vtkmodules.vtkRenderingCore import vtkProp3D as vtkProp3D
|
||||
from vtkmodules.vtkRenderingCore import vtkPropAssembly as vtkPropAssembly
|
||||
from vtkmodules.vtkRenderingCore import vtkPropCollection as vtkPropCollection
|
||||
from vtkmodules.vtkRenderingCore import vtkProperty as vtkProperty
|
||||
from vtkmodules.vtkRenderingCore import vtkPropPicker as vtkPropPicker
|
||||
from vtkmodules.vtkRenderingCore import vtkRenderedAreaPicker as vtkRenderedAreaPicker
|
||||
from vtkmodules.vtkRenderingCore import vtkRenderer as vtkRenderer
|
||||
from vtkmodules.vtkRenderingCore import vtkRenderWindow as vtkRenderWindow
|
||||
from vtkmodules.vtkRenderingCore import vtkRenderWindowInteractor as vtkRenderWindowInteractor
|
||||
from vtkmodules.vtkRenderingCore import vtkScenePicker as vtkScenePicker
|
||||
from vtkmodules.vtkRenderingCore import vtkSelectVisiblePoints as vtkSelectVisiblePoints
|
||||
from vtkmodules.vtkRenderingCore import vtkSkybox as vtkSkybox
|
||||
from vtkmodules.vtkRenderingCore import vtkTextActor as vtkTextActor
|
||||
from vtkmodules.vtkRenderingCore import vtkTextProperty as vtkTextProperty
|
||||
from vtkmodules.vtkRenderingCore import vtkTexture as vtkTexture
|
||||
from vtkmodules.vtkRenderingCore import vtkVolume as vtkVolume
|
||||
from vtkmodules.vtkRenderingCore import vtkVolumeProperty as vtkVolumeProperty
|
||||
from vtkmodules.vtkRenderingCore import vtkWindowToImageFilter as vtkWindowToImageFilter
|
||||
from vtkmodules.vtkRenderingCore import vtkWorldPointPicker as vtkWorldPointPicker
|
||||
from vtkmodules.vtkRenderingFreeType import (
|
||||
vtkMathTextFreeTypeTextRenderer as vtkMathTextFreeTypeTextRenderer,
|
||||
)
|
||||
from vtkmodules.vtkRenderingFreeType import vtkVectorText as vtkVectorText
|
||||
from vtkmodules.vtkRenderingLabel import vtkLabelPlacementMapper as vtkLabelPlacementMapper
|
||||
from vtkmodules.vtkRenderingLabel import vtkPointSetToLabelHierarchy as vtkPointSetToLabelHierarchy
|
||||
from vtkmodules.vtkRenderingUI import (
|
||||
vtkGenericRenderWindowInteractor as vtkGenericRenderWindowInteractor,
|
||||
)
|
||||
from vtkmodules.vtkRenderingVolume import (
|
||||
vtkFixedPointVolumeRayCastMapper as vtkFixedPointVolumeRayCastMapper,
|
||||
)
|
||||
from vtkmodules.vtkRenderingVolume import vtkGPUVolumeRayCastMapper as vtkGPUVolumeRayCastMapper
|
||||
from vtkmodules.vtkRenderingVolume import (
|
||||
vtkUnstructuredGridVolumeRayCastMapper as vtkUnstructuredGridVolumeRayCastMapper,
|
||||
)
|
||||
from vtkmodules.vtkRenderingVolume import vtkVolumePicker as vtkVolumePicker
|
||||
from vtkmodules.vtkViewsContext2D import vtkContextInteractorStyle as vtkContextInteractorStyle
|
||||
|
||||
from pyvista.core._vtk_core import *
|
||||
|
||||
from ._vtk_gl import *
|
||||
@@ -0,0 +1,44 @@
|
||||
"""GL-dependent imports from VTK.
|
||||
|
||||
These are the modules within VTK requiring libGL that must be loaded
|
||||
across pyvista's plotting API. These imports have the potential to
|
||||
raise an ImportError if the user does not have libGL installed.
|
||||
|
||||
ImportError: libGL.so.1: cannot open shared object file: No such file or directory
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
|
||||
try:
|
||||
# Necessary for displaying charts, otherwise crashes on rendering
|
||||
from vtkmodules import vtkRenderingContextOpenGL2 as vtkRenderingContextOpenGL2
|
||||
except ImportError: # pragma: no cover
|
||||
vtkRenderingContextOpenGL2 = None # type: ignore[assignment] # noqa: N816
|
||||
|
||||
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkCameraPass as vtkCameraPass
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
from vtkmodules.vtkRenderingOpenGL2 import ( # type: ignore[attr-defined]
|
||||
vtkCompositePolyDataMapper2 as vtkCompositePolyDataMapper2,
|
||||
)
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkDepthOfFieldPass as vtkDepthOfFieldPass
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkEDLShading as vtkEDLShading
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkGaussianBlurPass as vtkGaussianBlurPass
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkOpenGLFXAAPass as vtkOpenGLFXAAPass
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkOpenGLHardwareSelector as vtkOpenGLHardwareSelector
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkOpenGLRenderer as vtkOpenGLRenderer
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkOpenGLTexture as vtkOpenGLTexture
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkRenderPassCollection as vtkRenderPassCollection
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkRenderStepsPass as vtkRenderStepsPass
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkSequencePass as vtkSequencePass
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkShadowMapPass as vtkShadowMapPass
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkSSAAPass as vtkSSAAPass
|
||||
from vtkmodules.vtkRenderingOpenGL2 import vtkSSAOPass as vtkSSAOPass
|
||||
from vtkmodules.vtkRenderingVolumeOpenGL2 import (
|
||||
vtkOpenGLGPUVolumeRayCastMapper as vtkOpenGLGPUVolumeRayCastMapper,
|
||||
)
|
||||
from vtkmodules.vtkRenderingVolumeOpenGL2 import vtkSmartVolumeMapper as vtkSmartVolumeMapper
|
||||
@@ -0,0 +1,455 @@
|
||||
"""Wrap :vtk:`vtkActor` module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
from . import _vtk
|
||||
from ._property import Property
|
||||
from .prop3d import Prop3D
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .mapper import _BaseMapper
|
||||
|
||||
|
||||
class Actor(Prop3D, _vtk.vtkActor):
|
||||
"""Wrap :vtk:`vtkActor`.
|
||||
|
||||
This class represents the geometry & properties in a rendered
|
||||
scene. Normally, a :class:`pyvista.Actor` is constructed from
|
||||
:func:`pyvista.Plotter.add_mesh`, but there may be times when it is more
|
||||
convenient to construct an actor directly from a
|
||||
:class:`pyvista.DataSetMapper`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mapper : pyvista.DataSetMapper, optional
|
||||
DataSetMapper.
|
||||
|
||||
prop : pyvista.Property, optional
|
||||
Property of the actor.
|
||||
|
||||
name : str, optional
|
||||
The name of this actor used when tracking on a plotter.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor without using :class:`pyvista.Plotter`.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mapper = pv.DataSetMapper(mesh)
|
||||
>>> actor = pv.Actor(mapper=mapper)
|
||||
>>> actor
|
||||
Actor (...)
|
||||
Center: (0.0, 0.0, 0.0)
|
||||
Pickable: True
|
||||
Position: (0.0, 0.0, 0.0)
|
||||
Scale: (1.0, 1.0, 1.0)
|
||||
Visible: True
|
||||
X Bounds -4.993E-01, 4.993E-01
|
||||
Y Bounds -4.965E-01, 4.965E-01
|
||||
Z Bounds -5.000E-01, 5.000E-01
|
||||
User matrix: Identity
|
||||
Has mapper: True
|
||||
...
|
||||
|
||||
Change the actor properties and plot the actor.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mapper = pv.DataSetMapper(mesh)
|
||||
>>> actor = pv.Actor(mapper=mapper)
|
||||
>>> actor.prop.color = 'blue'
|
||||
>>> actor.plot()
|
||||
|
||||
Create an actor using the :class:`pyvista.Plotter` and then change the
|
||||
visibility of the actor.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> actor = pl.add_mesh(mesh)
|
||||
>>> actor.visibility = False
|
||||
>>> actor.visibility
|
||||
False
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, mapper=None, prop=None, name=None) -> None:
|
||||
"""Initialize actor."""
|
||||
super().__init__()
|
||||
if mapper is not None:
|
||||
self.mapper = mapper
|
||||
if prop is None:
|
||||
self.prop = Property()
|
||||
else:
|
||||
self.prop = prop
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def mapper(self) -> _BaseMapper: # numpydoc ignore=RT01
|
||||
"""Return or set the mapper of the actor.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor and assign a mapper to it.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> dataset = pv.Sphere()
|
||||
>>> actor = pv.Actor()
|
||||
>>> actor.mapper = pv.DataSetMapper(dataset)
|
||||
>>> actor.mapper
|
||||
DataSetMapper (...)
|
||||
Scalar visibility: True
|
||||
Scalar range: (0.0, 1.0)
|
||||
Interpolate before mapping: True
|
||||
Scalar map mode: default
|
||||
Color mode: direct
|
||||
<BLANKLINE>
|
||||
Attached dataset:
|
||||
PolyData (...)
|
||||
N Cells: 1680
|
||||
N Points: 842
|
||||
N Strips: 0
|
||||
X Bounds: -4.993e-01, 4.993e-01
|
||||
Y Bounds: -4.965e-01, 4.965e-01
|
||||
Z Bounds: -5.000e-01, 5.000e-01
|
||||
N Arrays: 1
|
||||
|
||||
"""
|
||||
return self.GetMapper() # type: ignore[return-value]
|
||||
|
||||
@mapper.setter
|
||||
def mapper(self, obj) -> None:
|
||||
self.SetMapper(obj)
|
||||
|
||||
@property
|
||||
def prop(self): # numpydoc ignore=RT01
|
||||
"""Return or set the property of this actor.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Modify the properties of an actor after adding a dataset to the plotter.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(pv.Sphere())
|
||||
>>> prop = actor.prop
|
||||
>>> prop.diffuse = 0.6
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return self.GetProperty()
|
||||
|
||||
@prop.setter
|
||||
def prop(self, obj: Property) -> None:
|
||||
self.SetProperty(obj)
|
||||
|
||||
@property
|
||||
def texture(self): # numpydoc ignore=RT01
|
||||
"""Return or set the actor texture.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The mapper dataset must have texture coordinates for the texture to be
|
||||
used.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor and add a texture to it. Note how the
|
||||
:class:`pyvista.PolyData` has texture coordinates by default.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> plane = pv.Plane()
|
||||
>>> plane.active_texture_coordinates is not None
|
||||
True
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(plane)
|
||||
>>> actor.texture = examples.download_masonry_texture()
|
||||
>>> actor.texture
|
||||
Texture (...)
|
||||
Components: 3
|
||||
Cube Map: False
|
||||
Dimensions: 256, 256
|
||||
|
||||
"""
|
||||
return self.GetTexture()
|
||||
|
||||
@texture.setter
|
||||
def texture(self, obj) -> None:
|
||||
self.SetTexture(obj)
|
||||
|
||||
@property
|
||||
def memory_address(self): # numpydoc ignore=RT01
|
||||
"""Return the memory address of this actor."""
|
||||
return self.GetAddressAsString('')
|
||||
|
||||
@property
|
||||
def pickable(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return or set actor pickability.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor using the :class:`pyvista.Plotter` and then make the
|
||||
actor unpickable.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(pv.Sphere())
|
||||
>>> actor.pickable = False
|
||||
>>> actor.pickable
|
||||
False
|
||||
|
||||
"""
|
||||
return bool(self.GetPickable())
|
||||
|
||||
@pickable.setter
|
||||
def pickable(self, value) -> None:
|
||||
self.SetPickable(value)
|
||||
|
||||
@property
|
||||
def visibility(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return or set actor visibility.
|
||||
|
||||
See Also
|
||||
--------
|
||||
use_bounds
|
||||
pyvista.Plotter.compute_bounds
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor using the :class:`pyvista.Plotter` and then change the
|
||||
visibility of the actor.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_airplane()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(mesh)
|
||||
>>> pl.bounds
|
||||
BoundsTuple(x_min = 139.06100463867188,
|
||||
x_max = 1654.9300537109375,
|
||||
y_min = 32.09429931640625,
|
||||
y_max = 1319.949951171875,
|
||||
z_min = -17.741199493408203,
|
||||
z_max = 282.1300048828125)
|
||||
|
||||
>>> actor.visibility = False
|
||||
>>> pl.bounds
|
||||
BoundsTuple(x_min = -1.0,
|
||||
x_max = 1.0,
|
||||
y_min = -1.0,
|
||||
y_max = 1.0,
|
||||
z_min = -1.0,
|
||||
z_max = 1.0)
|
||||
|
||||
"""
|
||||
return bool(self.GetVisibility())
|
||||
|
||||
@visibility.setter
|
||||
def visibility(self, value: bool) -> None:
|
||||
self.SetVisibility(value)
|
||||
|
||||
@property
|
||||
def use_bounds(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return or set the use of actor's bounds.
|
||||
|
||||
.. versionadded:: 0.45
|
||||
|
||||
See Also
|
||||
--------
|
||||
visibility
|
||||
pyvista.Plotter.compute_bounds
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor using the :class:`pyvista.Plotter` and then change the
|
||||
use of bounds for the actor.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> mesh = examples.load_airplane()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(mesh)
|
||||
>>> pl.bounds
|
||||
BoundsTuple(x_min = 139.06100463867188,
|
||||
x_max = 1654.9300537109375,
|
||||
y_min = 32.09429931640625,
|
||||
y_max = 1319.949951171875,
|
||||
z_min = -17.741199493408203,
|
||||
z_max = 282.1300048828125)
|
||||
|
||||
>>> actor.use_bounds = False
|
||||
>>> pl.bounds
|
||||
BoundsTuple(x_min = -1.0,
|
||||
x_max = 1.0,
|
||||
y_min = -1.0,
|
||||
y_max = 1.0,
|
||||
z_min = -1.0,
|
||||
z_max = 1.0)
|
||||
|
||||
Although the actor's bounds are no longer used, the actor remains visible.
|
||||
|
||||
>>> actor.visibility
|
||||
True
|
||||
|
||||
"""
|
||||
return bool(self.GetUseBounds())
|
||||
|
||||
@use_bounds.setter
|
||||
def use_bounds(self, value: bool) -> None:
|
||||
self.SetUseBounds(value)
|
||||
|
||||
def plot(self, **kwargs) -> None:
|
||||
"""Plot just the actor.
|
||||
|
||||
This may be useful when interrogating or debugging individual actors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
**kwargs : dict, optional
|
||||
Optional keyword arguments passed to :func:`pyvista.Plotter.show`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor without the :class:`pyvista.Plotter`, change its
|
||||
properties, and plot it.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Sphere()
|
||||
>>> mapper = pv.DataSetMapper(mesh)
|
||||
>>> actor = pv.Actor(mapper=mapper)
|
||||
>>> actor.prop.color = 'red'
|
||||
>>> actor.prop.show_edges = True
|
||||
>>> actor.plot()
|
||||
|
||||
"""
|
||||
pl = pyvista.Plotter()
|
||||
pl.add_actor(self)
|
||||
pl.show(**kwargs)
|
||||
|
||||
@_deprecate_positional_args
|
||||
def copy(self: Self, deep: bool = True) -> Self: # noqa: FBT001, FBT002
|
||||
"""Create a copy of this actor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
deep : bool, default: True
|
||||
Create a shallow or deep copy of the actor. A deep copy will have a
|
||||
new property and mapper, while a shallow copy will use the mapper
|
||||
and property of this actor.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Actor
|
||||
Deep or shallow copy of this actor.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an actor of a cube by adding it to a :class:`~pyvista.Plotter`
|
||||
and then copy the actor, change the properties, and add it back to the
|
||||
:class:`~pyvista.Plotter`.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> mesh = pv.Cube()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(mesh, color='b')
|
||||
>>> new_actor = actor.copy()
|
||||
>>> new_actor.prop.style = 'wireframe'
|
||||
>>> new_actor.prop.line_width = 5
|
||||
>>> new_actor.prop.color = 'r'
|
||||
>>> new_actor.prop.lighting = False
|
||||
>>> _ = pl.add_actor(new_actor)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
new_actor = type(self)()
|
||||
if deep:
|
||||
if self.mapper is not None:
|
||||
new_actor.mapper = self.mapper.copy()
|
||||
new_actor.prop = self.prop.copy()
|
||||
else:
|
||||
new_actor.ShallowCopy(self)
|
||||
return new_actor
|
||||
|
||||
def __repr__(self):
|
||||
"""Representation of the actor."""
|
||||
mat_info = 'Identity' if np.array_equal(self.user_matrix, np.eye(4)) else 'Set'
|
||||
bnd = self.bounds
|
||||
attr = [
|
||||
f'{type(self).__name__} ({hex(id(self))})',
|
||||
f' Center: {self.center}',
|
||||
f' Pickable: {self.pickable}',
|
||||
f' Position: {self.position}',
|
||||
f' Scale: {self.scale}',
|
||||
f' Visible: {self.visibility}',
|
||||
f' X Bounds {bnd[0]:.3E}, {bnd[1]:.3E}',
|
||||
f' Y Bounds {bnd[2]:.3E}, {bnd[3]:.3E}',
|
||||
f' Z Bounds {bnd[4]:.3E}, {bnd[5]:.3E}',
|
||||
f' User matrix: {mat_info}',
|
||||
f' Has mapper: {self.mapper is not None}',
|
||||
'',
|
||||
repr(self.prop),
|
||||
]
|
||||
if self.mapper is not None:
|
||||
attr.append('')
|
||||
attr.append(repr(self.mapper))
|
||||
return '\n'.join(attr)
|
||||
|
||||
@property
|
||||
def backface_prop(self) -> pyvista.Property | None: # numpydoc ignore=RT01
|
||||
"""Return or set the backface property.
|
||||
|
||||
By default this property matches the frontface property
|
||||
:attr:`Actor.prop`. Once accessed or modified, this backface
|
||||
property becomes independent of the frontface property. In
|
||||
order to restore the fallback to frontface property, assign
|
||||
``None`` to the property.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Property
|
||||
The object describing backfaces.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:ref:`backface_prop_example`
|
||||
|
||||
Examples
|
||||
--------
|
||||
Clip a sphere by a plane and color the inside of the clipped sphere
|
||||
light blue using the ``backface_prop``.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> plane = pv.Plane(i_size=1.5, j_size=1.5)
|
||||
>>> mesh = pv.Sphere().clip_surface(plane, invert=False)
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(mesh, smooth_shading=True)
|
||||
>>> actor.backface_prop.color = 'lightblue'
|
||||
>>> _ = pl.add_mesh(
|
||||
... plane,
|
||||
... opacity=0.25,
|
||||
... show_edges=True,
|
||||
... color='grey',
|
||||
... lighting=False,
|
||||
... )
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
if self.GetBackfaceProperty() is None:
|
||||
self.SetBackfaceProperty(self.prop.copy())
|
||||
return self.GetBackfaceProperty() # type: ignore[return-value]
|
||||
|
||||
@backface_prop.setter
|
||||
def backface_prop(self, value: pyvista.Property) -> None:
|
||||
self.SetBackfaceProperty(value)
|
||||
@@ -0,0 +1,152 @@
|
||||
"""Module containing pyvista implementation of :vtk:`vtkProperty`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
from .opts import InterpolationType
|
||||
from .opts import RepresentationType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import _vtk
|
||||
|
||||
|
||||
class ActorProperties(_NoNewAttrMixin):
|
||||
"""Properties wrapper for :vtk:`vtkProperty`.
|
||||
|
||||
Contains the surface properties of the object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
properties : :vtk:`vtkProperty`
|
||||
VTK properties of the current object.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Access the properties of the z-axis shaft.
|
||||
|
||||
>>> import pyvista as pv
|
||||
|
||||
>>> axes = pv.Axes()
|
||||
>>> z_axes_prop = axes.axes_actor.z_axis_shaft_properties
|
||||
>>> z_axes_prop.color = (1.0, 1.0, 0.0)
|
||||
>>> z_axes_prop.opacity = 0.5
|
||||
>>> axes.axes_actor.shaft_type = axes.axes_actor.ShaftType.CYLINDER
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_actor(axes.axes_actor)
|
||||
>>> _ = pl.add_mesh(pv.Sphere())
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, properties: _vtk.vtkProperty) -> None:
|
||||
super().__init__()
|
||||
self.properties = properties
|
||||
|
||||
@property
|
||||
def color(self): # numpydoc ignore=RT01
|
||||
"""Return or set the color of the actor."""
|
||||
return self.properties.GetColor()
|
||||
|
||||
@color.setter
|
||||
def color(self, color: tuple[float, float, float]):
|
||||
self.properties.SetColor(color[0], color[1], color[2])
|
||||
|
||||
@property
|
||||
def metallic(self): # numpydoc ignore=RT01
|
||||
"""Return or set the metallic coefficient of the surface."""
|
||||
return self.properties.GetMetallic()
|
||||
|
||||
@metallic.setter
|
||||
def metallic(self, value: float):
|
||||
self.properties.SetMetallic(value)
|
||||
|
||||
@property
|
||||
def roughness(self): # numpydoc ignore=RT01
|
||||
"""Return or set the roughness of the surface."""
|
||||
return self.properties.GetRoughness()
|
||||
|
||||
@roughness.setter
|
||||
def roughness(self, value: float):
|
||||
self.properties.SetRoughness(value)
|
||||
|
||||
@property
|
||||
def anisotropy(self): # numpydoc ignore=RT01
|
||||
"""Return or set the anisotropy coefficient."""
|
||||
return self.properties.GetAnisotropy()
|
||||
|
||||
@anisotropy.setter
|
||||
def anisotropy(self, value: float):
|
||||
self.properties.SetAnisotropy(value)
|
||||
|
||||
@property
|
||||
def anisotropy_rotation(self): # numpydoc ignore=RT01
|
||||
"""Return or set the anisotropy rotation coefficient."""
|
||||
return self.properties.GetAnisotropyRotation()
|
||||
|
||||
@anisotropy_rotation.setter
|
||||
def anisotropy_rotation(self, value: float):
|
||||
self.properties.SetAnisotropyRotation(value)
|
||||
|
||||
@property
|
||||
def lighting(self): # numpydoc ignore=RT01
|
||||
"""Return or set the lighting activation flag."""
|
||||
return self.properties.GetLighting()
|
||||
|
||||
@lighting.setter
|
||||
def lighting(self, flag: bool):
|
||||
self.properties.SetLighting(flag)
|
||||
|
||||
@property
|
||||
def interpolation_model(self): # numpydoc ignore=RT01
|
||||
"""Return or set the interpolation model.
|
||||
|
||||
Can be any of the options in :class:`pyvista.plotting.opts.InterpolationType` enum.
|
||||
"""
|
||||
return InterpolationType.from_any(self.properties.GetInterpolation())
|
||||
|
||||
@interpolation_model.setter
|
||||
def interpolation_model(self, model: InterpolationType):
|
||||
self.properties.SetInterpolation(model.value)
|
||||
|
||||
@property
|
||||
def index_of_refraction(self): # numpydoc ignore=RT01
|
||||
"""Return or set the Index Of Refraction of the base layer."""
|
||||
return self.properties.GetBaseIOR()
|
||||
|
||||
@index_of_refraction.setter
|
||||
def index_of_refraction(self, value: float):
|
||||
self.properties.SetBaseIOR(value)
|
||||
|
||||
@property
|
||||
def opacity(self): # numpydoc ignore=RT01
|
||||
"""Return or set the opacity of the actor."""
|
||||
return self.properties.GetOpacity()
|
||||
|
||||
@opacity.setter
|
||||
def opacity(self, value: float):
|
||||
self.properties.SetOpacity(value)
|
||||
|
||||
@property
|
||||
def shading(self): # numpydoc ignore=RT01
|
||||
"""Return or set the flag to activate the shading."""
|
||||
return self.properties.GetShading()
|
||||
|
||||
@shading.setter
|
||||
def shading(self, is_active: bool):
|
||||
self.properties.SetShading(is_active)
|
||||
|
||||
@property
|
||||
def representation(self) -> RepresentationType: # numpydoc ignore=RT01
|
||||
"""Return or set the representation of the actor.
|
||||
|
||||
Can be any of the options in :class:`pyvista.plotting.opts.RepresentationType` enum.
|
||||
"""
|
||||
return RepresentationType.from_any(self.properties.GetRepresentation())
|
||||
|
||||
@representation.setter
|
||||
def representation(self, value: RepresentationType):
|
||||
self.properties.SetRepresentation(RepresentationType.from_any(value).value)
|
||||
@@ -0,0 +1,538 @@
|
||||
"""Affine widget module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core.errors import VTKVersionError
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
from pyvista.core.utilities.misc import try_callback
|
||||
|
||||
from . import _vtk
|
||||
|
||||
DARK_YELLOW = (0.9647058823529412, 0.7450980392156863, 0)
|
||||
GLOBAL_AXES = np.eye(3)
|
||||
|
||||
|
||||
def _validate_axes(axes):
|
||||
"""Validate and normalize input axes.
|
||||
|
||||
Axes are expected to follow the right-hand rule (e.g. third axis is the
|
||||
cross product of the first two.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes : sequence
|
||||
The axes to be validated and normalized. Should be of shape (3, 3).
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The validated and normalized axes.
|
||||
|
||||
"""
|
||||
axes = np.array(axes)
|
||||
if axes.shape != (3, 3):
|
||||
msg = '`axes` must be a (3, 3) array.'
|
||||
raise ValueError(msg)
|
||||
|
||||
axes = axes / np.linalg.norm(axes, axis=1, keepdims=True)
|
||||
if not np.allclose(np.cross(axes[0], axes[1]), axes[2]):
|
||||
msg = '`axes` do not follow the right hand rule.'
|
||||
raise ValueError(msg)
|
||||
|
||||
return axes
|
||||
|
||||
|
||||
def _check_callable(func, name='callback'):
|
||||
"""Check if a variable is callable."""
|
||||
if func and not callable(func):
|
||||
msg = f'`{name}` must be a callable, not {type(func)}.'
|
||||
raise TypeError(msg)
|
||||
return func
|
||||
|
||||
|
||||
def _make_quarter_arc():
|
||||
"""Make a quarter circle centered at the origin."""
|
||||
circ = pyvista.Circle(resolution=100)
|
||||
circ.faces = np.empty(0, dtype=int)
|
||||
circ.lines = np.hstack(([26], np.arange(0, 26)))
|
||||
return circ
|
||||
|
||||
|
||||
def get_angle(v1, v2):
|
||||
"""Compute the angle between two vectors in degrees.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
v1 : numpy.ndarray
|
||||
First input vector.
|
||||
v2 : numpy.ndarray
|
||||
Second input vector.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
Angle between vectors in degrees.
|
||||
|
||||
"""
|
||||
return np.rad2deg(np.arccos(np.clip(np.dot(v1, v2), -1.0, 1.0)))
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def ray_plane_intersection(start_point, direction, plane_point, normal): # noqa: PLR0917
|
||||
"""Compute the intersection between a ray and a plane.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start_point : ndarray
|
||||
Starting point of the ray.
|
||||
direction : ndarray
|
||||
Direction of the ray.
|
||||
plane_point : ndarray
|
||||
A point on the plane.
|
||||
normal : ndarray
|
||||
Normal to the plane.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ndarray
|
||||
Intersection point.
|
||||
|
||||
"""
|
||||
t_value = np.dot(normal, (plane_point - start_point)) / np.dot(normal, direction)
|
||||
return start_point + t_value * direction
|
||||
|
||||
|
||||
class AffineWidget3D(_NoNewAttrMixin):
|
||||
"""3D affine transform widget.
|
||||
|
||||
This widget allows interactive transformations including translation and
|
||||
rotation using the left mouse button.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
plotter : pyvista.Plotter
|
||||
The plotter object.
|
||||
actor : pyvista.Actor
|
||||
The actor to which the widget is attached to.
|
||||
origin : sequence[float], optional
|
||||
Origin of the widget. Default is the center of the main actor.
|
||||
start : bool, default: True
|
||||
If True, start the widget immediately.
|
||||
scale : float, default: 0.15
|
||||
Scale factor for the widget relative to the length of the actor.
|
||||
line_radius : float, default: 0.02
|
||||
Relative radius of the lines composing the widget.
|
||||
always_visible : bool, default: True
|
||||
Make the widget always visible. Setting this to ``False`` will cause
|
||||
the widget geometry to be hidden by other actors in the plotter.
|
||||
axes_colors : tuple[ColorLike], optional
|
||||
Uses the theme by default. Configure the individual axis colors by
|
||||
modifying either the theme with ``pyvista.global_theme.axes.x_color =
|
||||
<COLOR>`` or setting this with a ``tuple`` as in ``('r', 'g', 'b')``.
|
||||
axes : numpy.ndarray, optional
|
||||
``(3, 3)`` Numpy array defining the X, Y, and Z axes. By default this
|
||||
matches the default coordinate system.
|
||||
release_callback : callable, optional
|
||||
Call this method when releasing the left mouse button. It is passed the
|
||||
``user_matrix`` of the actor.
|
||||
interact_callback : callable, optional
|
||||
Call this method when moving the mouse with the left mouse button
|
||||
pressed down and a valid movement actor selected. It is passed the
|
||||
``user_matrix`` of the actor.
|
||||
|
||||
Notes
|
||||
-----
|
||||
After interacting with the actor, the transform will be stored within
|
||||
:attr:`pyvista.Prop3D.user_matrix` but will not be applied to the
|
||||
dataset. Use this matrix in conjunction with
|
||||
:func:`pyvista.DataObjectFilters.transform` to transform the dataset.
|
||||
|
||||
Requires VTK >= v9.2
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create the affine widget outside of the plotter and add it.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_mesh(pv.Sphere())
|
||||
>>> widget = pv.AffineWidget3D(pl, actor)
|
||||
>>> pl.show()
|
||||
|
||||
Access the transform from the actor.
|
||||
|
||||
>>> actor.user_matrix
|
||||
array([[1., 0., 0., 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 1.]])
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args(allowed=['plotter', 'actor'])
|
||||
def __init__( # noqa: PLR0917
|
||||
self,
|
||||
plotter,
|
||||
actor,
|
||||
origin=None,
|
||||
start: bool = True, # noqa: FBT001, FBT002
|
||||
scale=0.15,
|
||||
line_radius=0.02,
|
||||
always_visible: bool = True, # noqa: FBT001, FBT002
|
||||
axes_colors=None,
|
||||
axes=None,
|
||||
release_callback=None,
|
||||
interact_callback=None,
|
||||
):
|
||||
"""Initialize the widget."""
|
||||
# needs VTK v9.2.0 due to the hardware picker
|
||||
if pyvista.vtk_version_info < (9, 2):
|
||||
msg = 'AfflineWidget3D requires VTK v9.2.0 or newer.'
|
||||
raise VTKVersionError(msg)
|
||||
|
||||
self._axes = np.eye(4)
|
||||
self._axes_inv = np.eye(4)
|
||||
self._pl = plotter
|
||||
self._main_actor = actor
|
||||
self._selected_actor: pyvista.Actor | None = None
|
||||
self._init_position = None
|
||||
self._mouse_move_observer: int | None = None
|
||||
self._left_press_observer: int | None = None
|
||||
self._left_release_observer: int | None = None
|
||||
|
||||
if self._main_actor.user_matrix is None:
|
||||
self._main_actor.user_matrix = np.eye(4)
|
||||
self._cached_matrix = self._main_actor.user_matrix
|
||||
|
||||
self._arrows = [] # type: ignore[var-annotated]
|
||||
self._circles = [] # type: ignore[var-annotated]
|
||||
self._pressing_down = False
|
||||
origin = origin or actor.center
|
||||
self._origin = np.array(origin)
|
||||
if axes_colors is None:
|
||||
axes_colors = (
|
||||
pyvista.global_theme.axes.x_color,
|
||||
pyvista.global_theme.axes.y_color,
|
||||
pyvista.global_theme.axes.z_color,
|
||||
)
|
||||
self._axes_colors = axes_colors
|
||||
self._circ = _make_quarter_arc()
|
||||
self._actor_length = self._main_actor.GetLength()
|
||||
self._line_radius = line_radius
|
||||
self._user_interact_callback = _check_callable(interact_callback)
|
||||
self._user_release_callback = _check_callable(release_callback)
|
||||
|
||||
self._init_actors(scale, always_visible)
|
||||
|
||||
# axes must be set after initializing actors
|
||||
if axes is not None:
|
||||
try:
|
||||
_validate_axes(axes)
|
||||
except ValueError:
|
||||
for actor_ in self._arrows + self._circles:
|
||||
self._pl.remove_actor(actor_)
|
||||
raise
|
||||
self.axes = axes
|
||||
|
||||
if start:
|
||||
self.enable()
|
||||
|
||||
def _init_actors(self, scale, always_visible):
|
||||
"""Initialize the widget's actors."""
|
||||
for ii, color in enumerate(self._axes_colors):
|
||||
arrow = pyvista.Arrow(
|
||||
start=(0, 0, 0),
|
||||
direction=GLOBAL_AXES[ii],
|
||||
scale=self._actor_length * scale * 1.15,
|
||||
tip_radius=0.05,
|
||||
shaft_radius=self._line_radius,
|
||||
)
|
||||
self._arrows.append(
|
||||
self._pl.add_mesh(arrow, color=color, lighting=False, render=False)
|
||||
)
|
||||
axis_circ = self._circ.copy()
|
||||
if ii == 0:
|
||||
axis_circ = axis_circ.rotate_y(-90)
|
||||
elif ii == 1:
|
||||
axis_circ = axis_circ.rotate_x(90)
|
||||
axis_circ.points *= self._main_actor.GetLength() * (scale * 1.6)
|
||||
# axis_circ.points += self._origin
|
||||
axis_circ = axis_circ.tube(
|
||||
radius=self._line_radius * self._actor_length * scale,
|
||||
absolute=True,
|
||||
radius_factor=1.0,
|
||||
)
|
||||
|
||||
self._circles.append(
|
||||
self._pl.add_mesh(
|
||||
axis_circ,
|
||||
color=color,
|
||||
lighting=False,
|
||||
render_lines_as_tubes=True,
|
||||
render=False,
|
||||
),
|
||||
)
|
||||
|
||||
# update origin and assign a default user_matrix
|
||||
for actor in self._arrows + self._circles:
|
||||
matrix = np.eye(4)
|
||||
matrix[:3, -1] = self._origin
|
||||
actor.user_matrix = matrix
|
||||
|
||||
if always_visible:
|
||||
for actor in self._arrows + self._circles:
|
||||
actor.mapper.SetResolveCoincidentTopologyToPolygonOffset()
|
||||
actor.mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(0, -20000)
|
||||
|
||||
def _get_world_coord_rot(self, interactor):
|
||||
"""Get the world coordinates given an interactor.
|
||||
|
||||
Unlike ``_get_world_coord_trans``, these coordinates are physically
|
||||
accurate, but sensitive to the position of the camera. Rotation is zoom
|
||||
independent.
|
||||
|
||||
"""
|
||||
x, y = interactor.GetEventPosition()
|
||||
coordinate = _vtk.vtkCoordinate()
|
||||
coordinate.SetCoordinateSystemToDisplay()
|
||||
coordinate.SetValue(x, y, 0)
|
||||
ren = interactor.GetRenderWindow().GetRenderers().GetFirstRenderer()
|
||||
point = np.array(coordinate.GetComputedWorldValue(ren))
|
||||
if self._selected_actor:
|
||||
index = self._circles.index(self._selected_actor)
|
||||
to_widget = np.array(ren.camera.position - self._origin)
|
||||
point = ray_plane_intersection(
|
||||
start_point=point,
|
||||
direction=to_widget,
|
||||
plane_point=self._origin,
|
||||
normal=self.axes[index],
|
||||
)
|
||||
return point
|
||||
|
||||
def _get_world_coord_trans(self, interactor):
|
||||
"""Get the world coordinates given an interactor.
|
||||
|
||||
This uses a modified scaled approach to get the world coordinates that
|
||||
are not physically accurate, but ignores zoom and works for
|
||||
translation.
|
||||
|
||||
"""
|
||||
x, y = interactor.GetEventPosition()
|
||||
ren = interactor.GetRenderWindow().GetRenderers().GetFirstRenderer()
|
||||
|
||||
# Get normalized view coordinates (-1, 1)
|
||||
width, height = ren.GetSize()
|
||||
ndc_x = 2 * (x / width) - 1
|
||||
ndc_y = 2 * (y / height) - 1
|
||||
ndc_z = 1
|
||||
|
||||
# convert camera coordinates to world coordinates
|
||||
camera = ren.GetActiveCamera()
|
||||
projection_matrix = pyvista.array_from_vtkmatrix(
|
||||
camera.GetProjectionTransformMatrix(ren.GetTiledAspectRatio(), 0, 1),
|
||||
)
|
||||
inverse_projection_matrix = np.linalg.inv(projection_matrix)
|
||||
camera_coords = np.dot(inverse_projection_matrix, [ndc_x, ndc_y, ndc_z, 1])
|
||||
modelview_matrix = pyvista.array_from_vtkmatrix(camera.GetModelViewTransformMatrix())
|
||||
inverse_modelview_matrix = np.linalg.inv(modelview_matrix)
|
||||
world_coords = np.dot(inverse_modelview_matrix, camera_coords)
|
||||
|
||||
# Scale by twice actor length (experimentally determined for good UX)
|
||||
return world_coords[:3] * self._actor_length * 2
|
||||
|
||||
def _move_callback(self, interactor, _event):
|
||||
"""Process actions for the move mouse event."""
|
||||
click_x, click_y = interactor.GetEventPosition()
|
||||
click_z = 0
|
||||
picker = interactor.GetPicker()
|
||||
renderer = interactor.GetInteractorStyle()._parent()._plotter.iren.get_poked_renderer()
|
||||
picker.Pick(click_x, click_y, click_z, renderer)
|
||||
actor = picker.GetActor()
|
||||
|
||||
if self._pressing_down:
|
||||
if self._selected_actor in self._arrows:
|
||||
current_pos = self._get_world_coord_trans(interactor)
|
||||
index = self._arrows.index(self._selected_actor)
|
||||
diff = current_pos - self._init_position
|
||||
trans_matrix = np.eye(4)
|
||||
trans_matrix[:3, -1] = self.axes[index] * np.dot(diff, self.axes[index])
|
||||
matrix = trans_matrix @ self._cached_matrix
|
||||
elif self._selected_actor in self._circles:
|
||||
current_pos = self._get_world_coord_rot(interactor)
|
||||
index = self._circles.index(self._selected_actor)
|
||||
vec_current = current_pos - self._origin
|
||||
vec_init = self._init_position - self._origin
|
||||
normal = self.axes[index]
|
||||
vec_current = vec_current - np.dot(vec_current, normal) * normal
|
||||
vec_init = vec_init - np.dot(vec_init, normal) * normal
|
||||
vec_current /= np.linalg.norm(vec_current)
|
||||
vec_init /= np.linalg.norm(vec_init)
|
||||
angle = get_angle(vec_init, vec_current)
|
||||
cross = np.cross(vec_init, vec_current)
|
||||
if cross[index] < 0:
|
||||
angle = -angle
|
||||
|
||||
trans = _vtk.vtkTransform()
|
||||
trans.Translate(self._origin) # type: ignore[call-overload]
|
||||
trans.RotateWXYZ(
|
||||
angle,
|
||||
self._axes[index][0],
|
||||
self._axes[index][1],
|
||||
self._axes[index][2],
|
||||
)
|
||||
trans.Translate(-self._origin) # type: ignore[call-overload]
|
||||
trans.Update()
|
||||
rot_matrix = pyvista.array_from_vtkmatrix(trans.GetMatrix())
|
||||
matrix = rot_matrix @ self._cached_matrix
|
||||
|
||||
if self._user_interact_callback:
|
||||
try_callback(self._user_interact_callback, self._main_actor.user_matrix)
|
||||
|
||||
self._main_actor.user_matrix = matrix
|
||||
|
||||
elif self._selected_actor and self._selected_actor is not actor:
|
||||
# Return the color of the currently selected actor to normal and
|
||||
# deselect it
|
||||
if self._selected_actor in self._arrows:
|
||||
index = self._arrows.index(self._selected_actor)
|
||||
elif self._selected_actor in self._circles:
|
||||
index = self._circles.index(self._selected_actor)
|
||||
self._selected_actor.prop.color = self._axes_colors[index]
|
||||
self._selected_actor = None
|
||||
|
||||
# Highlight the actor if there is no selected actor
|
||||
if actor and not self._selected_actor:
|
||||
if actor in self._arrows:
|
||||
index = self._arrows.index(actor)
|
||||
self._arrows[index].prop.color = DARK_YELLOW
|
||||
actor.prop.color = DARK_YELLOW
|
||||
self._selected_actor = actor
|
||||
elif actor in self._circles:
|
||||
index = self._circles.index(actor)
|
||||
self._circles[index].prop.color = DARK_YELLOW
|
||||
actor.prop.color = DARK_YELLOW
|
||||
self._selected_actor = actor
|
||||
self._pl.render()
|
||||
|
||||
def _press_callback(self, interactor, _event):
|
||||
"""Process actions for the mouse button press event."""
|
||||
if self._selected_actor:
|
||||
self._pl.enable_trackball_actor_style()
|
||||
self._pressing_down = True
|
||||
if self._selected_actor in self._circles:
|
||||
self._init_position = self._get_world_coord_rot(interactor)
|
||||
else:
|
||||
self._init_position = self._get_world_coord_trans(interactor)
|
||||
|
||||
def _release_callback(self, _interactor, _event):
|
||||
"""Process actions for the mouse button release event."""
|
||||
self._pl.enable_trackball_style()
|
||||
self._pressing_down = False
|
||||
self._cached_matrix = self._main_actor.user_matrix
|
||||
if self._user_release_callback:
|
||||
try_callback(self._user_release_callback, self._main_actor.user_matrix)
|
||||
|
||||
def _reset(self):
|
||||
"""Reset the actor and cached transform."""
|
||||
self._main_actor.user_matrix = np.eye(4)
|
||||
self._cached_matrix = np.eye(4)
|
||||
|
||||
@property
|
||||
def axes(self):
|
||||
"""Return or set the axes of the widget.
|
||||
|
||||
The axes will be checked for orthogonality. Non-orthogonal axes will
|
||||
raise a ``ValueError``
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
``(3, 3)`` array of axes.
|
||||
|
||||
"""
|
||||
return self._axes[:3, :3]
|
||||
|
||||
@axes.setter
|
||||
def axes(self, axes):
|
||||
mat = np.eye(4)
|
||||
mat[:3, :3] = _validate_axes(axes)
|
||||
mat[:3, -1] = self.origin
|
||||
self._axes = mat
|
||||
self._axes_inv = np.linalg.inv(self._axes) # type: ignore[assignment]
|
||||
for actor in self._arrows + self._circles:
|
||||
matrix = actor.user_matrix
|
||||
# Be sure to use the inverse here
|
||||
matrix[:3, :3] = self._axes_inv[:3, :3]
|
||||
actor.user_matrix = matrix
|
||||
|
||||
@property
|
||||
def origin(self) -> tuple[float, float, float]:
|
||||
"""Origin of the widget.
|
||||
|
||||
This is where the origin of the widget will be located and where the
|
||||
actor will be rotated about.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
Widget origin.
|
||||
|
||||
"""
|
||||
return cast('tuple[float, float, float]', tuple(self._origin))
|
||||
|
||||
@origin.setter
|
||||
def origin(self, value):
|
||||
value = np.array(value)
|
||||
diff = value - self._origin
|
||||
|
||||
for actor in self._circles + self._arrows:
|
||||
if actor.user_matrix is None:
|
||||
actor.user_matrix = np.eye(4)
|
||||
matrix = actor.user_matrix
|
||||
matrix[:3, -1] += diff
|
||||
actor.user_matrix = matrix
|
||||
|
||||
self._origin = value
|
||||
|
||||
def enable(self):
|
||||
"""Enable the widget."""
|
||||
if not self._pl._picker_in_use:
|
||||
self._pl.enable_mesh_picking(show_message=False, show=False, picker='hardware')
|
||||
self._mouse_move_observer = self._pl.iren.add_observer(
|
||||
'MouseMoveEvent',
|
||||
self._move_callback,
|
||||
)
|
||||
self._left_press_observer = self._pl.iren.add_observer(
|
||||
'LeftButtonPressEvent',
|
||||
self._press_callback,
|
||||
interactor_style_fallback=False,
|
||||
)
|
||||
self._left_release_observer = self._pl.iren.add_observer(
|
||||
'LeftButtonReleaseEvent',
|
||||
self._release_callback,
|
||||
interactor_style_fallback=False,
|
||||
)
|
||||
|
||||
def disable(self):
|
||||
"""Disable the widget."""
|
||||
self._pl.disable_picking()
|
||||
if self._mouse_move_observer:
|
||||
self._pl.iren.remove_observer(self._mouse_move_observer)
|
||||
if self._left_press_observer:
|
||||
self._pl.iren.remove_observer(self._left_press_observer)
|
||||
if self._left_release_observer:
|
||||
self._pl.iren.remove_observer(self._left_release_observer)
|
||||
|
||||
def remove(self):
|
||||
"""Disable and delete all actors of this widget."""
|
||||
self.disable()
|
||||
for actor in self._circles + self._arrows:
|
||||
self._pl.remove_actor(actor)
|
||||
self._circles = []
|
||||
self._arrows = []
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Module containing pyvista implementation of :vtk:`vtkAxes`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
from . import _vtk
|
||||
from .actor import Actor
|
||||
from .axes_actor import AxesActor
|
||||
|
||||
|
||||
class Axes(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkAxes):
|
||||
"""PyVista wrapper for the VTK Axes class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
show_actor : bool, optional
|
||||
Hide or show the actor of these axes. Default ``False``.
|
||||
|
||||
actor_scale : float, optional
|
||||
Scale the size of the axes actor. Default ``1``.
|
||||
|
||||
line_width : float, optional
|
||||
Width of the axes lines. Default ``1``.
|
||||
|
||||
symmetric : bool, optional
|
||||
If true, the axis continue to negative values.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an instance of axes at the pyvista module level.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def __init__( # noqa: PLR0917
|
||||
self,
|
||||
show_actor: bool = False, # noqa: FBT001, FBT002
|
||||
actor_scale=1,
|
||||
line_width=1.0,
|
||||
symmetric: bool = False, # noqa: FBT001, FBT002
|
||||
): # numpydoc ignore=PR01,RT01
|
||||
"""Initialize a new axes descriptor."""
|
||||
super().__init__()
|
||||
self.SetSymmetric(symmetric)
|
||||
# Add the axes mapper
|
||||
self.mapper = _vtk.vtkPolyDataMapper()
|
||||
self.mapper.SetInputConnection(self.GetOutputPort())
|
||||
# Add the axes actor
|
||||
self.actor = Actor(mapper=self.mapper)
|
||||
self.axes_actor = AxesActor()
|
||||
self.actor.visibility = show_actor
|
||||
self.actor.scale = actor_scale
|
||||
self.actor.prop.line_width = line_width
|
||||
|
||||
@property
|
||||
def origin(self): # numpydoc ignore=RT01
|
||||
"""Return or set th origin of the axes in world coordinates.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.origin
|
||||
(0.0, 0.0, 0.0)
|
||||
|
||||
Set the origin of the camera.
|
||||
|
||||
>>> axes.origin = (2.0, 1.0, 1.0)
|
||||
>>> axes.origin
|
||||
(2.0, 1.0, 1.0)
|
||||
|
||||
"""
|
||||
return self.GetOrigin()
|
||||
|
||||
@origin.setter
|
||||
def origin(self, value):
|
||||
self.SetOrigin(value)
|
||||
|
||||
def show_actor(self):
|
||||
"""Show an actor of axes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.show_actor()
|
||||
|
||||
"""
|
||||
self.actor.visibility = True
|
||||
|
||||
def hide_actor(self):
|
||||
"""Hide an actor of axes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.hide_actor()
|
||||
|
||||
"""
|
||||
self.actor.visibility = False
|
||||
|
||||
def show_symmetric(self):
|
||||
"""Show symmetric of axes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.show_symmetric()
|
||||
|
||||
"""
|
||||
self.SymmetricOn()
|
||||
|
||||
def hide_symmetric(self):
|
||||
"""Hide symmetric of axes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.hide_symmetric()
|
||||
|
||||
"""
|
||||
self.SymmetricOff()
|
||||
|
||||
def __del__(self):
|
||||
"""Clean the attributes of the class."""
|
||||
self.axes_actor = None # type: ignore[assignment]
|
||||
self.actor = None # type: ignore[assignment]
|
||||
self.mapper = None # type: ignore[assignment]
|
||||
@@ -0,0 +1,665 @@
|
||||
"""Axes actor module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from enum import Enum
|
||||
import warnings
|
||||
|
||||
import pyvista
|
||||
from pyvista.core._typing_core import BoundsTuple
|
||||
from pyvista.core.errors import PyVistaDeprecationWarning
|
||||
from pyvista.core.utilities.misc import _BoundsSizeMixin
|
||||
from pyvista.core.utilities.misc import _NameMixin
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
from . import _vtk
|
||||
from .actor_properties import ActorProperties
|
||||
|
||||
|
||||
class AxesActor(
|
||||
_NoNewAttrMixin, _NameMixin, _BoundsSizeMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkAxesActor
|
||||
):
|
||||
"""Axes actor wrapper for :vtk:`vtkAxesActor`.
|
||||
|
||||
Hybrid 2D/3D actor used to represent 3D axes in a scene. The user
|
||||
can define the geometry to use for the shaft or the tip, and the
|
||||
user can set the text for the three axes. To see full customization
|
||||
options, refer to :vtk:`vtkAxesActor`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:class:`~pyvista.AxesAssembly`
|
||||
|
||||
:ref:`axes_objects_example`
|
||||
Example showing different axes objects.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Customize the axis shaft color and shape.
|
||||
|
||||
>>> import pyvista as pv
|
||||
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.z_axis_shaft_properties.color = (0.0, 1.0, 1.0)
|
||||
>>> axes.axes_actor.shaft_type = axes.axes_actor.ShaftType.CYLINDER
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_actor(axes.axes_actor)
|
||||
>>> _ = pl.add_mesh(pv.Sphere())
|
||||
>>> pl.show()
|
||||
|
||||
Or you can use this as a custom orientation widget with
|
||||
:func:`add_orientation_widget() <pyvista.Renderer.add_orientation_widget>`:
|
||||
|
||||
>>> import pyvista as pv
|
||||
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes_actor = axes.axes_actor
|
||||
>>> axes.axes_actor.shaft_type = 0
|
||||
|
||||
>>> axes_actor.x_axis_shaft_properties.color = (1.0, 1.0, 1.0)
|
||||
>>> axes_actor.y_axis_shaft_properties.color = (1.0, 1.0, 1.0)
|
||||
>>> axes_actor.z_axis_shaft_properties.color = (1.0, 1.0, 1.0)
|
||||
|
||||
>>> axes_actor.x_label = 'U'
|
||||
>>> axes_actor.y_label = 'V'
|
||||
>>> axes_actor.z_label = 'W'
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(pv.Cone())
|
||||
>>> _ = pl.add_orientation_widget(
|
||||
... axes_actor,
|
||||
... viewport=(0, 0, 0.5, 0.5),
|
||||
... )
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
|
||||
class ShaftType(Enum):
|
||||
"""Types of shaft shapes available."""
|
||||
|
||||
CYLINDER = 0
|
||||
LINE = 1
|
||||
|
||||
class TipType(Enum):
|
||||
"""Types of tip shapes available."""
|
||||
|
||||
CONE = 0
|
||||
SPHERE = 1
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize actor."""
|
||||
super().__init__()
|
||||
|
||||
self.x_axis_shaft_properties.color = pyvista.global_theme.axes.x_color.float_rgb
|
||||
self.x_axis_tip_properties.color = pyvista.global_theme.axes.x_color.float_rgb
|
||||
self.x_axis_shaft_properties.opacity = pyvista.global_theme.axes.x_color.float_rgba[3]
|
||||
self.x_axis_tip_properties.opacity = pyvista.global_theme.axes.x_color.float_rgba[3]
|
||||
self.x_axis_shaft_properties.lighting = pyvista.global_theme.lighting
|
||||
|
||||
self.y_axis_shaft_properties.color = pyvista.global_theme.axes.y_color.float_rgb
|
||||
self.y_axis_tip_properties.color = pyvista.global_theme.axes.y_color.float_rgb
|
||||
self.y_axis_shaft_properties.opacity = pyvista.global_theme.axes.y_color.float_rgba[3]
|
||||
self.y_axis_tip_properties.opacity = pyvista.global_theme.axes.y_color.float_rgba[3]
|
||||
self.y_axis_shaft_properties.lighting = pyvista.global_theme.lighting
|
||||
|
||||
self.z_axis_shaft_properties.color = pyvista.global_theme.axes.z_color.float_rgb
|
||||
self.z_axis_tip_properties.color = pyvista.global_theme.axes.z_color.float_rgb
|
||||
self.z_axis_shaft_properties.opacity = pyvista.global_theme.axes.z_color.float_rgba[3]
|
||||
self.z_axis_tip_properties.opacity = pyvista.global_theme.axes.z_color.float_rgba[3]
|
||||
self.z_axis_shaft_properties.lighting = pyvista.global_theme.lighting
|
||||
|
||||
@property
|
||||
def bounds(self) -> BoundsTuple:
|
||||
"""Return the bounding box of this.
|
||||
|
||||
Returns
|
||||
-------
|
||||
BoundsTuple
|
||||
Bounding box.
|
||||
The form is: ``(x_min, x_max, y_min, y_max, z_min, z_max)``.
|
||||
|
||||
"""
|
||||
return BoundsTuple(*self.GetBounds())
|
||||
|
||||
@property
|
||||
def center(self) -> tuple[float, float, float]:
|
||||
"""Return the center.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[float, float, float]
|
||||
Center of axes actor.
|
||||
|
||||
"""
|
||||
return self.GetCenter()
|
||||
|
||||
@property
|
||||
def visibility(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return or set AxesActor visibility.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create an Axes object and then access the
|
||||
visibility attribute of its AxesActor.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.visibility
|
||||
True
|
||||
|
||||
"""
|
||||
return bool(self.GetVisibility())
|
||||
|
||||
@visibility.setter
|
||||
def visibility(self, value: bool):
|
||||
self.SetVisibility(value)
|
||||
|
||||
@property
|
||||
def total_length(self) -> tuple[float, float, float]: # numpydoc ignore=RT01
|
||||
"""Return or set the length of all axes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.total_length
|
||||
(1.0, 1.0, 1.0)
|
||||
>>> axes.axes_actor.total_length = 1.2
|
||||
>>> axes.axes_actor.total_length
|
||||
(1.2, 1.2, 1.2)
|
||||
>>> axes.axes_actor.total_length = (1.0, 0.9, 0.5)
|
||||
>>> axes.axes_actor.total_length
|
||||
(1.0, 0.9, 0.5)
|
||||
|
||||
"""
|
||||
return self.GetTotalLength()
|
||||
|
||||
@total_length.setter
|
||||
def total_length(self, length):
|
||||
if isinstance(length, Iterable):
|
||||
self.SetTotalLength(length[0], length[1], length[2]) # type: ignore[index]
|
||||
else:
|
||||
self.SetTotalLength(length, length, length)
|
||||
|
||||
@property
|
||||
def shaft_length(self) -> tuple[float, float, float]: # numpydoc ignore=RT01
|
||||
"""Return or set the length of the axes shaft.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.shaft_length
|
||||
(0.8, 0.8, 0.8)
|
||||
>>> axes.axes_actor.shaft_length = 0.7
|
||||
>>> axes.axes_actor.shaft_length
|
||||
(0.7, 0.7, 0.7)
|
||||
>>> axes.axes_actor.shaft_length = (1.0, 0.9, 0.5)
|
||||
>>> axes.axes_actor.shaft_length
|
||||
(1.0, 0.9, 0.5)
|
||||
|
||||
"""
|
||||
return self.GetNormalizedShaftLength()
|
||||
|
||||
@shaft_length.setter
|
||||
def shaft_length(self, length):
|
||||
if isinstance(length, Iterable):
|
||||
self.SetNormalizedShaftLength(length[0], length[1], length[2]) # type: ignore[index]
|
||||
else:
|
||||
self.SetNormalizedShaftLength(length, length, length)
|
||||
|
||||
@property
|
||||
def tip_length(self) -> tuple[float, float, float]: # numpydoc ignore=RT01
|
||||
"""Return or set the length of the tip.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.tip_length
|
||||
(0.2, 0.2, 0.2)
|
||||
>>> axes.axes_actor.tip_length = 0.3
|
||||
>>> axes.axes_actor.tip_length
|
||||
(0.3, 0.3, 0.3)
|
||||
>>> axes.axes_actor.tip_length = (0.1, 0.4, 0.2)
|
||||
>>> axes.axes_actor.tip_length
|
||||
(0.1, 0.4, 0.2)
|
||||
|
||||
"""
|
||||
return self.GetNormalizedTipLength()
|
||||
|
||||
@tip_length.setter
|
||||
def tip_length(self, length):
|
||||
if isinstance(length, Iterable):
|
||||
self.SetNormalizedTipLength(length[0], length[1], length[2]) # type: ignore[index]
|
||||
else:
|
||||
self.SetNormalizedTipLength(length, length, length)
|
||||
|
||||
@property
|
||||
def label_position(self) -> tuple[float, float, float]: # numpydoc ignore=RT01
|
||||
"""Position of the label along the axes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.label_position
|
||||
(1.0, 1.0, 1.0)
|
||||
>>> axes.axes_actor.label_position = 0.3
|
||||
>>> axes.axes_actor.label_position
|
||||
(0.3, 0.3, 0.3)
|
||||
>>> axes.axes_actor.label_position = (0.1, 0.4, 0.2)
|
||||
>>> axes.axes_actor.label_position
|
||||
(0.1, 0.4, 0.2)
|
||||
|
||||
"""
|
||||
return self.GetNormalizedLabelPosition()
|
||||
|
||||
@label_position.setter
|
||||
def label_position(self, length):
|
||||
if isinstance(length, Iterable):
|
||||
self.SetNormalizedLabelPosition(length[0], length[1], length[2]) # type: ignore[index]
|
||||
else:
|
||||
self.SetNormalizedLabelPosition(length, length, length)
|
||||
|
||||
@property
|
||||
def cone_resolution(self) -> int: # numpydoc ignore=RT01
|
||||
"""Return or set the resolution of the cone tip.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.cone_resolution
|
||||
16
|
||||
>>> axes.axes_actor.cone_resolution = 24
|
||||
>>> axes.axes_actor.cone_resolution
|
||||
24
|
||||
|
||||
"""
|
||||
return self.GetConeResolution()
|
||||
|
||||
@cone_resolution.setter
|
||||
def cone_resolution(self, res: int):
|
||||
self.SetConeResolution(res)
|
||||
|
||||
@property
|
||||
def sphere_resolution(self) -> int: # numpydoc ignore=RT01
|
||||
"""Return or set the resolution of the spherical tip.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.sphere_resolution
|
||||
16
|
||||
>>> axes.axes_actor.sphere_resolution = 24
|
||||
>>> axes.axes_actor.sphere_resolution
|
||||
24
|
||||
|
||||
"""
|
||||
return self.GetSphereResolution()
|
||||
|
||||
@sphere_resolution.setter
|
||||
def sphere_resolution(self, res: int):
|
||||
self.SetSphereResolution(res)
|
||||
|
||||
@property
|
||||
def cylinder_resolution(self) -> int: # numpydoc ignore=RT01
|
||||
"""Return or set the resolution of the shaft cylinder.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.cylinder_resolution
|
||||
16
|
||||
>>> axes.axes_actor.cylinder_resolution = 24
|
||||
>>> axes.axes_actor.cylinder_resolution
|
||||
24
|
||||
|
||||
"""
|
||||
return self.GetCylinderResolution()
|
||||
|
||||
@cylinder_resolution.setter
|
||||
def cylinder_resolution(self, res: int):
|
||||
self.SetCylinderResolution(res)
|
||||
|
||||
@property
|
||||
def cone_radius(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set the radius of the cone tip.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.cone_radius
|
||||
0.4
|
||||
>>> axes.axes_actor.cone_radius = 0.8
|
||||
>>> axes.axes_actor.cone_radius
|
||||
0.8
|
||||
|
||||
"""
|
||||
return self.GetConeRadius()
|
||||
|
||||
@cone_radius.setter
|
||||
def cone_radius(self, rad: float):
|
||||
self.SetConeRadius(rad)
|
||||
|
||||
@property
|
||||
def sphere_radius(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set the radius of the spherical tip.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.sphere_radius
|
||||
0.4
|
||||
>>> axes.axes_actor.sphere_radius = 0.8
|
||||
>>> axes.axes_actor.sphere_radius
|
||||
0.8
|
||||
|
||||
"""
|
||||
return self.GetSphereRadius()
|
||||
|
||||
@sphere_radius.setter
|
||||
def sphere_radius(self, rad: float):
|
||||
self.SetSphereRadius(rad)
|
||||
|
||||
@property
|
||||
def cylinder_radius(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set the radius of the shaft cylinder.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.cylinder_radius
|
||||
0.05
|
||||
>>> axes.axes_actor.cylinder_radius = 0.03
|
||||
>>> axes.axes_actor.cylinder_radius
|
||||
0.03
|
||||
|
||||
"""
|
||||
return self.GetCylinderRadius()
|
||||
|
||||
@cylinder_radius.setter
|
||||
def cylinder_radius(self, rad: float):
|
||||
self.SetCylinderRadius(rad)
|
||||
|
||||
@property
|
||||
def shaft_type(self) -> ShaftType: # numpydoc ignore=RT01
|
||||
"""Return or set the shaft type.
|
||||
|
||||
Can be either a cylinder(0) or a line(1).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.shaft_type = axes.axes_actor.ShaftType.LINE
|
||||
>>> axes.axes_actor.shaft_type
|
||||
<ShaftType.LINE: 1>
|
||||
|
||||
"""
|
||||
return AxesActor.ShaftType(self.GetShaftType())
|
||||
|
||||
@shaft_type.setter
|
||||
def shaft_type(self, shaft_type: ShaftType | int):
|
||||
shaft_type = AxesActor.ShaftType(shaft_type)
|
||||
if shaft_type == AxesActor.ShaftType.CYLINDER:
|
||||
self.SetShaftTypeToCylinder()
|
||||
elif shaft_type == AxesActor.ShaftType.LINE:
|
||||
self.SetShaftTypeToLine()
|
||||
|
||||
@property
|
||||
def tip_type(self) -> TipType: # numpydoc ignore=RT01
|
||||
"""Return or set the shaft type.
|
||||
|
||||
Can be either a cone(0) or a sphere(1).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.tip_type = axes.axes_actor.TipType.SPHERE
|
||||
>>> axes.axes_actor.tip_type
|
||||
<TipType.SPHERE: 1>
|
||||
|
||||
"""
|
||||
return AxesActor.TipType(self.GetTipType())
|
||||
|
||||
@tip_type.setter
|
||||
def tip_type(self, tip_type: TipType | int):
|
||||
tip_type = AxesActor.TipType(tip_type)
|
||||
if tip_type == AxesActor.TipType.CONE:
|
||||
self.SetTipTypeToCone()
|
||||
elif tip_type == AxesActor.TipType.SPHERE:
|
||||
self.SetTipTypeToSphere()
|
||||
|
||||
@property
|
||||
def labels(self) -> tuple[str, str, str]: # numpydoc ignore=RT01
|
||||
"""Return or set the axes labels.
|
||||
|
||||
This property may be used as an alternative to using :attr:`~x_axis_label`,
|
||||
:attr:`~y_axis_label`, and :attr:`~z_axis_label` separately.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes_actor = pv.AxesActor()
|
||||
>>> axes_actor.labels = ['X Axis', 'Y Axis', 'Z Axis']
|
||||
>>> axes_actor.labels
|
||||
('X Axis', 'Y Axis', 'Z Axis')
|
||||
|
||||
"""
|
||||
return self.x_label, self.y_label, self.z_label
|
||||
|
||||
@labels.setter
|
||||
def labels(self, labels: list[str] | tuple[str]):
|
||||
if not isinstance(labels, (list, tuple)):
|
||||
msg = f'Labels must be a list or tuple. Got {labels} instead.' # type: ignore[unreachable]
|
||||
raise TypeError(msg)
|
||||
|
||||
if len(labels) != 3:
|
||||
msg = f'Labels must be a list or tuple with three items. Got {labels} instead.'
|
||||
raise ValueError(msg)
|
||||
self.x_label = labels[0]
|
||||
self.y_label = labels[1]
|
||||
self.z_label = labels[2]
|
||||
|
||||
@property
|
||||
def x_axis_label(self) -> str: # numpydoc ignore=RT01
|
||||
"""Return or set the label for the x-axis.
|
||||
|
||||
.. deprecated:: 0.44.0
|
||||
|
||||
This parameter is deprecated. Use :attr:`x_label` instead.
|
||||
|
||||
"""
|
||||
# deprecated 0.44.0, convert to error in 0.46.0, remove 0.47.0
|
||||
warnings.warn(
|
||||
'Use of `x_axis_label` is deprecated. Use `x_label` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
if pyvista._version.version_info >= (0, 47): # pragma: no cover
|
||||
msg = 'Remove this deprecated property'
|
||||
raise RuntimeError(msg)
|
||||
return self.GetXAxisLabelText() # pragma: no cover
|
||||
|
||||
@x_axis_label.setter
|
||||
def x_axis_label(self, label: str):
|
||||
# deprecated 0.44.0, convert to error in 0.46.0, remove 0.47.0
|
||||
warnings.warn(
|
||||
'Use of `x_axis_label` is deprecated. Use `x_label` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
if pyvista._version.version_info >= (0, 47): # pragma: no cover
|
||||
msg = 'Remove this deprecated property'
|
||||
raise RuntimeError(msg)
|
||||
self.SetXAxisLabelText(label) # pragma: no cover
|
||||
|
||||
@property
|
||||
def x_label(self) -> str: # numpydoc ignore=RT01
|
||||
"""Return or set the label for the x-axis.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.x_label = 'This axis'
|
||||
>>> axes.axes_actor.x_label
|
||||
'This axis'
|
||||
|
||||
"""
|
||||
return self.GetXAxisLabelText()
|
||||
|
||||
@x_label.setter
|
||||
def x_label(self, label: str):
|
||||
self.SetXAxisLabelText(label)
|
||||
|
||||
@property
|
||||
def y_axis_label(self) -> str: # numpydoc ignore=RT01
|
||||
"""Return or set the label for the y-axis.
|
||||
|
||||
.. deprecated:: 0.44.0
|
||||
|
||||
This parameter is deprecated. Use :attr:`y_label` instead.
|
||||
|
||||
"""
|
||||
# deprecated 0.44.0, convert to error in 0.46.0, remove 0.47.0
|
||||
warnings.warn(
|
||||
'Use of `y_axis_label` is deprecated. Use `y_label` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
if pyvista._version.version_info >= (0, 47): # pragma: no cover
|
||||
msg = 'Remove this deprecated property'
|
||||
raise RuntimeError(msg)
|
||||
return self.GetYAxisLabelText() # pragma: no cover
|
||||
|
||||
@y_axis_label.setter
|
||||
def y_axis_label(self, label: str):
|
||||
# deprecated 0.44.0, convert to error in 0.46.0, remove 0.47.0
|
||||
warnings.warn(
|
||||
'Use of `y_axis_label` is deprecated. Use `y_label` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
if pyvista._version.version_info >= (0, 47): # pragma: no cover
|
||||
msg = 'Remove this deprecated property'
|
||||
raise RuntimeError(msg)
|
||||
self.SetYAxisLabelText(label) # pragma: no cover
|
||||
|
||||
@property
|
||||
def y_label(self) -> str: # numpydoc ignore=RT01
|
||||
"""Return or set the label for the y-axis.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.y_label = 'This axis'
|
||||
>>> axes.axes_actor.y_label
|
||||
'This axis'
|
||||
|
||||
"""
|
||||
return self.GetYAxisLabelText()
|
||||
|
||||
@y_label.setter
|
||||
def y_label(self, label: str):
|
||||
self.SetYAxisLabelText(label)
|
||||
|
||||
@property
|
||||
def z_axis_label(self) -> str: # numpydoc ignore=RT01
|
||||
"""Return or set the label for the z-axis.
|
||||
|
||||
.. deprecated:: 0.44.0
|
||||
|
||||
This parameter is deprecated. Use :attr:`z_label` instead.
|
||||
|
||||
"""
|
||||
# deprecated 0.44.0, convert to error in 0.46.0, remove 0.47.0
|
||||
warnings.warn(
|
||||
'Use of `z_axis_label` is deprecated. Use `z_label` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
if pyvista._version.version_info >= (0, 47): # pragma: no cover
|
||||
msg = 'Remove this deprecated property'
|
||||
raise RuntimeError(msg)
|
||||
return self.GetZAxisLabelText() # pragma: no cover
|
||||
|
||||
@z_axis_label.setter
|
||||
def z_axis_label(self, label: str):
|
||||
# deprecated 0.44.0, convert to error in 0.46.0, remove 0.47.0
|
||||
warnings.warn(
|
||||
'Use of `z_axis_label` is deprecated. Use `z_label` instead.',
|
||||
PyVistaDeprecationWarning,
|
||||
)
|
||||
if pyvista._version.version_info >= (0, 47): # pragma: no cover
|
||||
msg = 'Remove this deprecated property'
|
||||
raise RuntimeError(msg)
|
||||
self.SetZAxisLabelText(label) # pragma: no cover
|
||||
|
||||
@property
|
||||
def z_label(self) -> str: # numpydoc ignore=RT01
|
||||
"""Return or set the label for the z-axis.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> axes = pv.Axes()
|
||||
>>> axes.axes_actor.z_label = 'This axis'
|
||||
>>> axes.axes_actor.z_label
|
||||
'This axis'
|
||||
|
||||
"""
|
||||
return self.GetZAxisLabelText()
|
||||
|
||||
@z_label.setter
|
||||
def z_label(self, label: str):
|
||||
self.SetZAxisLabelText(label)
|
||||
|
||||
@property
|
||||
def x_axis_shaft_properties(self): # numpydoc ignore=RT01
|
||||
"""Return or set the properties of the x-axis shaft."""
|
||||
return ActorProperties(self.GetXAxisShaftProperty())
|
||||
|
||||
@property
|
||||
def y_axis_shaft_properties(self): # numpydoc ignore=RT01
|
||||
"""Return or set the properties of the y-axis shaft."""
|
||||
return ActorProperties(self.GetYAxisShaftProperty())
|
||||
|
||||
@property
|
||||
def z_axis_shaft_properties(self): # numpydoc ignore=RT01
|
||||
"""Return or set the properties of the z-axis shaft."""
|
||||
return ActorProperties(self.GetZAxisShaftProperty())
|
||||
|
||||
@property
|
||||
def x_axis_tip_properties(self): # numpydoc ignore=RT01
|
||||
"""Return or set the properties of the x-axis tip."""
|
||||
return ActorProperties(self.GetXAxisTipProperty())
|
||||
|
||||
@x_axis_tip_properties.setter
|
||||
def x_axis_tip_properties(self, properties: ActorProperties):
|
||||
self.x_axis_tip_properties = properties
|
||||
|
||||
@property
|
||||
def y_axis_tip_properties(self): # numpydoc ignore=RT01
|
||||
"""Return or set the properties of the y-axis tip."""
|
||||
return ActorProperties(self.GetYAxisTipProperty())
|
||||
|
||||
@y_axis_tip_properties.setter
|
||||
def y_axis_tip_properties(self, properties: ActorProperties):
|
||||
self.y_axis_tip_properties = properties
|
||||
|
||||
@property
|
||||
def z_axis_tip_properties(self): # numpydoc ignore=RT01
|
||||
"""Return or set the properties of the z-axis tip."""
|
||||
return ActorProperties(self.GetZAxisTipProperty())
|
||||
|
||||
@z_axis_tip_properties.setter
|
||||
def z_axis_tip_properties(self, properties: ActorProperties):
|
||||
self.z_axis_tip_properties = properties
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
||||
"""Contains the BackgroundRenderer class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
from .renderer import Renderer
|
||||
|
||||
|
||||
class BackgroundRenderer(Renderer):
|
||||
"""BackgroundRenderer for visualizing a background image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parent : Renderer
|
||||
The parent renderer for the background renderer.
|
||||
image_path : str
|
||||
Path to the image to use as a background.
|
||||
scale : float, default: 1
|
||||
Scaling factor for the background image.
|
||||
view_port : tuple[float], optional
|
||||
Viewport for the background renderer.
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args(allowed=['parent', 'image_path'])
|
||||
def __init__( # noqa: PLR0917
|
||||
self, parent, image_path, scale=1, view_port=None
|
||||
):
|
||||
"""Initialize BackgroundRenderer with an image."""
|
||||
# avoiding circular import
|
||||
from . import _vtk # noqa: PLC0415
|
||||
|
||||
# read the image first as we don't need to create a render if
|
||||
# the image path is invalid
|
||||
image_data = pyvista.read(image_path)
|
||||
|
||||
super().__init__(parent, border=False)
|
||||
self.SetLayer(0)
|
||||
self.InteractiveOff()
|
||||
self.SetBackground(self.parent.renderer.GetBackground())
|
||||
self._scale = scale
|
||||
self._modified_observer = None
|
||||
self._prior_window_size = None
|
||||
if view_port is not None:
|
||||
self.viewport = view_port
|
||||
|
||||
# create image actor
|
||||
image_actor = _vtk.vtkImageActor()
|
||||
image_actor.SetInputData(image_data)
|
||||
self.add_actor(image_actor, name='background')
|
||||
self.camera.enable_parallel_projection()
|
||||
self.reset_camera() # necessary to get first render
|
||||
self.resize()
|
||||
|
||||
def resize(self, *args): # noqa: ARG002
|
||||
"""Resize a background renderer.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*args : tuple
|
||||
Ignored arguments.
|
||||
|
||||
"""
|
||||
if self.parent is None: # when deleted
|
||||
return
|
||||
if self.parent.render_window is None: # BasePlotter
|
||||
return
|
||||
|
||||
if self._prior_window_size != self.parent.window_size:
|
||||
self._prior_window_size = self.parent.window_size
|
||||
|
||||
actor = self._actors['background']
|
||||
image_data = actor.GetInput()
|
||||
origin = image_data.GetOrigin()
|
||||
extent = image_data.GetExtent()
|
||||
spacing = image_data.GetSpacing()
|
||||
xc = origin[0] + 0.5 * (extent[0] + extent[1]) * spacing[0]
|
||||
yc = origin[1] + 0.5 * (extent[2] + extent[3]) * spacing[1]
|
||||
yd = (extent[3] - extent[2] + 1) * spacing[1]
|
||||
dist = self.camera.distance
|
||||
|
||||
# make the longest dimensions match the plotting window
|
||||
img_dim = np.array(image_data.dimensions[:2])
|
||||
self.camera._focus = np.array([xc, yc, 0.0])
|
||||
self.camera.position = np.array([xc, yc, dist])
|
||||
|
||||
ratio = img_dim / np.array(self.parent.window_size)
|
||||
scale_value = 1
|
||||
if ratio.max() > 1:
|
||||
# images are not scaled if larger than the window
|
||||
scale_value = ratio.max()
|
||||
|
||||
if self._scale is not None:
|
||||
scale_value /= self._scale
|
||||
|
||||
self.camera.parallel_scale = 0.5 * yd / self._scale
|
||||
@@ -0,0 +1,912 @@
|
||||
"""Module containing pyvista implementation of :vtk:`vtkCamera`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from weakref import proxy
|
||||
import xml.dom.minidom as md
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
from . import _vtk
|
||||
from .helpers import view_vectors
|
||||
|
||||
|
||||
class Camera(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkCamera):
|
||||
"""PyVista wrapper for the VTK Camera class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
renderer : pyvista.Renderer, optional
|
||||
Renderer to attach the camera to.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a camera at the pyvista module level.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> camera = pv.Camera()
|
||||
|
||||
Access the active camera of a plotter and get the position of the
|
||||
camera.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.position
|
||||
(1.0, 1.0, 1.0)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, renderer=None):
|
||||
"""Initialize a new camera descriptor."""
|
||||
self._parallel_projection = False
|
||||
self._elevation = 0.0
|
||||
self._azimuth = 0.0
|
||||
self._is_set = False
|
||||
self._focus = None # Used by BackgroundRenderer
|
||||
|
||||
if renderer:
|
||||
if not isinstance(renderer, pyvista.Renderer):
|
||||
msg = 'Camera only accepts a pyvista.Renderer or None as the ``renderer`` argument'
|
||||
raise TypeError(msg)
|
||||
self._renderer = proxy(renderer)
|
||||
else:
|
||||
self._renderer = None # type: ignore[assignment]
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
"""Compare whether the relevant attributes of two cameras are equal."""
|
||||
# attributes which are native python types and thus implement __eq__
|
||||
|
||||
native_attrs = [
|
||||
'position',
|
||||
'focal_point',
|
||||
'parallel_projection',
|
||||
'distance',
|
||||
'thickness',
|
||||
'parallel_scale',
|
||||
'clipping_range',
|
||||
'view_angle',
|
||||
'roll',
|
||||
]
|
||||
for attr in native_attrs:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
|
||||
this_trans = self.model_transform_matrix
|
||||
that_trans = other.model_transform_matrix
|
||||
trans_count = sum(1 for trans in [this_trans, that_trans] if trans is not None)
|
||||
if trans_count == 1:
|
||||
# either but not both are None
|
||||
return False
|
||||
return not (trans_count == 2 and not np.array_equal(this_trans, that_trans))
|
||||
|
||||
__hash__ = None # type: ignore[assignment] # https://github.com/pyvista/pyvista/pull/7671
|
||||
|
||||
def __repr__(self):
|
||||
"""Print a repr specifying the id of the camera and its camera type."""
|
||||
repr_str = f'{self.__class__.__name__} ({hex(id(self))})'
|
||||
repr_str += f'\n Position: {self.position}'
|
||||
repr_str += f'\n Focal Point: {self.focal_point}'
|
||||
repr_str += f'\n Parallel Projection: {self.parallel_projection}'
|
||||
repr_str += f'\n Distance: {self.distance}'
|
||||
repr_str += f'\n Thickness: {self.thickness}'
|
||||
repr_str += f'\n Parallel Scale: {self.parallel_scale}'
|
||||
repr_str += f'\n Clipping Range: {self.clipping_range}'
|
||||
repr_str += f'\n View Angle: {self.view_angle}'
|
||||
repr_str += f'\n Roll: {self.roll}'
|
||||
return repr_str
|
||||
|
||||
def __str__(self):
|
||||
"""Return the object string representation."""
|
||||
return self.__repr__()
|
||||
|
||||
def __del__(self):
|
||||
"""Delete the camera."""
|
||||
self.RemoveAllObservers()
|
||||
|
||||
@property
|
||||
def is_set(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Get or set whether this camera has been configured."""
|
||||
return self._is_set
|
||||
|
||||
@is_set.setter
|
||||
def is_set(self, value: bool):
|
||||
self._is_set = bool(value)
|
||||
|
||||
@classmethod
|
||||
def from_paraview_pvcc(cls, filename: str | Path) -> Camera:
|
||||
"""Load a Paraview camera file (.pvcc extension).
|
||||
|
||||
Returns a pyvista.Camera object for which attributes has been read
|
||||
from the filename argument.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str or pathlib.Path
|
||||
Path to Paraview camera file (.pvcc).
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Camera
|
||||
Camera from the camera file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera = pv.Camera.from_paraview_pvcc('camera.pvcc') # doctest:+SKIP
|
||||
>>> pl.camera.position
|
||||
(1.0, 1.0, 1.0)
|
||||
|
||||
"""
|
||||
to_find = {
|
||||
'CameraPosition': ('position', float),
|
||||
'CameraFocalPoint': ('focal_point', float),
|
||||
'CameraViewAngle': ('view_angle', float),
|
||||
'CameraViewUp': ('up', float),
|
||||
'CameraParallelProjection': ('parallel_projection', int),
|
||||
'CameraParallelScale': ('parallel_scale', float),
|
||||
}
|
||||
camera = cls()
|
||||
|
||||
tree = ET.parse(filename)
|
||||
root = tree.getroot()[0]
|
||||
for element in root:
|
||||
attrib = element.attrib
|
||||
attrib_name = attrib['name']
|
||||
|
||||
if attrib_name in to_find:
|
||||
name, typ = to_find[attrib_name]
|
||||
nelems = int(attrib['number_of_elements'])
|
||||
|
||||
# Set the camera attributes
|
||||
if nelems == 3:
|
||||
values = [typ(e.attrib['value']) for e in element]
|
||||
setattr(camera, name, values)
|
||||
elif nelems == 1:
|
||||
# Special case for bool since bool("0") returns True.
|
||||
# So first convert to int from `to_find` and then apply bool
|
||||
if 'name' in element[-1].attrib and element[-1].attrib['name'] == 'bool':
|
||||
val = bool(typ(element[0].attrib['value']))
|
||||
else:
|
||||
val = typ(element[0].attrib['value'])
|
||||
setattr(camera, name, val)
|
||||
|
||||
camera.is_set = True
|
||||
return camera
|
||||
|
||||
def to_paraview_pvcc(self, filename: str | Path):
|
||||
"""Write the camera parameters to a Paraview camera file (.pvcc extension).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str or pathlib.Path
|
||||
Path to Paraview camera file (.pvcc).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.to_paraview_pvcc('camera.pvcc') # doctest:+SKIP
|
||||
|
||||
"""
|
||||
root = ET.Element('PVCameraConfiguration')
|
||||
root.attrib['description'] = 'ParaView camera configuration'
|
||||
root.attrib['version'] = '1.0'
|
||||
|
||||
dico = dict(group='views', type='RenderView', id='0', servers='21')
|
||||
proxy = ET.SubElement(root, 'Proxy', dico)
|
||||
|
||||
# Add tuples
|
||||
to_find = {
|
||||
'CameraPosition': 'position',
|
||||
'CameraFocalPoint': 'focal_point',
|
||||
'CameraViewUp': 'up',
|
||||
}
|
||||
for name, attr in to_find.items():
|
||||
e = ET.SubElement(
|
||||
proxy,
|
||||
'Property',
|
||||
dict(name=name, id=f'0.{name}', number_of_elements='3'),
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
tmp = ET.Element('Element')
|
||||
tmp.attrib['index'] = str(i)
|
||||
tmp.attrib['value'] = str(getattr(self, attr)[i])
|
||||
e.append(tmp)
|
||||
|
||||
# Add single values
|
||||
to_find = {
|
||||
'CameraViewAngle': 'view_angle',
|
||||
'CameraParallelScale': 'parallel_scale',
|
||||
'CameraParallelProjection': 'parallel_projection',
|
||||
}
|
||||
|
||||
for name, attr in to_find.items():
|
||||
e = ET.SubElement(
|
||||
proxy,
|
||||
'Property',
|
||||
dict(name=name, id=f'0.{name}', number_of_elements='1'),
|
||||
)
|
||||
tmp = ET.Element('Element')
|
||||
tmp.attrib['index'] = '0'
|
||||
|
||||
val = getattr(self, attr)
|
||||
if not isinstance(val, bool):
|
||||
tmp.attrib['value'] = str(val)
|
||||
e.append(tmp)
|
||||
else:
|
||||
tmp.attrib['value'] = '1' if val else '0'
|
||||
e.append(tmp)
|
||||
e.append(ET.Element('Domain', dict(name='bool', id=f'0.{name}.bool')))
|
||||
|
||||
xmlstr = ET.tostring(root).decode()
|
||||
newxml = md.parseString(xmlstr)
|
||||
with Path(filename).open('w') as outfile:
|
||||
outfile.write(newxml.toprettyxml(indent='\t', newl='\n'))
|
||||
|
||||
@property
|
||||
def position(self): # numpydoc ignore=RT01
|
||||
"""Return or set the position of the camera in world coordinates.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.position
|
||||
(1.0, 1.0, 1.0)
|
||||
>>> pl.camera.position = (2.0, 1.0, 1.0)
|
||||
>>> pl.camera.position
|
||||
(2.0, 1.0, 1.0)
|
||||
|
||||
"""
|
||||
return self.GetPosition()
|
||||
|
||||
@position.setter
|
||||
def position(self, value):
|
||||
self.SetPosition(value)
|
||||
self._elevation = 0.0
|
||||
self._azimuth = 0.0
|
||||
if self._renderer: # type: ignore[truthy-bool]
|
||||
self.reset_clipping_range()
|
||||
self.is_set = True
|
||||
|
||||
def reset_clipping_range(self):
|
||||
"""Reset the camera clipping range based on the bounds of the visible actors.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(pv.Sphere())
|
||||
>>> pl.camera.clipping_range = (1, 2)
|
||||
>>> pl.camera.reset_clipping_range() # doctest:+SKIP
|
||||
(0.0039213485598532955, 3.9213485598532953)
|
||||
|
||||
"""
|
||||
if self._renderer is None:
|
||||
msg = 'Camera is must be associated with a renderer to reset its clipping range.' # type: ignore[unreachable]
|
||||
raise AttributeError(msg)
|
||||
self._renderer.reset_camera_clipping_range()
|
||||
|
||||
@property
|
||||
def focal_point(self): # numpydoc ignore=RT01
|
||||
"""Location of the camera's focus in world coordinates.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.focal_point
|
||||
(0.0, 0.0, 0.0)
|
||||
>>> pl.camera.focal_point = (2.0, 0.0, 0.0)
|
||||
>>> pl.camera.focal_point
|
||||
(2.0, 0.0, 0.0)
|
||||
|
||||
"""
|
||||
return self.GetFocalPoint()
|
||||
|
||||
@focal_point.setter
|
||||
def focal_point(self, point):
|
||||
self.SetFocalPoint(point)
|
||||
self.is_set = True
|
||||
|
||||
@property
|
||||
def model_transform_matrix(self): # numpydoc ignore=RT01
|
||||
"""Return or set the camera's model transformation matrix.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.model_transform_matrix
|
||||
array([[1., 0., 0., 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 1.]])
|
||||
>>> pl.camera.model_transform_matrix = np.array(
|
||||
... [
|
||||
... [1.0, 0.0, 0.0, 0.0],
|
||||
... [0.0, 1.0, 0.0, 0.0],
|
||||
... [0.0, 0.0, 1.0, 0.0],
|
||||
... [0.0, 0.0, 0.0, 0.5],
|
||||
... ]
|
||||
... )
|
||||
>>>
|
||||
array([[1., 0., 0., 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 0.5]])
|
||||
|
||||
"""
|
||||
vtk_matrix = self.GetModelTransformMatrix()
|
||||
matrix = np.empty((4, 4))
|
||||
vtk_matrix.DeepCopy(matrix.ravel(), vtk_matrix)
|
||||
return matrix
|
||||
|
||||
@model_transform_matrix.setter
|
||||
def model_transform_matrix(self, matrix):
|
||||
vtk_matrix = _vtk.vtkMatrix4x4()
|
||||
vtk_matrix.DeepCopy(matrix.ravel())
|
||||
self.SetModelTransformMatrix(vtk_matrix)
|
||||
|
||||
@property
|
||||
def distance(self): # numpydoc ignore=RT01
|
||||
"""Return or set the distance of the focal point from the camera.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Setting the distance keeps the camera fixed and moves the focal point.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.distance
|
||||
1.73205
|
||||
>>> pl.camera.distance = 2.0
|
||||
>>> pl.camera.distance
|
||||
2.0
|
||||
|
||||
"""
|
||||
return self.GetDistance()
|
||||
|
||||
@distance.setter
|
||||
def distance(self, distance):
|
||||
self.SetDistance(distance)
|
||||
self.is_set = True
|
||||
|
||||
@property
|
||||
def thickness(self): # numpydoc ignore=RT01
|
||||
"""Return or set the distance between clipping planes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.thickness
|
||||
1000.0
|
||||
>>> pl.camera.thickness = 100
|
||||
>>> pl.camera.thickness
|
||||
100.0
|
||||
|
||||
"""
|
||||
return self.GetThickness()
|
||||
|
||||
@thickness.setter
|
||||
def thickness(self, length):
|
||||
self.SetThickness(length)
|
||||
|
||||
@property
|
||||
def parallel_scale(self): # numpydoc ignore=RT01
|
||||
"""Return or set the scaling used for a parallel projection.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.parallel_scale
|
||||
1.0
|
||||
>>> pl.camera.parallel_scale = 2.0
|
||||
>>> pl.camera.parallel_scale
|
||||
2.0
|
||||
|
||||
"""
|
||||
return self.GetParallelScale()
|
||||
|
||||
@parallel_scale.setter
|
||||
def parallel_scale(self, scale):
|
||||
self.SetParallelScale(scale)
|
||||
|
||||
def zoom(self, value):
|
||||
"""Set the zoom of the camera.
|
||||
|
||||
In perspective mode, decrease the view angle by the specified
|
||||
factor.
|
||||
|
||||
In parallel mode, decrease the parallel scale by the specified
|
||||
factor. A value greater than 1 is a zoom-in, a value less than
|
||||
1 is a zoom-out.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : float or str
|
||||
Zoom of the camera. If a float, must be greater than 0. Otherwise,
|
||||
if a string, must be ``"tight"``. If tight, the plot will be zoomed
|
||||
such that the actors fill the entire viewport.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Show the Default zoom.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(pv.Sphere())
|
||||
>>> pl.camera.zoom(1.0)
|
||||
>>> pl.show()
|
||||
|
||||
Show 2x zoom.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(pv.Sphere())
|
||||
>>> pl.camera.zoom(2.0)
|
||||
>>> pl.show()
|
||||
|
||||
Zoom so the actor fills the entire render window.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(pv.Sphere())
|
||||
>>> pl.camera.zoom('tight')
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
if value != 'tight':
|
||||
msg = 'If a string, ``zoom`` can only be "tight"'
|
||||
raise ValueError(msg)
|
||||
self.tight()
|
||||
return
|
||||
|
||||
self.Zoom(value)
|
||||
self.is_set = True
|
||||
|
||||
@property
|
||||
def up(self): # numpydoc ignore=RT01
|
||||
"""Return or set the "up" of the camera.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.up
|
||||
(0.0, 0.0, 1.0)
|
||||
>>> pl.camera.up = (0.410018, 0.217989, 0.885644)
|
||||
>>> pl.camera.up
|
||||
(0.410018, 0.217989, 0.885644)
|
||||
|
||||
"""
|
||||
return self.GetViewUp()
|
||||
|
||||
@up.setter
|
||||
def up(self, vector):
|
||||
self.SetViewUp(vector)
|
||||
self.is_set = True
|
||||
|
||||
def enable_parallel_projection(self):
|
||||
"""Enable parallel projection.
|
||||
|
||||
The camera will have a parallel projection. Parallel
|
||||
projection is often useful when viewing images or 2D datasets,
|
||||
but will look odd when viewing 3D datasets.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import demos
|
||||
>>> pl = pv.demos.orientation_plotter()
|
||||
>>> pl.enable_parallel_projection()
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
self._parallel_projection = True
|
||||
self.SetParallelProjection(True)
|
||||
|
||||
def disable_parallel_projection(self):
|
||||
"""Disable the use of parallel projection.
|
||||
|
||||
This is default behavior.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import demos
|
||||
>>> pl = pv.demos.orientation_plotter()
|
||||
>>> pl.disable_parallel_projection()
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
self._parallel_projection = False
|
||||
self.SetParallelProjection(False)
|
||||
|
||||
@property
|
||||
def parallel_projection(self): # numpydoc ignore=RT01
|
||||
"""Return the state of the parallel projection.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import demos
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.disable_parallel_projection()
|
||||
>>> pl.parallel_projection
|
||||
False
|
||||
|
||||
"""
|
||||
return self._parallel_projection
|
||||
|
||||
@parallel_projection.setter
|
||||
def parallel_projection(self, state):
|
||||
if state:
|
||||
self.enable_parallel_projection()
|
||||
else:
|
||||
self.disable_parallel_projection()
|
||||
|
||||
@property
|
||||
def clipping_range(self): # numpydoc ignore=RT01
|
||||
"""Return or set the location of the clipping planes.
|
||||
|
||||
Clipping planes are the near and far clipping planes along
|
||||
the direction of projection.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.clipping_range
|
||||
(0.01, 1000.01)
|
||||
>>> pl.camera.clipping_range = (1, 10)
|
||||
>>> pl.camera.clipping_range
|
||||
(1.0, 10.0)
|
||||
|
||||
"""
|
||||
return self.GetClippingRange()
|
||||
|
||||
@clipping_range.setter
|
||||
def clipping_range(self, points):
|
||||
if points[0] > points[1]:
|
||||
msg = 'Near point must be lower than the far point.'
|
||||
raise ValueError(msg)
|
||||
self.SetClippingRange(points[0], points[1])
|
||||
|
||||
@property
|
||||
def view_angle(self): # numpydoc ignore=RT01
|
||||
"""Return or set the camera view angle.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> plotter = pv.Plotter()
|
||||
>>> plotter.camera.view_angle
|
||||
30.0
|
||||
>>> plotter.camera.view_angle = 60.0
|
||||
>>> plotter.camera.view_angle
|
||||
60.0
|
||||
|
||||
"""
|
||||
return self.GetViewAngle()
|
||||
|
||||
@view_angle.setter
|
||||
def view_angle(self, value):
|
||||
self.SetViewAngle(value)
|
||||
|
||||
@property
|
||||
def direction(self): # numpydoc ignore=RT01
|
||||
"""Vector from the camera position to the focal point.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.direction # doctest:+SKIP
|
||||
(-0.5773502691896257, -0.5773502691896257, -0.5773502691896257)
|
||||
|
||||
"""
|
||||
return self.GetDirectionOfProjection()
|
||||
|
||||
def view_frustum(self, aspect=1.0):
|
||||
"""Get the view frustum.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aspect : float, default: 1.0
|
||||
The aspect of the viewport to compute the planes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
View frustum.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> plotter = pv.Plotter()
|
||||
>>> frustum = plotter.camera.view_frustum(1.0)
|
||||
>>> frustum.n_points
|
||||
8
|
||||
>>> frustum.n_cells
|
||||
6
|
||||
|
||||
"""
|
||||
frustum_planes = [0] * 24
|
||||
self.GetFrustumPlanes(aspect, frustum_planes) # type: ignore[arg-type]
|
||||
planes = _vtk.vtkPlanes()
|
||||
planes.SetFrustumPlanes(frustum_planes) # type: ignore[arg-type]
|
||||
|
||||
frustum_source = _vtk.vtkFrustumSource()
|
||||
frustum_source.ShowLinesOff()
|
||||
frustum_source.SetPlanes(planes)
|
||||
frustum_source.Update()
|
||||
|
||||
return pyvista.wrap(frustum_source.GetOutput())
|
||||
|
||||
@property
|
||||
def roll(self): # numpydoc ignore=RT01
|
||||
"""Return or set the roll of the camera about the direction of projection.
|
||||
|
||||
This will spin the camera about its axis.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.roll
|
||||
-120.00000000000001
|
||||
>>> pl.camera.roll = 45.0
|
||||
>>> pl.camera.roll
|
||||
45.0
|
||||
|
||||
"""
|
||||
return self.GetRoll()
|
||||
|
||||
@roll.setter
|
||||
def roll(self, angle):
|
||||
self.SetRoll(angle)
|
||||
self.is_set = True
|
||||
|
||||
@property
|
||||
def elevation(self): # numpydoc ignore=RT01
|
||||
"""Return or set the vertical rotation of the scene.
|
||||
|
||||
Rotate the camera about the cross product of the negative of
|
||||
the direction of projection and the view up vector, using the
|
||||
focal point as the center of rotation.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.elevation
|
||||
0.0
|
||||
>>> pl.camera.elevation = 45.0
|
||||
>>> pl.camera.elevation
|
||||
45.0
|
||||
|
||||
"""
|
||||
return self._elevation
|
||||
|
||||
@elevation.setter
|
||||
def elevation(self, angle):
|
||||
if self._elevation:
|
||||
self.Elevation(-self._elevation)
|
||||
self._elevation = angle
|
||||
self.Elevation(angle)
|
||||
self.is_set = True
|
||||
|
||||
@property
|
||||
def azimuth(self): # numpydoc ignore=RT01
|
||||
"""Return or set the azimuth of the camera.
|
||||
|
||||
Rotate the camera about the view up vector centered at the
|
||||
focal point. Note that the view up vector is whatever was set
|
||||
via SetViewUp, and is not necessarily perpendicular to the
|
||||
direction of projection.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> pl = pv.Plotter()
|
||||
>>> pl.camera.azimuth
|
||||
0.0
|
||||
>>> pl.camera.azimuth = 45.0
|
||||
>>> pl.camera.azimuth
|
||||
45.0
|
||||
|
||||
"""
|
||||
return self._azimuth
|
||||
|
||||
@azimuth.setter
|
||||
def azimuth(self, angle):
|
||||
if self._azimuth:
|
||||
self.Azimuth(-self._azimuth)
|
||||
self._azimuth = angle
|
||||
self.Azimuth(angle)
|
||||
self.is_set = True
|
||||
|
||||
def copy(self):
|
||||
"""Return a deep copy of the camera.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.Camera
|
||||
Deep copy of the camera.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a camera and check that it shares a transformation
|
||||
matrix with its shallow copy.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> import numpy as np
|
||||
>>> camera = pv.Camera()
|
||||
>>> camera.model_transform_matrix = np.array(
|
||||
... [
|
||||
... [1.0, 0.0, 0.0, 0.0],
|
||||
... [0.0, 1.0, 0.0, 0.0],
|
||||
... [0.0, 0.0, 1.0, 0.0],
|
||||
... [0.0, 0.0, 0.0, 1.0],
|
||||
... ]
|
||||
... )
|
||||
>>> copied_camera = camera.copy()
|
||||
>>> copied_camera == camera
|
||||
True
|
||||
>>> camera.model_transform_matrix = np.array(
|
||||
... [
|
||||
... [1.0, 0.0, 0.0, 0.0],
|
||||
... [0.0, 1.0, 0.0, 0.0],
|
||||
... [0.0, 0.0, 1.0, 0.0],
|
||||
... [0.0, 0.0, 0.0, 0.5],
|
||||
... ]
|
||||
... )
|
||||
>>> copied_camera == camera
|
||||
False
|
||||
|
||||
"""
|
||||
immutable_attrs = [
|
||||
'position',
|
||||
'focal_point',
|
||||
'model_transform_matrix',
|
||||
'distance',
|
||||
'thickness',
|
||||
'parallel_scale',
|
||||
'up',
|
||||
'clipping_range',
|
||||
'view_angle',
|
||||
'roll',
|
||||
'parallel_projection',
|
||||
'is_set',
|
||||
]
|
||||
new_camera = Camera()
|
||||
|
||||
for attr in immutable_attrs:
|
||||
value = getattr(self, attr)
|
||||
setattr(new_camera, attr, value)
|
||||
|
||||
return new_camera
|
||||
|
||||
@_deprecate_positional_args
|
||||
def tight( # noqa: PLR0917
|
||||
self,
|
||||
padding=0.0,
|
||||
adjust_render_window: bool = True, # noqa: FBT001, FBT002
|
||||
view='xy',
|
||||
negative: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Adjust the camera position so that the actors fill the entire renderer.
|
||||
|
||||
The camera view direction is reoriented to be normal to the ``view``
|
||||
plane. When ``negative=False``, The first letter of ``view`` refers
|
||||
to the axis that points to the right. The second letter of ``view``
|
||||
refers to axis that points up. When ``negative=True``, the first
|
||||
letter refers to the axis that points left. The up direction is
|
||||
unchanged.
|
||||
|
||||
Parallel projection is enabled when using this function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
padding : float, default: 0.0
|
||||
Additional padding around the actor(s). This is effectively a zoom,
|
||||
where a value of 0.01 results in a zoom out of 1%.
|
||||
|
||||
adjust_render_window : bool, default: True
|
||||
Adjust the size of the render window as to match the dimensions of
|
||||
the visible actors.
|
||||
|
||||
view : {'xy', 'yx', 'xz', 'zx', 'yz', 'zy'}, default: 'xy'
|
||||
Plane to which the view is oriented.
|
||||
|
||||
negative : bool, default: False
|
||||
Whether to view in opposite direction.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This resets the view direction to look at a plane with parallel projection.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Display the puppy image with a tight view.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> from pyvista import examples
|
||||
>>> puppy = examples.download_puppy()
|
||||
>>> pl = pv.Plotter(border=True, border_width=5)
|
||||
>>> _ = pl.add_mesh(puppy, rgb=True)
|
||||
>>> pl.camera.tight()
|
||||
>>> pl.show()
|
||||
|
||||
Set the background to blue use a 5% padding around the image.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_mesh(puppy, rgb=True)
|
||||
>>> pl.background_color = 'b'
|
||||
>>> pl.camera.tight(padding=0.05)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
# Inspired by vedo resetCamera. Thanks @marcomusy.
|
||||
x0, x1, y0, y1, z0, z1 = self._renderer.bounds
|
||||
|
||||
self.enable_parallel_projection()
|
||||
|
||||
self._renderer.ComputeAspect()
|
||||
aspect = self._renderer.GetAspect()
|
||||
|
||||
position0 = np.array([x0, y0, z0])
|
||||
position1 = np.array([x1, y1, z1])
|
||||
objects_size = position1 - position0
|
||||
position = position0 + objects_size / 2
|
||||
|
||||
direction, viewup = view_vectors(view, negative=negative)
|
||||
horizontal = np.cross(direction, viewup)
|
||||
|
||||
vert_dist = abs(objects_size @ viewup)
|
||||
horiz_dist = abs(objects_size @ horizontal)
|
||||
|
||||
# set focal point to objects' center
|
||||
# offset camera position from objects center by dist in opposite of viewing direction
|
||||
# (actual distance doesn't matter due to parallel projection)
|
||||
dist = 1
|
||||
camera_position = position + dist * direction
|
||||
|
||||
self.SetViewUp(*viewup)
|
||||
self.SetPosition(*camera_position)
|
||||
self.SetFocalPoint(*position)
|
||||
|
||||
ps = max(horiz_dist / aspect[0], vert_dist) / 2
|
||||
self.parallel_scale = ps * (1 + padding)
|
||||
self._renderer.ResetCameraClippingRange(x0, x1, y0, y1, z0, z1)
|
||||
|
||||
if adjust_render_window:
|
||||
ren_win = self._renderer.GetRenderWindow()
|
||||
size = list(ren_win.GetSize())
|
||||
size_ratio = size[0] / size[1]
|
||||
tight_ratio = horiz_dist / vert_dist
|
||||
resize_ratio = tight_ratio / size_ratio
|
||||
if resize_ratio < 1:
|
||||
size[0] = round(size[0] * resize_ratio)
|
||||
else:
|
||||
size[1] = round(size[1] / resize_ratio)
|
||||
|
||||
ren_win.SetSize(size)
|
||||
|
||||
# simply call tight again to reset the parallel scale due to the
|
||||
# resized window
|
||||
self.tight(padding=padding, adjust_render_window=False, view=view, negative=negative)
|
||||
|
||||
self.is_set = True
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user