This commit is contained in:
cjw
2026-02-12 23:22:11 +08:00
parent 7b09eb3d89
commit 89660bba4e
5988 changed files with 2517516 additions and 0 deletions
@@ -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,
)