angles = np.linspace(0.0, angle, resolution + 1)
    arc['Distance'] = radius * angles
    return arc


def Pyramid(points=None):
    """Create a pyramid defined by 5 points.

    Parameters
    ----------
    points : sequence, optional
        Points of the pyramid.  Points are ordered such that the first
        four points are the four counterclockwise points on the
        quadrilateral face, and the last point is the apex.

        Defaults to pyramid in example.

    Returns
    -------
    pyvista.UnstructuredGrid
        Unstructured grid containing a single pyramid cell.

    Examples
    --------
    >>> import pyvista
    >>> pointa = [1.0, 1.0, 0.0]
    >>> pointb = [-1.0, 1.0, 0.0]
    >>> pointc = [-1.0, -1.0, 0.0]
    >>> pointd = [1.0, -1.0, 0.0]
    >>> pointe = [0.0, 0.0, 1.608]
    >>> pyramid = pyvista.Pyramid([pointa, pointb, pointc, pointd, pointe])
    >>> pyramid.plot(show_edges=True, line_width=5)
    """
    if points is None:
        points = [
            [1.0, 1.0, 0.0],
            [-1.0, 1.0, 0.0],
            [-1.0, -1.0, 0.0],
            [1.0, -1.0, 0.0],
            [0.0, 0.0, (4 - 2**0.5) ** 0.5],
        ]

    if len(points) != 5:
        raise TypeError('Points must be given as length 5 np.ndarray or list')

    check_valid_vector(points[0], 'points[0]')
    check_valid_vector(points[1], 'points[1]')
    check_valid_vector(points[2], 'points[2]')
    check_valid_vector(points[3], 'points[3]')
    check_valid_vector(points[4], 'points[4]')

    pyramid = _vtk.vtkPyramid()
    pyramid.GetPointIds().SetId(0, 0)
    pyramid.GetPointIds().SetId(1, 1)
    pyramid.GetPointIds().SetId(2, 2)
    pyramid.GetPointIds().SetId(3, 3)
    pyramid.GetPointIds().SetId(4, 4)

    ug = _vtk.vtkUnstructuredGrid()
    ug.SetPoints(pyvista.vtk_points(np.array(points), False))
    ug.InsertNextCell(pyramid.GetCellType(), pyramid.GetPointIds())

    return pyvista.wrap(ug)


def Triangle(points=None):
    """Create a triangle defined by 3 points.

    Parameters
    ----------
    points : sequence, optional
        Points of the triangle.  Defaults to a right isosceles
        triangle (see example).

    Returns
    -------
    pyvista.PolyData
        Triangle mesh.

    Examples
    --------
    >>> import pyvista
    >>> pointa = [0, 0, 0]
    >>> pointb = [1, 0, 0]
    >>> pointc = [0.5, 0.707, 0]
    >>> triangle = pyvista.Triangle([pointa, pointb, pointc])
    >>> triangle.plot(show_edges=True, line_width=5)
    """
    if points is None:
        points = [[0, 0, 0], [1, 0, 0], [0.5, 0.5**0.5, 0]]

    if len(points) != 3:
        raise TypeError('Points must be given as length 3 np.ndarray or list')

    check_valid_vector(points[0], 'points[0]')
    check_valid_vector(points[1], 'points[1]')
    check_valid_vector(points[2], 'points[2]')

    cells = np.array([[3, 0, 1, 2]])
    return pyvista.wrap(pyvista.PolyData(points, cells))


def Rectangle(points=None):
    """Create a rectangle defined by 4 points.

    Parameters
    ----------
    points : sequence, optional
        Points of the rectangle.  Defaults to a simple example.

    Returns
    -------
    pyvista.PolyData
        Rectangle mesh.

    Examples
    --------
    >>> import pyvista
    >>> pointa = [1.0, 0.0, 0.0]
    >>> pointb = [1.0, 1.0, 0.0]
    >>> pointc = [0.0, 1.0, 0.0]
    >>> pointd = [0.0, 0.0, 0.0]
    >>> rectangle = pyvista.Rectangle([pointa, pointb, pointc, pointd])
    >>> rectangle.plot(show_edges=True, line_width=5)

    """
    if points is None:
        points = [[1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]]
    if len(points) != 4:
        raise TypeError('Points must be given as length 4 np.ndarray or list')

    check_valid_vector(points[0], 'points[0]')
    check_valid_vector(points[1], 'points[1]')
    check_valid_vector(points[2], 'points[2]')
    check_valid_vector(points[3], 'points[3]')

    cells = np.array([[4, 0, 1, 2, 3]])
    return pyvista.wrap(pyvista.PolyData(points, cells))


def Circle(radius=0.5, resolution=100):
    """Create a single PolyData circle defined by radius in the XY plane.

    Parameters
    ----------
    radius : float, optional
        Radius of circle.

    resolution : int, optional
        Number of points on the circle.

    Returns
    -------
    pyvista.PolyData
        Circle mesh.

    Examples
    --------
    >>> import pyvista
    >>> radius = 0.5
    >>> circle = pyvista.Circle(radius)
    >>> circle.plot(show_edges=True, line_width=5)
    """
    points = np.zeros((resolution, 3))
    theta = np.linspace(0.0, 2.0 * np.pi, resolution)
    points[:, 0] = radius * np.cos(theta)
    points[:, 1] = radius * np.sin(theta)
    cells = np.array([np.append(np.array([resolution]), np.arange(resolution))])
    return pyvista.wrap(pyvista.PolyData(points, cells))


def Superquadric(
    center=(0.0, 0.0, 0.0),
    scale=(1.0, 1.0, 1.0),
    size=0.5,
    theta_roundness=1.0,
    phi_roundness=1.0,
    theta_resolution=16,
    phi_resolution=16,
    toroidal=False,
    thickness=1 / 3,
):
    """Create a superquadric.

    Parameters
    ----------
    center : iterable, optional
        Center of the superquadric in ``[x, y, z]``.

    scale : iterable, optional
        Scale factors of the superquadric in ``[x, y, z]``.

    size : float, optional
        Superquadric isotropic size.

    theta_roundness : float, optional
        Superquadric east/west roundness.
        Values range from 0 (rectangular) to 1 (circular) to higher orders.

    phi_roundness : float, optional
        Superquadric north/south roundness.
        Values range from 0 (rectangular) to 1 (circular) to higher orders.

    theta_resolution : int, optional
        Number of points in the longitude direction.
        Values are rounded to nearest multiple of 4.

    phi_resolution : int, optional
        Number of points in the latitude direction.
        Values are rounded to nearest multiple of 8.

    toroidal : bool, optional
        Whether or not the superquadric is toroidal (``True``)
        or ellipsoidal (``False``).

    thickness : float, optional
        Superquadric ring thickness.
        Only applies if toroidal is set to ``True``.

    Returns
    -------
    pyvista.PolyData
        Superquadric mesh.

    See Also
    --------
    pyvista.ParametricSuperEllipsoid :
        Parametric superquadric if toroidal is ``False``.
    pyvista.ParametricSuperToroid :
        Parametric superquadric if toroidal is ``True``.

    Examples
    --------
    >>> import pyvista
    >>> superquadric = pyvista.Superquadric(scale=(3., 1., 0.5),
    ...                                     phi_roundness=0.1,
    ...                                     theta_roundness=0.5)
    >>> superquadric.plot(show_edges=True)

    """
    superquadricSource = _vtk.vtkSuperquadricSource()
    superquadricSource.SetCenter(center)
    superquadricSource.SetScale(scale)
    superquadricSource.SetSize(size)
    superquadricSource.SetThetaRoundness(theta_roundness)
    superquadricSource.SetPhiRoundness(phi_roundness)
    superquadricSource.SetThetaResolution(round(theta_resolution / 4) * 4)
    superquadricSource.SetPhiResolution(round(phi_resolution / 8) * 8)
    superquadricSource.SetToroidal(toroidal)
    superquadricSource.SetThickness(thickness)
    superquadricSource.Update()
    return pyvista.wrap(superquadricSource.GetOutput())


def PlatonicSolid(kind='tetrahedron', radius=1.0, center=(0.0, 0.0, 0.0)):
    """Create a Platonic solid of a given size.

    Parameters
    ----------
    kind : str or int, optional
        The kind of Platonic solid to create. Either the name of the
        polyhedron or an integer index:

            * ``'tetrahedron'`` or ``0``
            * ``'cube'`` or ``1``
            * ``'octahedron'`` or ``2``
            * ``'icosahedron'`` or ``3``
            * ``'dodecahedron'`` or ``4``

    radius : float, optional
        The radius of the circumscribed sphere for the solid to create.

    center : sequence, optional
        Three-length sequence defining the center of the solid to create.

    Returns
    -------
    pyvista.PolyData
        One of the five Platonic solids. Cell scalars are defined that
        assign integer labels to each face (with array name
        ``"FaceIndex"``).

    Examples
    --------
    Create and plot a dodecahedron.

    >>> import pyvista
    >>> dodeca = pyvista.PlatonicSolid('dodecahedron')
    >>> dodeca.plot(categories=True)

    See :ref:`platonic_example` for more examples using this filter.

    """
    kinds = {
        'tetrahedron': 0,
        'cube': 1,
        'octahedron': 2,
        'icosahedron': 3,
        'dodecahedron': 4,
    }
    if isinstance(kind, str):
        if kind not in kinds:
            raise ValueError(f'Invalid Platonic solid kind "{kind}".')
        kind = kinds[kind]
    elif isinstance(kind, int) and kind not in range(5):
        raise ValueError(f'Invalid Platonic solid index "{kind}".')
    elif not isinstance(kind, int):
        raise ValueError(f'Invalid Platonic solid index type "{type(kind).__name__}".')
    check_valid_vector(center, 'center')

    solid = _vtk.vtkPlatonicSolidSource()
    solid.SetSolidType(kind)
    solid.Update()
    solid = pyvista.wrap(solid.GetOutput())
    # rename and activate cell scalars
    cell_data = solid.cell_data.get_array(0)
    solid.clear_data()
    solid.cell_data['FaceIndex'] = cell_data
    # scale and translate
    solid.scale(radius, inplace=True)
    solid.points += np.asanyarray(center) - solid.center
    return solid


def Tetrahedron(radius=1.0, center=(0.0, 0.0, 0.0)):
    """Create a tetrahedron of a given size.

    A tetrahedron is composed of four congruent equilateral triangles.

    Parameters
    ----------
    radius : float, optional
        The radius of the circumscribed sphere for the tetrahedron.

    center : sequence, optional
        Three-length sequence defining the center of the tetrahedron.

    Returns
    -------
    pyvista.PolyData
        Mesh for the tetrahedron. Cell scalars are defined that assign
        integer labels to each face (with array name ``"FaceIndex"``).

    Examples
    --------
    Create and plot a tetrahedron.

    >>> import pyvista
    >>> tetra = pyvista.Tetrahedron()
    >>> tetra.plot(categories=True)

    See :ref:`platonic_example` for more examples using this filter.

    """
    return PlatonicSolid(kind='tetrahedron', radius=radius, center=center)


def Octahedron(radius=1.0, center=(0.0, 0.0, 0.0)):
    """Create an octahedron of a given size.

    An octahedron is composed of eight congruent equilateral
    triangles.

    Parameters
    ----------
    radius : float, optional
        The radius of the circumscribed sphere for the octahedron.

    center : sequence, optional
        Three-length sequence defining the center of the octahedron.

    Returns
    -------
    pyvista.PolyData
        Mesh for the octahedron. Cell scalars are defined that assign
        integer labels to each face (with array name ``"FaceIndex"``).

    Examples
    --------
    Create and plot an octahedron.

    >>> import pyvista
    >>> tetra = pyvista.Octahedron()
    >>> tetra.plot(categories=True)

    See :ref:`platonic_example` for more examples using this filter.

    """
    return PlatonicSolid(kind='octahedron', radius=radius, center=center)


def Dodecahedron(radius=1.0, center=(0.0, 0.0, 0.0)):
    """Create a dodecahedron of a given size.

    A dodecahedron is composed of twelve congruent regular pentagons.

    Parameters
    ----------
    radius : float, optional
        The radius of the circumscribed sphere for the dodecahedron.

    center : sequence, optional
        Three-length sequence defining the center of the dodecahedron.

    Returns
    -------
    pyvista.PolyData
        Mesh for the dodecahedron. Cell scalars are defined that assign
        integer labels to each face (with array name ``"FaceIndex"``).

    Examples
    --------
    Create and plot a dodecahedron.

    >>> import pyvista
    >>> tetra = pyvista.Dodecahedron()
    >>> tetra.plot(categories=True)

    See :ref:`platonic_example` for more examples using this filter.

    """
    return PlatonicSolid(kind='dodecahedron', radius=radius, center=center)


def Icosahedron(radius=1.0, center=(0.0, 0.0, 0.0)):
    """Create an icosahedron of a given size.

    An icosahedron is composed of twenty congruent equilateral
    triangles.

    Parameters
    ----------
    radius : float, optional
        The radius of the circumscribed sphere for the icosahedron.

    center : sequence, optional
        Three-length sequence defining the center of the icosahedron.

    Returns
    -------
    pyvista.PolyData
        Mesh for the icosahedron. Cell scalars are defined that assign
        integer labels to each face (with array name ``"FaceIndex"``).

    Examples
    --------
    Create and plot an icosahedron.

    >>> import pyvista
    >>> tetra = pyvista.Icosahedron()
    >>> tetra.plot(categories=True)

    See :ref:`platonic_example` for more examples using this filter.

    """
    return PlatonicSolid(kind='icosahedron', radius=radius, center=center)


# pyvista/utilities/helpers.py

"""Supporting functions for polydata and grid objects."""

import collections.abc
import enum
import logging
import os
import signal
import sys
import threading
from threading import Thread
import traceback
from typing import Optional
import warnings

import numpy as np

import pyvista
from pyvista import _vtk
from pyvista.errors import AmbiguousDataError, MissingDataError

from . import transformations
from .fileio import from_meshio


class FieldAssociation(enum.Enum):
    """Represents which type of vtk field a scalar or vector array is associated with."""

    POINT = _vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS
    CELL = _vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS
    NONE = _vtk.vtkDataObject.FIELD_ASSOCIATION_NONE
    ROW = _vtk.vtkDataObject.FIELD_ASSOCIATION_ROWS


def get_vtk_type(typ):
    """Look up the VTK type for a given numpy data type.

    Corrects for string type mapping issues.

    Parameters
    ----------
    typ : numpy.dtype
        Numpy data type.

    Returns
    -------
    int
        Integer type id specified in ``vtkType.h``

    """
    typ = _vtk.get_vtk_array_type(typ)
    # This handles a silly string type bug
    if typ == 3:
        return 13
    return typ


def vtk_bit_array_to_char(vtkarr_bint):
    """Cast vtk bit array to a char array.

    Parameters
    ----------
    vtkarr_bint : vtk.vtkBitArray
        VTK binary array.

    Returns
    -------
    vtk.vtkCharArray
        VTK char array.

    Notes
    -----
    This performs a copy.

    """
    vtkarr = _vtk.vtkCharArray()
    vtkarr.DeepCopy(vtkarr_bint)
    return vtkarr


def vtk_id_list_to_array(vtk_id_list):
    """Convert a vtkIdList to a NumPy array.

    Parameters
    ----------
    vtk_id_list : vtk.vtkIdList
        VTK ID list.

    Returns
    -------
    numpy.ndarray
        Array of IDs.

    """
    return np.array([vtk_id_list.GetId(i) for i in range(vtk_id_list.GetNumberOfIds())])


def convert_string_array(arr, name=None):
    """Convert a numpy array of strings to a vtkStringArray or vice versa.

    Parameters
    ----------
    arr : numpy.ndarray
        Numpy string array to convert.

    name : str, optional
        Name to set the vtkStringArray to.

    Returns
    -------
    vtkStringArray
        VTK string array.

    Notes
    -----
    Note that this is terribly inefficient. If you have ideas on how
    to make this faster, please consider opening a pull request.

    """
    if isinstance(arr, np.ndarray):
        # VTK default fonts only support ASCII. See https://gitlab.kitware.com/vtk/vtk/-/issues/16904
        if np.issubdtype(arr.dtype, np.str_) and not ''.join(arr).isascii():  # avoids segfault
            raise ValueError(
                'String array contains non-ASCII characters that are not supported by VTK.'
            )
        vtkarr = _vtk.vtkStringArray()
        ########### OPTIMIZE ###########
        for val in arr:
            vtkarr.InsertNextValue(val)
        ################################
        if isinstance(name, str):
            vtkarr.SetName(name)
        return vtkarr
    # Otherwise it is a vtk array and needs to be converted back to numpy
    ############### OPTIMIZE ###############
    nvalues = arr.GetNumberOfValues()
    return np.array([arr.GetValue(i) for i in range(nvalues)], dtype='|U')
    ########################################


def convert_array(arr, name=None, deep=False, array_type=None):
    """Convert a NumPy array to a vtkDataArray or vice versa.

    Parameters
    ----------
    arr : np.ndarray or vtkDataArray
        A numpy array or vtkDataArry to convert.
    name : str, optional
        The name of the data array for VTK.
    deep : bool, optional
        If input is numpy array then deep copy values.
    array_type : int, optional
        VTK array type ID as specified in specified in ``vtkType.h``.

    Returns
    -------
    vtkDataArray, numpy.ndarray, or DataFrame
        The converted array.  If input is a :class:`numpy.ndarray` then
        returns ``vtkDataArray`` or is input is ``vtkDataArray`` then
        returns NumPy ``ndarray``.

    """
    if arr is None:
        return
    if isinstance(arr, (list, tuple)):
        arr = np.array(arr)
    if isinstance(arr, np.ndarray):
        if arr.dtype == np.dtype('O'):
            arr = arr.astype('|S')
        arr = np.ascontiguousarray(arr)
        if arr.dtype.type in (np.str_, np.bytes_):
            # This handles strings
            vtk_data = convert_string_array(arr)
        else:
            # This will handle numerical data
            arr = np.ascontiguousarray(arr)
            vtk_data = _vtk.numpy_to_vtk(num_array=arr, deep=deep, array_type=array_type)
        if isinstance(name, str):
            vtk_data.SetName(name)
        return vtk_data
    # Otherwise input must be a vtkDataArray
    if not isinstance(arr, (_vtk.vtkDataArray, _vtk.vtkBitArray, _vtk.vtkStringArray)):
        raise TypeError(f'Invalid input array type ({type(arr)}).')
    # Handle booleans
    if isinstance(arr, _vtk.vtkBitArray):
        arr = vtk_bit_array_to_char(arr)
    # Handle string arrays
    if isinstance(arr, _vtk.vtkStringArray):
        return convert_string_array(arr)
    # Convert from vtkDataArry to NumPy
    return _vtk.vtk_to_numpy(arr)


def is_pyvista_dataset(obj):
    """Return ``True`` if the object is a PyVista wrapped dataset.

    Parameters
    ----------
    obj : anything
        Any object to test.

    Returns
    -------
    bool
        ``True`` when the object is a :class:`pyvista.DataSet`.

    """
    return isinstance(obj, (pyvista.DataSet, pyvista.MultiBlock))


def _assoc_array(obj, name, association='point'):
    """Return a point, cell, or field array from a pyvista.DataSet or VTK object.

    If the array or index doesn't exist, return nothing. This matches VTK's
    behavior when using ``GetAbstractArray`` with an invalid key or index.

    """
    vtk_attr = f'Get{association.title()}Data'
    python_attr = f'{association.lower()}_data'

    if isinstance(obj, pyvista.DataSet):
        try:
            return getattr(obj, python_attr).get_array(name)
        except KeyError:  # pragma: no cover
            return None
    abstract_array = getattr(obj, vtk_attr)().GetAbstractArray(name)
    if abstract_array is not None:
        return pyvista.pyvista_ndarray(abstract_array)
    return None


def point_array(obj, name):
    """Return point array of a pyvista or vtk object.

    Parameters
    ----------
    obj : pyvista.DataSet or vtk.vtkDataSet
        PyVista or VTK dataset.

    name : str or int
        Name or index of the array.

    Returns
    -------
    pyvista.pyvista_ndarray or None
        Wrapped array if the index or name is valid. Otherwise, ``None``.

    """
    return _assoc_array(obj, name, 'point')


def field_array(obj, name):
    """Return field data of a pyvista or vtk object.

    Parameters
    ----------
    obj : pyvista.DataSet or vtk.vtkDataSet
        PyVista or VTK dataset.

    name : str or int
        Name or index of the array.

    Returns
    -------
    pyvista.pyvista_ndarray or None
        Wrapped array if the index or name is valid. Otherwise, ``None``.

    """
    return _assoc_array(obj, name, 'field')


def cell_array(obj, name):
    """Return cell array of a pyvista or vtk object.

    Parameters
    ----------
    obj : pyvista.DataSet or vtk.vtkDataSet
        PyVista or VTK dataset.

    name : str or int
        Name or index of the array.

    Returns
    -------
    pyvista.pyvista_ndarray or None
        Wrapped array if the index or name is valid. Otherwise, ``None``.

    """
    return _assoc_array(obj, name, 'cell')


def row_array(obj, name):
    """Return row array of a vtk object.

    Parameters
    ----------
    obj : vtk.vtkDataSet
        PyVista or VTK dataset.

    name : str
        Name of the array.

    Returns
    -------
    numpy.ndarray
        Wrapped array.

    """
    vtkarr = obj.GetRowData().GetAbstractArray(name)
    return convert_array(vtkarr)


def parse_field_choice(field):
    """Return a field association object for a given field type string.

    Parameters
    ----------
    field : str, FieldAssociation
        Name of the field (e.g, ``'cell'``, ``'field'``, ``'point'``,
        ``'row'``).

    Returns
    -------
    pyvista.FieldAssociation
        Field association.

    """
    if isinstance(field, str):
        field = field.strip().lower()
        if field in ['cell', 'c', 'cells']:
            field = FieldAssociation.CELL
        elif field in ['point', 'p', 'points']:
            field = FieldAssociation.POINT
        elif field in ['field', 'f', 'fields']:
            field = FieldAssociation.NONE
        elif field in ['row', 'r']:
            field = FieldAssociation.ROW
        else:
            raise ValueError(f'Data field ({field}) not supported.')
    elif isinstance(field, FieldAssociation):
        pass
    else:
        raise TypeError(f'Data field ({field}) not supported.')
    return field


def get_array(mesh, name, preference='cell', err=False) -> Optional[np.ndarray]:
    """Search point, cell and field data for an array.

    Parameters
    ----------
    mesh : pyvista.DataSet
        Dataset to get the array from.

    name : str
        The name of the array to get the range.

    preference : str, optional
        When scalars is specified, this is the preferred array type to
        search for in the dataset.  Must be either ``'point'``,
        ``'cell'``, or ``'field'``.

    err : bool, optional
        Whether to throw an error if array is not present.

    Returns
    -------
    pyvista.pyvista_ndarray or ``None``
        Requested array.  Return ``None`` if there is no array
        matching the ``name`` and ``err=False``.

    """
    if isinstance(mesh, _vtk.vtkTable):
        arr = row_array(mesh, name)
        if arr is None and err:
            raise KeyError(f'Data array ({name}) not present in this dataset.')
        return arr

    if not isinstance(preference, str):
        raise TypeError('`preference` must be a string')
    if preference not in ['cell', 'point', 'field']:
        raise ValueError(
            f'`preference` must be either "cell", "point", "field" for a '
            f'{type(mesh)}, not "{preference}".'
        )

    parr = point_array(mesh, name)
    carr = cell_array(mesh, name)
    farr = field_array(mesh, name)
    preference = parse_field_choice(preference)
    if sum([array is not None for array in (parr, carr, farr)]) > 1:
        if preference == FieldAssociation.CELL:
            return carr
        elif preference == FieldAssociation.POINT:
            return parr
        else:  # must be field
            return farr

    if parr is not None:
        return parr
    elif carr is not None:
        return carr
    elif farr is not None:
        return farr
    elif err:
        raise KeyError(f'Data array ({name}) not present in this dataset.')
    return None


def get_array_association(mesh, name, preference='cell', err=False) -> FieldAssociation:
    """Return the array association.

    Parameters
    ----------
    mesh : Dataset
        Dataset to get the array association from.

    name : str
        The name of the array.

    preference : str, optional
        When scalars is specified, this is the preferred array type to
        search for in the dataset.  Must be either ``'point'``,
        ``'cell'``, or ``'field'``.

    err : bool, optional
        Boolean to control whether to throw an error if array is not
        present.

    Returns
    -------
    pyvista.FieldAssociation
        Association of the array. If array is not present and ``err`` is
        ``False``, ``FieldAssociation.NONE`` is returned.

    """
    if isinstance(mesh, _vtk.vtkTable):
        arr = row_array(mesh, name)
        if arr is None and err:
            raise KeyError(f'Data array ({name}) not present in this dataset.')
        return FieldAssociation.ROW

    # with multiple arrays, return the array preference if possible
    parr = point_array(mesh, name)
    carr = cell_array(mesh, name)
    farr = field_array(mesh, name)
    arrays = [parr, carr, farr]
    preferences = [FieldAssociation.POINT, FieldAssociation.CELL, FieldAssociation.NONE]
    preference = parse_field_choice(preference)
    if preference not in preferences:
        raise ValueError(f'Data field ({preference}) not supported.')

    matches = [pref for pref, array in zip(preferences, arrays) if array is not None]
    # optionally raise if no match
    if not matches:
        if err:
            raise KeyError(f'Data array ({name}) not present in this dataset.')
        return FieldAssociation.NONE
    # use preference if it applies
    if preference in matches:
        return preference
    # otherwise return first in order of point -> cell -> field
    return matches[0]


def vtk_points(points, deep=True, force_float=False):
    """Convert numpy array or array-like to a ``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, optional
        Perform a deep copy of the array.  Only applicable if
        ``points`` is a :class:`numpy.ndarray`.

    force_float : bool, optional
        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.

    Returns
    -------
    vtk.vtkPoints
        The vtkPoints object.

    Examples
    --------
    >>> import pyvista
    >>> import numpy as np
    >>> points = np.random.random((10, 3))
    >>> vpoints = pyvista.vtk_points(points)
    >>> vpoints  # doctest:+SKIP
    (vtkmodules.vtkCommonCore.vtkPoints)0x7f0c2e26af40

    """
    points = np.asanyarray(points)

    # verify is numeric
    if not np.issubdtype(points.dtype, np.number):
        raise TypeError('Points must be a numeric type')

    if force_float:
        if 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)

    # check dimensionality
    if points.ndim == 1:
        points = points.reshape(-1, 3)
    elif points.ndim > 2:
        raise ValueError(f'Dimension of ``points`` should be 1 or 2, not {points.ndim}')

    # verify shape
    if points.shape[1] != 3:
        raise ValueError(
            'Points array must contain three values per point. '
            f'Shape is {points.shape} and should be (X, 3)'
        )

    # 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):
    """Generate non-connected line segments from points.

    Assumes points are ordered as line segments and an even number of
    points.

    Parameters
    ----------
    points : numpy.ndarray
        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
    >>> import numpy as np
    >>> points = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0], [1, 1, 0]])
    >>> lines = pyvista.lines_from_points(points)
    >>> lines.plot()

    """
    if len(points) % 2 != 0:
        raise ValueError("An even number of points must be given to define each segment.")
    # 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


def lines_from_points(points, close=False):
    """Make a connected line set given an array of points.

    Parameters
    ----------
    points : np.ndarray
        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, optional
        If ``True``, close the line segments into a loop.

    Returns
    -------
    pyvista.PolyData
        PolyData with lines and cells.

    Examples
    --------
    >>> import numpy as np
    >>> import pyvista
    >>> points = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
    >>> poly = pyvista.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


def make_tri_mesh(points, faces):
    """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
    >>> 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 = pyvista.make_tri_mesh(points, faces)
    >>> tri_mesh.plot(show_edges=True, line_width=5)

    """
    if points.shape[1] != 3:
        raise ValueError("Points array should have shape (N, 3).")
    if faces.ndim != 2 or faces.shape[1] != 3:
        raise ValueError("Face array should have shape (M, 3).")
    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, vec):
    """Create a pyvista.PolyData object composed of vectors.

    Parameters
    ----------
    orig : numpy.ndarray
        Array of vector origins.

    vec : numpy.ndarray
        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 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
    >>> 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 = pyvista.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:
        raise ValueError('orig array must be 3D')

    if vec.ndim != 2:
        vec = vec.reshape((-1, 3))
    elif vec.shape[1] != 3:
        raise ValueError('vec array must be 3D')

    # Create vtk points and cells objects
    vpts = _vtk.vtkPoints()
    vpts.SetData(_vtk.numpy_to_vtk(np.ascontiguousarray(orig), deep=True))

    npts = orig.shape[0]
    cells = np.empty((npts, 2), dtype=pyvista.ID_TYPE)
    cells[:, 0] = 1
    cells[:, 1] = np.arange(npts, dtype=pyvista.ID_TYPE)
    vcells = pyvista.utilities.cells.CellArray(cells, npts)

    # 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)


def trans_from_matrix(matrix):  # pragma: no cover
    """Convert a vtk matrix to a numpy.ndarray.

    DEPRECATED: Please use ``array_from_vtkmatrix``.

    """
    # import needs to happen here to prevent a circular import
    from pyvista.core.errors import DeprecationError

    raise DeprecationError('DEPRECATED: Please use ``array_from_vtkmatrix``.')


def array_from_vtkmatrix(matrix):
    """Convert a vtk matrix to an array.

    Parameters
    ----------
    matrix : vtk.vtkMatrix3x3 or vtk.vtkMatrix4x4
        The vtk matrix to be converted to a ``numpy.ndarray``.
        Returned ndarray has shape (3, 3) or (4, 4) as appropriate.

    Returns
    -------
    numpy.ndarray
        Numpy array containing the data from ``matrix``.

    """
    if isinstance(matrix, _vtk.vtkMatrix3x3):
        shape = (3, 3)
    elif isinstance(matrix, _vtk.vtkMatrix4x4):
        shape = (4, 4)
    else:
        raise TypeError(
            'Expected vtk.vtkMatrix3x3 or vtk.vtkMatrix4x4 input,'
            f' got {type(matrix).__name__} instead.'
        )
    array = np.zeros(shape)
    for i in range(shape[0]):
        for j in range(shape[1]):
            array[i, j] = matrix.GetElement(i, j)
    return array


def vtkmatrix_from_array(array):
    """Convert a ``numpy.ndarray`` or array-like to a vtk matrix.

    Parameters
    ----------
    array : numpy.ndarray or array-like
        The array or array-like to be converted to a vtk matrix.
        Shape (3, 3) gets converted to a ``vtk.vtkMatrix3x3``, shape (4, 4)
        gets converted to a ``vtk.vtkMatrix4x4``. No other shapes are valid.

    Returns
    -------
    vtk.vtkMatrix3x3 or vtk.vtkMatrix4x4
        VTK matrix.

    """
    array = np.asarray(array)
    if array.shape == (3, 3):
        matrix = _vtk.vtkMatrix3x3()
    elif array.shape == (4, 4):
        matrix = _vtk.vtkMatrix4x4()
    else:
        raise ValueError(f'Invalid shape {array.shape}, must be (3, 3) or (4, 4).')
    m, n = array.shape
    for i in range(m):
        for j in range(n):
            matrix.SetElement(i, j, array[i, j])
    return matrix


def is_meshio_mesh(obj):
    """Test if passed object is instance of ``meshio.Mesh``.

    Parameters
    ----------
    obj
        Any object.

    Returns
    -------
    bool
        ``True`` if ``obj`` is an ``meshio.Mesh``.

    """
    try:
        import meshio

        return isinstance(obj, meshio.Mesh)
    except ImportError:
        return False


def wrap(dataset):
    """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.

    Parameters
    ----------
    dataset : :class:`numpy.ndarray`, :class:`trimesh.Trimesh`, or VTK object
        Dataset to wrap.

    Returns
    -------
    pyvista.DataSet
        The PyVista wrapped dataset.

    Examples
    --------
    Wrap a numpy array representing a random point cloud.

    >>> import numpy as np
    >>> import pyvista
    >>> points = np.random.random((10, 3))
    >>> cloud = pyvista.wrap(points)
    >>> cloud  # doctest:+SKIP
    PolyData (0x7fc52db83d70)
      N Cells:  10
      N Points: 10
      X Bounds: 1.123e-01, 7.457e-01
      Y Bounds: 1.009e-01, 9.877e-01
      Z Bounds: 2.346e-03, 9.640e-01
      N Arrays: 0

    Wrap a Trimesh object.

    >>> import trimesh
    >>> import pyvista
    >>> points = [[0, 0, 0], [0, 0, 1], [0, 1, 0]]
    >>> faces = [[0, 1, 2]]
    >>> tmesh = trimesh.Trimesh(points, faces=faces, process=False)
    >>> mesh = pyvista.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

    Wrap a VTK object.

    >>> import pyvista
    >>> 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 = pyvista.wrap(point)
    >>> 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

    # 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.UniformGrid(dims=dataset.shape)
            if isinstance(dataset, pyvista.pyvista_ndarray):
                # this gets rid of pesky VTK reference since we're raveling this
                dataset = np.array(dataset, copy=False)
            mesh['values'] = dataset.ravel(order='F')
            mesh.active_scalars_name = 'values'
            return mesh
        else:
            raise NotImplementedError('NumPy array could not be wrapped pyvista.')

    # 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:
            logging.warning(f'VTK data type ({key}) is not currently supported by pyvista.')
        return

    # wrap meshio
    if is_meshio_mesh(dataset):
        return from_meshio(dataset)

    # wrap trimesh
    if dataset.__class__.__name__ == 'Trimesh':
        # trimesh doesn't pad faces
        n_face = dataset.faces.shape[0]
        faces = np.empty((n_face, 4), dataset.faces.dtype)
        faces[:, 1:] = dataset.faces
        faces[:, 0] = 3
        return pyvista.PolyData(np.asarray(dataset.vertices), faces)

    # otherwise, flag tell the user we can't wrap this object
    raise NotImplementedError(f'Unable to wrap ({type(dataset)}) into a pyvista type.')


def image_to_texture(image):
    """Convert ``vtkImageData`` (:class:`pyvista.UniformGrid`) to a ``vtkTexture``.

    Parameters
    ----------
    image : pyvista.UniformGrid or vtkImageData
        Image to convert.

    Returns
    -------
    vtkTexture
        VTK texture.

    """
    return pyvista.Texture(image)


def numpy_to_texture(image):
    """Convert a NumPy image array to a vtk.vtkTexture.

    Parameters
    ----------
    image : numpy.ndarray
        Numpy image array. Texture datatype expected to be ``np.uint8``.

    Returns
    -------
    pyvista.Texture
        PyVista texture.

    Examples
    --------
    Create an all white texture.

    >>> import pyvista as pv
    >>> import numpy as np
    >>> tex_arr = np.ones((1024, 1024, 3), dtype=np.uint8) * 255
    >>> tex = pv.numpy_to_texture(tex_arr)

    """
    if image.dtype != np.uint8:
        image = image.astype(np.uint8)
        warnings.warn(
            'Expected `image` dtype to be ``np.uint8``. `image` has been copied '
            'and converted to np.uint8.',
            UserWarning,
        )

    return pyvista.Texture(image)


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
        Three item cartesian point (i.e. ``[x, y, z]``).

    bounds : sequence
        Six item bounds in the form of ``(xMin, xMax, yMin, yMax, zMin, zMax)``.

    Returns
    -------
    bool
        ``True`` when ``point`` is inside ``bounds``.

    """
    if isinstance(point, (int, float)):
        point = [point]
    if isinstance(point, (np.ndarray, collections.abc.Sequence)) and not isinstance(
        point, collections.deque
    ):
        if len(bounds) < 2 * len(point) or len(bounds) % 2 != 0:
            raise ValueError('Bounds mismatch point dimensionality')
        point = collections.deque(point)
        bounds = collections.deque(bounds)
        return is_inside_bounds(point, bounds)
    if not isinstance(point, collections.deque):
        raise TypeError(f'Unknown input data type ({type(point)}).')
    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


def fit_plane_to_points(points, return_meta=False):
    """Fit a plane to a set of points using the SVD algorithm.

    Parameters
    ----------
    points : sequence
        Size ``[N x 3]`` sequence of points to fit a plane through.

    return_meta : bool, optional
        If ``True``, also returns the center and normal used to
        generate the plane.

    Returns
    -------
    pyvista.PolyData
        Plane mesh.

    numpy.ndarray
        Plane center if ``return_meta=True``.

    numpy.ndarray
        Plane normal if ``return_meta=True``.

    Examples
    --------
    Fit a plane to a random point cloud.

    >>> import pyvista
    >>> import numpy as np
    >>> cloud = np.random.random((10, 3))
    >>> cloud[:, 2] *= 0.1
    >>> plane, center, normal = pyvista.fit_plane_to_points(cloud, return_meta=True)

    Plot the fitted plane.

    >>> pl = pyvista.Plotter()
    >>> _ = pl.add_mesh(plane, color='tan', style='wireframe', line_width=4)
    >>> _ = pl.add_points(cloud, render_points_as_spheres=True,
    ...                   color='r', point_size=30)
    >>> pl.show()

    """
    data = np.array(points)
    center = data.mean(axis=0)
    result = np.linalg.svd(data - center)
    normal = np.cross(result[2][0], result[2][1])
    plane = pyvista.Plane(center=center, direction=normal)
    if return_meta:
        return plane, center, normal
    return plane


def raise_not_matching(scalars, dataset):
    """Raise exception about inconsistencies.

    Parameters
    ----------
    scalars : numpy.ndarray
        Array of scalars.

    dataset : pyvista.DataSet
        Dataset to check against.

    Raises
    ------
    ValueError
        Raises a ValueError if the size of scalars does not the dataset.
    """
    if isinstance(dataset, _vtk.vtkTable):
        raise ValueError(
            f'Number of scalars ({scalars.shape[0]}) must match number of rows ({dataset.n_rows}).'
        )
    raise ValueError(
        f'Number of scalars ({scalars.shape[0]}) '
        f'must match either the number of points ({dataset.n_points}) '
        f'or the number of cells ({dataset.n_cells}).'
    )


def generate_plane(normal, origin):
    """Return a _vtk.vtkPlane.

    Parameters
    ----------
    normal : sequence
        Three item sequence representing the normal of the plane.

    origin : sequence
        Three item sequence representing the origin of the plane.

    Returns
    -------
    vtk.vtkPlane
        VTK plane.

    """
    plane = _vtk.vtkPlane()
    # NORMAL MUST HAVE MAGNITUDE OF 1
    normal = normal / np.linalg.norm(normal)
    plane.SetNormal(normal)
    plane.SetOrigin(origin)
    return plane


def try_callback(func, *args):
    """Wrap a given callback in a try statement.

    Parameters
    ----------
    func : callable
        Callable object.

    *args
        Any arguments.

    """
    try:
        func(*args)
    except Exception:
        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')
        logging.warning(formatted_exception)


def check_depth_peeling(number_of_peels=100, occlusion_ratio=0.0):
    """Check if depth peeling is available.

    Attempts to use depth peeling to see if it is available for the
    current environment. Returns ``True`` if depth peeling is
    available and has been successfully leveraged, otherwise
    ``False``.

    Parameters
    ----------
    number_of_peels : int, optional
        Maximum number of depth peels.

    occlusion_ratio : float, optional
        Occlusion ratio.

    Returns
    -------
    bool
        ``True`` when system supports depth peeling with the specified
        settings.

    """
    # Try Depth Peeling with a basic scene
    source = _vtk.vtkSphereSource()
    mapper = _vtk.vtkPolyDataMapper()
    mapper.SetInputConnection(source.GetOutputPort())
    actor = _vtk.vtkActor()
    actor.SetMapper(mapper)
    # requires opacity < 1
    actor.GetProperty().SetOpacity(0.5)
    renderer = _vtk.vtkRenderer()
    renderWindow = _vtk.vtkRenderWindow()
    renderWindow.AddRenderer(renderer)
    renderWindow.SetOffScreenRendering(True)
    renderWindow.SetAlphaBitPlanes(True)
    renderWindow.SetMultiSamples(0)
    renderer.AddActor(actor)
    renderer.SetUseDepthPeeling(True)
    renderer.SetMaximumNumberOfPeels(number_of_peels)
    renderer.SetOcclusionRatio(occlusion_ratio)
    renderWindow.Render()
    return renderer.GetLastRenderingUsedDepthPeeling() == 1


def threaded(fn):
    """Call a function using a thread.

    Parameters
    ----------
    fn : callable
        Callable object.

    Returns
    -------
    function
        Wrapped function.

    """

    def wrapper(*args, **kwargs):
        thread = Thread(target=fn, args=args, kwargs=kwargs)
        thread.start()
        return thread

    return wrapper


class conditional_decorator:
    """Conditional decorator for methods.

    Parameters
    ----------
    dec
        Decorator
    condition
        Condition to match.

    """

    def __init__(self, dec, condition):
        """Initialize."""
        self.decorator = dec
        self.condition = condition

    def __call__(self, func):
        """Call the decorated function if condition is matched."""
        if not self.condition:
            # Return the function unchanged, not decorated.
            return func
        return self.decorator(func)


class ProgressMonitor:
    """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, optional
        Message to display in the progress bar.

    scaling : float, optional
        Unused keyword argument.

    """

    def __init__(self, algorithm, message="", scaling=100):
        """Initialize observer."""
        try:
            from tqdm import tqdm  # noqa
        except ImportError:
            raise ImportError("Please install `tqdm` to monitor algorithms.")
        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):
        """Pass signal to custom interrupt handler."""
        self._interrupt_signal_received = (sig, frame)
        logging.debug('SIGINT received. Delaying KeyboardInterrupt until VTK algorithm finishes.')

    def __call__(self, obj, event, *args):
        """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)
            self._old_progress = progress

    def __enter__(self):
        """Enter event for ``with`` context."""
        from tqdm import tqdm

        # 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, type, value, traceback):
        """Exit event for ``with`` context."""
        self._progress_bar.total = 1
        self._progress_bar.refresh()
        self._progress_bar.close()
        self.algorithm.RemoveObservers(self.event_type)
        if threading.current_thread().__class__.__name__ == '_MainThread':
            signal.signal(signal.SIGINT, self._old_handler)


def abstract_class(cls_):
    """Decorate a class, overriding __new__.

    Preventing a class from being instantiated similar to abc.ABCMeta
    but does not require an abstract method.
    """

    def __new__(cls, *args, **kwargs):
        if cls is cls_:
            raise TypeError(f'{cls.__name__} is an abstract class and may not be instantiated.')
        return object.__new__(cls)

    cls_.__new__ = __new__
    return cls_


def axis_rotation(points, angle, inplace=False, deg=True, 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, optional
        Updates points in-place while returning nothing.

    deg : bool, optional
        If ``True``, the angle is interpreted as degrees instead of
        radians. Default is ``True``.

    axis : str, optional
        Name of axis to rotate about. Valid options are ``'x'``, ``'y'``,
        and ``'z'``. Default value is ``'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
    >>> from pyvista import examples
    >>> points = examples.load_airplane().points
    >>> points_orig = points.copy()
    >>> pyvista.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:
        raise ValueError('Invalid axis. Must be either "x", "y", or "z"')

    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 cubemap(path='', prefix='', ext='.jpg'):
    """Construct a cubemap from 6 images from a directory.

    Each of the 6 images must be in the following format:

    - <prefix>negx<ext>
    - <prefix>negy<ext>
    - <prefix>negz<ext>
    - <prefix>posx<ext>
    - <prefix>posy<ext>
    - <prefix>posz<ext>

    Prefix may be empty, and extension will default to ``'.jpg'``

    For example, if you have 6 images with the skybox2 prefix:

    - ``'skybox2-negx.jpg'``
    - ``'skybox2-negy.jpg'``
    - ``'skybox2-negz.jpg'``
    - ``'skybox2-posx.jpg'``
    - ``'skybox2-posy.jpg'``
    - ``'skybox2-posz.jpg'``

    Parameters
    ----------
    path : str, optional
        Directory containing the cubemap images.

    prefix : str, optional
        Prefix to the filename.

    ext : str, optional
        The filename extension.  For example ``'.jpg'``.

    Returns
    -------
    pyvista.Texture
        Texture with cubemap.

    Notes
    -----
    Cubemap will appear flipped relative to the XY plane between VTK v9.1 and
    VTK v9.2.

    Examples
    --------
    Load a skybox given a directory, prefix, and file extension.

    >>> import pyvista
    >>> skybox = pyvista.cubemap('my_directory', 'skybox', '.jpeg')  # doctest:+SKIP

    """
    sets = ['posx', 'negx', 'posy', 'negy', 'posz', 'negz']
    image_paths = [os.path.join(path, f'{prefix}{suffix}{ext}') for suffix in sets]
    return _cubemap_from_paths(image_paths)


def cubemap_from_filenames(image_paths):
    """Construct a cubemap from 6 images.

    Images must be in the following order:

    - Positive X
    - Negative X
    - Positive Y
    - Negative Y
    - Positive Z
    - Negative Z

    Parameters
    ----------
    image_paths : list
        Paths of the individual cubemap images.

    Returns
    -------
    pyvista.Texture
        Texture with cubemap.

    Examples
    --------
    Load a skybox given a list of image paths.

    >>> image_paths = [
    ...     '/home/user/_px.jpg',
    ...     '/home/user/_nx.jpg',
    ...     '/home/user/_py.jpg',
    ...     '/home/user/_ny.jpg',
    ...     '/home/user/_pz.jpg',
    ...     '/home/user/_nz.jpg',
    ... ]
    >>> skybox = pyvista.cubemap(image_paths=image_paths)  # doctest:+SKIP

    """
    if len(image_paths) != 6:
        raise ValueError("image_paths must contain 6 paths")

    return _cubemap_from_paths(image_paths)


def _cubemap_from_paths(image_paths):
    """Construct a cubemap from image paths."""
    for image_path in image_paths:
        if not os.path.isfile(image_path):
            file_str = '\n'.join(image_paths)
            raise FileNotFoundError(
                f'Unable to locate {image_path}\n'
                'Expected to find the following files:\n'
                f'{file_str}'
            )

    texture = pyvista.Texture()
    texture.SetMipmap(True)
    texture.SetInterpolate(True)
    texture.cube_map = True  # Must be set prior to setting images

    # add each image to the cubemap
    for i, fn in enumerate(image_paths):
        image = pyvista.read(fn)
        flip = _vtk.vtkImageFlip()
        flip.SetInputDataObject(image)
        flip.SetFilteredAxis(1)  # flip y axis
        flip.Update()
        texture.SetInputDataObject(i, flip.GetOutput())

    return texture


def set_default_active_vectors(mesh: 'pyvista.DataSet') -> None:
    """Set a default vectors array on mesh, if not already set.

    If an active vector already exists, no changes are made.

    If an active vectors does not exist, it checks for possibly cell
    or point arrays with shape ``(n, 3)``.  If only one exists, then
    it is set as the active vectors.  Otherwise, an error is raised.

    Parameters
    ----------
    mesh : pyvista.DataSet
        Dataset to set default active vectors.

    Raises
    ------
    MissingDataError
        If no vector-like arrays exist.

    AmbiguousDataError
        If more than one vector-like arrays exist.

    """
    if mesh.active_vectors_name is not None:
        return

    point_data = mesh.point_data
    cell_data = mesh.cell_data

    possible_vectors_point = [
        name for name, value in point_data.items() if value.ndim == 2 and value.shape[1] == 3
    ]
    possible_vectors_cell = [
        name for name, value in cell_data.items() if value.ndim == 2 and value.shape[1] == 3
    ]

    possible_vectors = possible_vectors_point + possible_vectors_cell
    n_possible_vectors = len(possible_vectors)

    if n_possible_vectors == 1:
        if len(possible_vectors_point) == 1:
            preference = 'point'
        else:
            preference = 'cell'
        mesh.set_active_vectors(possible_vectors[0], preference=preference)
    elif n_possible_vectors < 1:
        raise MissingDataError("No vector-like data available.")
    elif n_possible_vectors > 1:
        raise AmbiguousDataError(
            "Multiple vector-like data available\n"
            f"cell data: {possible_vectors_cell}.\n"
            f"point data: {possible_vectors_point}.\n"
            "Set one as active using DataSet.set_active_vectors(name, preference=type)"
        )


def set_default_active_scalars(mesh: 'pyvista.DataSet') -> None:
    """Set a default scalars array on mesh, if not already set.

    If an active scalars already exists, no changes are made.

    If an active scalars does not exist, it checks for point or cell
    arrays.  If only one exists, then it is set as the active scalars.
    Otherwise, an error is raised.

    Parameters
    ----------
    mesh : pyvista.DataSet
        Dataset to set default active scalars.

    Raises
    ------
    MissingDataError
        If no arrays exist.

    AmbiguousDataError
        If more than one array exists.

    """
    if mesh.active_scalars_name is not None:
        return

    point_data = mesh.point_data
    cell_data = mesh.cell_data

    possible_scalars_point = point_data.keys()
    possible_scalars_cell = cell_data.keys()

    possible_scalars = possible_scalars_point + possible_scalars_cell
    n_possible_scalars = len(possible_scalars)

    if n_possible_scalars == 1:
        if len(possible_scalars_point) == 1:
            preference = 'point'
        else:
            preference = 'cell'
        mesh.set_active_scalars(possible_scalars[0], preference=preference)
    elif n_possible_scalars < 1:
        raise MissingDataError("No data available.")
    elif n_possible_scalars > 1:
        raise AmbiguousDataError(
            "Multiple data available\n"
            f"cell data: {possible_scalars_cell}.\n"
            f"point data: {possible_scalars_point}.\n"
            "Set one as active using DataSet.set_active_scalars(name, preference=type)"
        )


# pyvista/utilities/misc.py

"""Miscellaneous pyvista functions."""
from collections import namedtuple
from functools import lru_cache
import importlib
import os
import warnings

import numpy as np

import pyvista
from pyvista import _vtk


def _set_plot_theme_from_env():
    """Set plot theme from an environment variable."""
    from pyvista.themes import _ALLOWED_THEMES, set_plot_theme

    if 'PYVISTA_PLOT_THEME' in os.environ:
        try:
            theme = os.environ['PYVISTA_PLOT_THEME']
            set_plot_theme(theme.lower())
        except KeyError:
            allowed = ', '.join([item.name for item in _ALLOWED_THEMES])
            warnings.warn(
                f'\n\nInvalid PYVISTA_PLOT_THEME environment variable "{theme}". '
                f'Should be one of the following: {allowed}'
            )


@lru_cache(maxsize=None)
def has_module(module_name):
    """Return if a module can be imported."""
    module_spec = importlib.util.find_spec(module_name)
    return module_spec is not None


def raise_has_duplicates(arr):
    """Raise a ValueError if an array is not unique."""
    if has_duplicates(arr):
        raise ValueError("Array contains duplicate values.")


def has_duplicates(arr):
    """Return if an array has any duplicates."""
    s = np.sort(arr, axis=None)
    return (s[1:] == s[:-1]).any()


def _get_vtk_id_type():
    """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 PyVistaDeprecationWarning(Warning):
    """Non-supressed Depreciation Warning."""

    pass


class PyVistaFutureWarning(Warning):
    """Non-supressed Future Warning."""

    pass


class PyVistaEfficiencyWarning(Warning):
    """Efficiency warning."""

    pass


def VTKVersionInfo():
    """Return the vtk version as a namedtuple."""
    version_info = namedtuple('VTKVersionInfo', ['major', 'minor', 'micro'])

    try:
        ver = _vtk.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 version_info(major, minor, micro)


def uses_egl() -> bool:
    """Check if VTK has been compiled with EGL support via OSMesa."""
    ren_win_str = str(type(_vtk.vtkRenderWindow()))
    return 'EGL' in ren_win_str or 'OSOpenGL' in ren_win_str


def copy_vtk_array(array, deep=True):
    """Create a deep or shallow copy of a VTK array.

    Parameters
    ----------
    array : vtk.vtkDataArray or vtk.vtkAbstractArray
        VTK array.

    deep : bool, optional
        When ``True``, create a deep copy of the array. When ``False``, returns
        a shallow copy.

    Returns
    -------
    vtk.vtkDataArray or vtk.vtkAbstractArray
        Copy of the original VTK array.

    Examples
    --------
    Perform a deep copy of a vtk array.

    >>> import vtk
    >>> import pyvista
    >>> arr = vtk.vtkFloatArray()
    >>> _ = arr.SetNumberOfValues(10)
    >>> arr.SetValue(0, 1)
    >>> arr_copy = pyvista.utilities.misc.copy_vtk_array(arr)
    >>> arr_copy.GetValue(0)
    1.0

    """
    if not isinstance(array, (_vtk.vtkDataArray, _vtk.vtkAbstractArray)):
        raise TypeError(f"Invalid type {type(array)}.")

    new_array = type(array)()
    if deep:
        new_array.DeepCopy(array)
    else:
        new_array.ShallowCopy(array)

    return new_array


def can_create_mpl_figure():  # pragma: no cover
    """Return if a figure can be created with matplotlib."""
    try:
        import matplotlib.pyplot as plt

        figure = plt.figure()
        plt.close(figure)
        return True
    except:
        return False


vtk_version_info = VTKVersionInfo()


def set_pickle_format(format: str):
    """Set the format used to serialize :class:`pyvista.DataObject` when pickled."""
    supported = {'xml', 'legacy'}
    format = format.lower()
    if format not in supported:
        raise ValueError(
            f'Unsupported pickle format `{format}`. Valid options are `{"`, `".join(supported)}`.'
        )
    pyvista.PICKLE_FORMAT = format


# pyvista/utilities/parametric_objects.py

"""Module managing parametric objects."""

from math import pi

import numpy as np

import pyvista
from pyvista import _vtk
from pyvista.utilities.errors import check_valid_vector

from .geometric_objects import translate


def Spline(points, n_points=None):
    """Create a spline from points.

    Parameters
    ----------
    points : np.ndarray
        Array of points to build a spline out of.  Array must be 3D
        and directionally ordered.

    n_points : int, optional
        Number of points to interpolate along the points array.

    Returns
    -------
    pyvista.PolyData
        Line mesh of spline.

    Examples
    --------
    Construct a spline.

    >>> import numpy as np
    >>> import pyvista as pv
    >>> theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
    >>> z = np.linspace(-2, 2, 100)
    >>> r = z**2 + 1
    >>> x = r * np.sin(theta)
    >>> y = r * np.cos(theta)
    >>> points = np.column_stack((x, y, z))
    >>> spline = pv.Spline(points, 1000)
    >>> spline.plot(render_lines_as_tubes=True, line_width=10, show_scalar_bar=False)

    """
    spline_function = _vtk.vtkParametricSpline()
    spline_function.SetPoints(pyvista.vtk_points(points, False))

    # get interpolation density
    u_res = n_points
    if u_res is None:
        u_res = points.shape[0]

    u_res -= 1
    spline = surface_from_para(spline_function, u_res)
    return spline.compute_arc_length()


def KochanekSpline(points, tension=None, bias=None, continuity=None, n_points=None):
    """Create a Kochanek spline from points.

    Parameters
    ----------
    points : sequence
        Array of points to build a Kochanek spline out of.  Array must
        be 3D and directionally ordered.

    tension : sequence, optional
        Changes the length of the tangent vector.  Defaults to ``[0.0,
        0.0, 0.0]``.

    bias : sequence, optional
        Primarily changes the direction of the tangent vector.
        Defaults to ``[0.0, 0.0, 0.0]``.

    continuity : sequence, optional
        Changes the sharpness in change between tangents.  Defaults to
        ``[0.0, 0.0, 0.0]``.

    n_points : int, optional
        Number of points on the spline.  Defaults to the number of
        points in ``points``.

    Returns
    -------
    pyvista.PolyData
        Kochanek spline.

    Examples
    --------
    Construct a Kochanek spline.

    >>> import numpy as np
    >>> import pyvista as pv
    >>> theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
    >>> z = np.linspace(-2, 2, 100)
    >>> r = z ** 2 + 1
    >>> x = r * np.sin(theta)
    >>> y = r * np.cos(theta)
    >>> points = np.column_stack((x, y, z))
    >>> kochanek_spline = pv.KochanekSpline(points, n_points=6)
    >>> kochanek_spline.plot(line_width=4, color="k")

    See :ref:`create_kochanek_spline_example` for an additional example.

    """
    if tension is None:
        tension = np.array([0.0, 0.0, 0.0])
    check_valid_vector(tension, "tension")
    if not np.all(np.abs(tension) <= 1.0):
        raise ValueError(
            "The absolute value of all values of the tension array elements must be <= 1.0 "
        )

    if bias is None:
        bias = np.array([0.0, 0.0, 0.0])
    check_valid_vector(bias, "bias")
    if not np.all(np.abs(bias) <= 1.0):
        raise ValueError(
            "The absolute value of all values of the bias array elements must be <= 1.0 "
        )

    if continuity is None:
        continuity = np.array([0.0, 0.0, 0.0])
    check_valid_vector(continuity, "continuity")
    if not np.all(np.abs(continuity) <= 1.0):
        raise ValueError(
            "The absolute value of all values continuity array elements must be <= 1.0 "
        )

    spline_function = _vtk.vtkParametricSpline()
    spline_function.SetPoints(pyvista.vtk_points(points, False))

    # set Kochanek spline for each direction
    xspline = _vtk.vtkKochanekSpline()
    yspline = _vtk.vtkKochanekSpline()
    zspline = _vtk.vtkKochanekSpline()
    xspline.SetDefaultBias(bias[0])
    yspline.SetDefaultBias(bias[1])
    zspline.SetDefaultBias(bias[2])
    xspline.SetDefaultTension(tension[0])
    yspline.SetDefaultTension(tension[1])
    zspline.SetDefaultTension(tension[2])
    xspline.SetDefaultContinuity(continuity[0])
    yspline.SetDefaultContinuity(continuity[1])
    zspline.SetDefaultContinuity(continuity[2])
    spline_function.SetXSpline(xspline)
    spline_function.SetYSpline(yspline)
    spline_function.SetZSpline(zspline)

    # get interpolation density
    u_res = n_points
    if u_res is None:
        u_res = points.shape[0]

    u_res -= 1
    spline = surface_from_para(spline_function, u_res)
    return spline.compute_arc_length()


def ParametricBohemianDome(a=None, **kwargs):
    """Generate a Bohemian dome surface.

    Parameters
    ----------
    a : float, optional
        Bohemian dome surface parameter.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricBohemianDome surface.

    Examples
    --------
    Create a ParametricBohemianDome mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricBohemianDome()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricBohemianDome()
    if a is not None:
        parametric_function.SetA(a)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricBour(**kwargs):
    """Generate Bour's minimal surface.

    Parameters
    ----------
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricBour surface.

    Examples
    --------
    Create a ParametricBour mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricBour()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricBour()

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricBoy(zscale=None, **kwargs):
    """Generate Boy's surface.

    This is a model of the projective plane without singularities.  It
    was found by Werner Boy on assignment from David Hilbert.

    For further information about this surface, please consult the
    technical description "Parametric surfaces" in the
    "VTK Technical Documents" section in the VTK.org web pages.

    Parameters
    ----------
    zscale : float, optional
        The scale factor for the z-coordinate.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricBoy surface.

    Examples
    --------
    Create a ParametricBoy mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricBoy()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricBoy()
    if zscale is not None:
        parametric_function.SetZScale(zscale)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricCatalanMinimal(**kwargs):
    """Generate Catalan's minimal surface.

    ParametricCatalanMinimal generates Catalan's minimal surface
    parametrically. This minimal surface contains the cycloid as a
    geodesic.

    Parameters
    ----------
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricCatalanMinimal surface.

    Examples
    --------
    Create a ParametricCatalanMinimal mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricCatalanMinimal()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricCatalanMinimal()

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricConicSpiral(a=None, b=None, c=None, n=None, **kwargs):
    """Generate conic spiral surfaces that resemble sea-shells.

    ParametricConicSpiral generates conic spiral surfaces. These can
    resemble sea shells, or may look like a torus "eating" its own
    tail.

    Parameters
    ----------
    a : float, optional
        The scale factor.
        Default is 0.2.

    b : float, optional
        The A function coefficient.
        See the definition in Parametric surfaces referred to above.
        Default is 1.

    c : float, optional
        The B function coefficient.
        See the definition in Parametric surfaces referred to above.
        Default is 0.1.

    n : float, optional
        The C function coefficient.
        See the definition in Parametric surfaces referred to above.
        Default is 2.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricConicSpiral surface.

    Examples
    --------
    Create a ParametricConicSpiral mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricConicSpiral()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricConicSpiral()
    if a is not None:
        parametric_function.SetA(a)

    if b is not None:
        parametric_function.SetB(b)

    if c is not None:
        parametric_function.SetC(c)

    if n is not None:
        parametric_function.SetN(n)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricCrossCap(**kwargs):
    """Generate a cross-cap.

    ParametricCrossCap generates a cross-cap which is a non-orientable
    self-intersecting single-sided surface.  This is one possible
    image of a projective plane in three-space.

    Parameters
    ----------
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricCrossCap surface.

    Examples
    --------
    Create a ParametricCrossCap mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricCrossCap()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricCrossCap()

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricDini(a=None, b=None, **kwargs):
    """Generate Dini's surface.

    ParametricDini generates Dini's surface.  Dini's surface is a
    surface that possesses constant negative Gaussian curvature

    Parameters
    ----------
    a : float, optional
        The scale factor.  See the definition in Parametric surfaces
        referred to above.  Default is 1.

    b : float, optional
        The scale factor.  See the definition in Parametric surfaces
        referred to above.  Default is 0.2.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricDini surface.

    Examples
    --------
    Create a ParametricDini mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricDini()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricDini()
    if a is not None:
        parametric_function.SetA(a)

    if b is not None:
        parametric_function.SetB(b)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricEllipsoid(xradius=None, yradius=None, zradius=None, **kwargs):
    """Generate an ellipsoid.

    ParametricEllipsoid generates an ellipsoid.  If all the radii are
    the same, we have a sphere.  An oblate spheroid occurs if RadiusX
    = RadiusY > RadiusZ.  Here the Z-axis forms the symmetry axis. To
    a first approximation, this is the shape of the earth.  A prolate
    spheroid occurs if RadiusX = RadiusY < RadiusZ.

    Parameters
    ----------
    xradius : float, optional
        The scaling factor for the x-axis. Default is 1.

    yradius : float, optional
        The scaling factor for the y-axis. Default is 1.

    zradius : float, optional
        The scaling factor for the z-axis. Default is 1.

    **kwargs : dict, optional
        See :func:`surface_from_para` and :func:`parametric_keywords`
        for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricEllipsoid surface.

    Examples
    --------
    Create a ParametricEllipsoid mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricEllipsoid()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricEllipsoid()
    parametric_keywords(
        parametric_function,
        min_u=kwargs.pop("min_u", 0),
        max_u=kwargs.pop("max_u", 2 * pi),
        min_v=kwargs.pop("min_v", 0.0),
        max_v=kwargs.pop("max_v", pi),
        join_u=kwargs.pop("join_u", False),
        join_v=kwargs.pop("join_v", False),
        twist_u=kwargs.pop("twist_u", False),
        twist_v=kwargs.pop("twist_v", False),
        clockwise=kwargs.pop("clockwise", True),
    )

    if xradius is not None:
        parametric_function.SetXRadius(xradius)

    if yradius is not None:
        parametric_function.SetYRadius(yradius)

    if zradius is not None:
        parametric_function.SetZRadius(zradius)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricEnneper(**kwargs):
    """Generate Enneper's surface.

    ParametricEnneper generates Enneper's surface.  Enneper's surface
    is a self-intersecting minimal surface possessing constant
    negative Gaussian curvature.

    Parameters
    ----------
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricEnneper surface.

    Examples
    --------
    Create a ParametricEnneper mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricEnneper()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricEnneper()

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricFigure8Klein(radius=None, **kwargs):
    """Generate a figure-8 Klein bottle.

    ParametricFigure8Klein generates a figure-8 Klein bottle.  A Klein
    bottle is a closed surface with no interior and only one surface.
    It is unrealisable in 3 dimensions without intersecting surfaces.

    Parameters
    ----------
    radius : float, optional
        The radius of the bottle. Default is 1.
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricFigure8Klein surface.

    Examples
    --------
    Create a ParametricFigure8Klein mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricFigure8Klein()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricFigure8Klein()
    if radius is not None:
        parametric_function.SetRadius(radius)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricHenneberg(**kwargs):
    """Generate Henneberg's minimal surface.

    Parameters
    ----------
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricHenneberg surface.

    Examples
    --------
    Create a ParametricHenneberg mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricHenneberg()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricHenneberg()

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricKlein(**kwargs):
    """Generate a "classical" representation of a Klein bottle.

    ParametricKlein generates a "classical" representation of a Klein
    bottle.  A Klein bottle is a closed surface with no interior and only one
    surface.  It is unrealisable in 3 dimensions without intersecting
    surfaces.

    Parameters
    ----------
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricKlein surface.

    Examples
    --------
    Create a ParametricKlein mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricKlein()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricKlein()

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricKuen(deltav0=None, **kwargs):
    """Generate Kuens' surface.

    ParametricKuen generates Kuens' surface. This surface has a constant
    negative gaussian curvature.

    Parameters
    ----------
    deltav0 : float, optional
        The value to use when ``V == 0``.
        Default is 0.05, giving the best appearance with the default settings.
        Setting it to a value less than 0.05 extrapolates the surface
        towards a pole in the -z direction.
        Setting it to 0 retains the pole whose z-value is -inf.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricKuen surface.

    Examples
    --------
    Create a ParametricKuen mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricKuen()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricKuen()
    if deltav0 is not None:
        parametric_function.SetDeltaV0(deltav0)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricMobius(radius=None, **kwargs):
    """Generate a Mobius strip.

    Parameters
    ----------
    radius : float, optional
        The radius of the Mobius strip. Default is 1.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricMobius surface.

    Examples
    --------
    Create a ParametricMobius mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricMobius()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricMobius()
    if radius is not None:
        parametric_function.SetRadius(radius)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricPluckerConoid(n=None, **kwargs):
    """Generate Plucker's conoid surface.

    ParametricPluckerConoid generates Plucker's conoid surface
    parametrically.  Plucker's conoid is a ruled surface, named after
    Julius Plucker. It is possible to set the number of folds in this
    class via the parameter 'n'.

    Parameters
    ----------
    n : int, optional
        This is the number of folds in the conoid.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricPluckerConoid surface.

    Examples
    --------
    Create a ParametricPluckerConoid mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricPluckerConoid()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricPluckerConoid()
    if n is not None:
        parametric_function.SetN(n)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricPseudosphere(**kwargs):
    """Generate a pseudosphere.

    ParametricPseudosphere generates a parametric pseudosphere. The
    pseudosphere is generated as a surface of revolution of the
    tractrix about it's asymptote, and is a surface of constant
    negative Gaussian curvature.

    Parameters
    ----------
    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricPseudosphere surface.

    Examples
    --------
    Create a ParametricPseudosphere mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricPseudosphere()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricPseudosphere()

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricRandomHills(
    numberofhills=None,
    hillxvariance=None,
    hillyvariance=None,
    hillamplitude=None,
    randomseed=None,
    xvariancescalefactor=None,
    yvariancescalefactor=None,
    amplitudescalefactor=None,
    **kwargs,
):
    """Generate a surface covered with randomly placed hills.

    ParametricRandomHills generates a surface covered with randomly
    placed hills. Hills will vary in shape and height since the
    presence of nearby hills will contribute to the shape and height
    of a given hill.  An option is provided for placing hills on a
    regular grid on the surface.  In this case the hills will all have
    the same shape and height.

    Parameters
    ----------
    numberofhills : int, optional
        The number of hills.
        Default is 30.

    hillxvariance : float, optional
        The hill variance in the x-direction.
        Default is 2.5.

    hillyvariance : float, optional
        The hill variance in the y-direction.
        Default is 2.5.

    hillamplitude : float, optional
        The hill amplitude (height).
        Default is 2.

    randomseed : int, optional
        The Seed for the random number generator,
        a value of 1 will initialize the random number generator,
        a negative value will initialize it with the system time.
        Default is 1.

    xvariancescalefactor : float, optional
        The scaling factor for the variance in the x-direction.
        Default is 13.

    yvariancescalefactor : float, optional
        The scaling factor for the variance in the y-direction.
        Default is 13.

    amplitudescalefactor : float, optional
        The scaling factor for the amplitude.
        Default is 13.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricRandomHills surface.

    Examples
    --------
    Create a ParametricRandomHills mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricRandomHills()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricRandomHills()
    if numberofhills is not None:
        parametric_function.SetNumberOfHills(numberofhills)

    if hillxvariance is not None:
        parametric_function.SetHillXVariance(hillxvariance)

    if hillyvariance is not None:
        parametric_function.SetHillYVariance(hillyvariance)

    if hillamplitude is not None:
        parametric_function.SetHillAmplitude(hillamplitude)

    if randomseed is not None:
        parametric_function.SetRandomSeed(randomseed)

    if xvariancescalefactor is not None:
        parametric_function.SetXVarianceScaleFactor(xvariancescalefactor)

    if yvariancescalefactor is not None:
        parametric_function.SetYVarianceScaleFactor(yvariancescalefactor)

    if amplitudescalefactor is not None:
        parametric_function.SetAmplitudeScaleFactor(amplitudescalefactor)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricRoman(radius=None, **kwargs):
    """Generate Steiner's Roman Surface.

    Parameters
    ----------
    radius : float, optional
        The radius. Default is 1.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricRoman surface.

    Examples
    --------
    Create a ParametricRoman mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricRoman()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricRoman()
    if radius is not None:
        parametric_function.SetRadius(radius)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricSuperEllipsoid(xradius=None, yradius=None, zradius=None, n1=None, n2=None, **kwargs):
    """Generate a superellipsoid.

    ParametricSuperEllipsoid generates a superellipsoid.  A superellipsoid
    is a versatile primitive that is controlled by two parameters n1 and
    n2. As special cases it can represent a sphere, square box, and closed
    cylindrical can.

    Parameters
    ----------
    xradius : float, optional
        The scaling factor for the x-axis. Default is 1.

    yradius : float, optional
        The scaling factor for the y-axis. Default is 1.

    zradius : float, optional
        The scaling factor for the z-axis. Default is 1.

    n1 : float, optional
        The "squareness" parameter in the z axis.  Default is 1.

    n2 : float, optional
        The "squareness" parameter in the x-y plane. Default is 1.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricSuperEllipsoid surface.

    See Also
    --------
    pyvista.ParametricSuperToroid :
        Toroidal equivalent of ParametricSuperEllipsoid.
    pyvista.Superquadric :
        Geometric object with additional parameters.

    Examples
    --------
    Create a ParametricSuperEllipsoid surface that looks like a box
    with smooth edges.

    >>> import pyvista
    >>> mesh = pyvista.ParametricSuperEllipsoid(n1=0.02, n2=0.02)
    >>> mesh.plot(color='w', smooth_shading=True)

    Create one that looks like a spinning top.

    >>> mesh = pyvista.ParametricSuperEllipsoid(n1=4, n2=0.5)
    >>> mesh.plot(color='w', smooth_shading=True, cpos='xz')

    """
    parametric_function = _vtk.vtkParametricSuperEllipsoid()
    if xradius is not None:
        parametric_function.SetXRadius(xradius)

    if yradius is not None:
        parametric_function.SetYRadius(yradius)

    if zradius is not None:
        parametric_function.SetZRadius(zradius)

    if n1 is not None:
        parametric_function.SetN1(n1)

    if n2 is not None:
        parametric_function.SetN2(n2)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    surf = surface_from_para(parametric_function, **kwargs)

    translate(surf, center, direction)

    return surf


def ParametricSuperToroid(
    ringradius=None,
    crosssectionradius=None,
    xradius=None,
    yradius=None,
    zradius=None,
    n1=None,
    n2=None,
    **kwargs,
):
    """Generate a supertoroid.

    ParametricSuperToroid generates a supertoroid.  Essentially a
    supertoroid is a torus with the sine and cosine terms raised to a power.
    A supertoroid is a versatile primitive that is controlled by four
    parameters r0, r1, n1 and n2. r0, r1 determine the type of torus whilst
    the value of n1 determines the shape of the torus ring and n2 determines
    the shape of the cross section of the ring. It is the different values of
    these powers which give rise to a family of 3D shapes that are all
    basically toroidal in shape.

    Parameters
    ----------
    ringradius : float, optional
        The radius from the center to the middle of the ring of the
        supertoroid. Default is 1.

    crosssectionradius : float, optional
        The radius of the cross section of ring of the supertoroid.
        Default = 0.5.

    xradius : float, optional
        The scaling factor for the x-axis. Default is 1.

    yradius : float, optional
        The scaling factor for the y-axis. Default is 1.

    zradius : float, optional
        The scaling factor for the z-axis. Default is 1.

    n1 : float, optional
        The shape of the torus ring.  Default is 1.

    n2 : float, optional
        The shape of the cross section of the ring. Default is 1.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricSuperToroid surface.

    See Also
    --------
    pyvista.ParametricSuperEllipsoid :
        Ellipsoidal equivalent of ParametricSuperToroid.
    pyvista.Superquadric :
        Geometric object with additional parameters.

    Examples
    --------
    Create a ParametricSuperToroid mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricSuperToroid(n1=2, n2=0.3)
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricSuperToroid()
    if ringradius is not None:
        parametric_function.SetRingRadius(ringradius)

    if crosssectionradius is not None:
        parametric_function.SetCrossSectionRadius(crosssectionradius)

    if xradius is not None:
        parametric_function.SetXRadius(xradius)

    if yradius is not None:
        parametric_function.SetYRadius(yradius)

    if zradius is not None:
        parametric_function.SetZRadius(zradius)

    if n1 is not None:
        parametric_function.SetN1(n1)

    if n2 is not None:
        parametric_function.SetN2(n2)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def ParametricTorus(ringradius=None, crosssectionradius=None, **kwargs):
    """Generate a torus.

    Parameters
    ----------
    ringradius : float, optional
        The radius from the center to the middle of the ring of the
        torus. Default is 1.0.

    crosssectionradius : float, optional
        The radius of the cross section of ring of the torus. Default is 0.5.

    **kwargs : dict, optional
        See :func:`surface_from_para` for additional keyword arguments.

    Returns
    -------
    pyvista.PolyData
        ParametricTorus surface.

    Examples
    --------
    Create a ParametricTorus mesh.

    >>> import pyvista
    >>> mesh = pyvista.ParametricTorus()
    >>> mesh.plot(color='w', smooth_shading=True)

    """
    parametric_function = _vtk.vtkParametricTorus()
    if ringradius is not None:
        parametric_function.SetRingRadius(ringradius)

    if crosssectionradius is not None:
        parametric_function.SetCrossSectionRadius(crosssectionradius)

    center = kwargs.pop('center', [0.0, 0.0, 0.0])
    direction = kwargs.pop('direction', [1.0, 0.0, 0.0])
    kwargs.setdefault('clean', True)
    surf = surface_from_para(parametric_function, **kwargs)
    translate(surf, center, direction)
    return surf


def parametric_keywords(
    parametric_function,
    min_u=0,
    max_u=2 * pi,
    min_v=0.0,
    max_v=2 * pi,
    join_u=False,
    join_v=False,
    twist_u=False,
    twist_v=False,
    clockwise=True,
):
    """Apply keyword arguments to a parametric function.

    Parameters
    ----------
    parametric_function : vtk.vtkParametricFunction
        Parametric function to generate mesh from.

    min_u : float, optional
        The minimum u-value.

    max_u : float, optional
        The maximum u-value.

    min_v : float, optional
        The minimum v-value.

    max_v : float, optional
        The maximum v-value.

    join_u : bool, optional
        Joins the first triangle strip to the last one with a twist in
        the u direction.

    join_v : bool, optional
        Joins the first triangle strip to the last one with a twist in
        the v direction.

    twist_u : bool, optional
        Joins the first triangle strip to the last one with a twist in
        the u direction.

    twist_v : bool, optional
        Joins the first triangle strip to the last one with a twist in
        the v direction.

    clockwise : bool, optional
        Determines the ordering of the vertices forming the triangle
        strips.

    """
    parametric_function.SetMinimumU(min_u)
    parametric_function.SetMaximumU(max_u)
    parametric_function.SetMinimumV(min_v)
    parametric_function.SetMaximumV(max_v)
    parametric_function.SetJoinU(join_u)
    parametric_function.SetJoinV(join_v)
    parametric_function.SetTwistU(twist_u)
    parametric_function.SetTwistV(twist_v)
    parametric_function.SetClockwiseOrdering(clockwise)


def surface_from_para(parametric_function, u_res=100, v_res=100, w_res=100, clean=False):
    """Construct a mesh from a parametric function.

    Parameters
    ----------
    parametric_function : vtk.vtkParametricFunction
        Parametric function to generate mesh from.

    u_res : int, optional
        Resolution in the u direction.

    v_res : int, optional
        Resolution in the v direction.

    w_res : int, optional
        Resolution in the w direction.

    clean : bool, optional
        Clean and merge duplicate points to avoid "creases" when
        plotting with smooth shading.

    Returns
    -------
    pyvista.PolyData
        Surface from the parametric function.

    """
    # convert to a mesh
    para_source = _vtk.vtkParametricFunctionSource()
    para_source.SetParametricFunction(parametric_function)
    para_source.SetUResolution(u_res)
    para_source.SetVResolution(v_res)
    para_source.SetWResolution(w_res)
    para_source.Update()
    surf = pyvista.wrap(para_source.GetOutput())
    if clean:
        surf = surf.clean(
            tolerance=1e-7,  # determined experimentally
            absolute=False,
            lines_to_points=False,
            polys_to_lines=False,
            strips_to_polys=False,
        )
    return surf


# pyvista/utilities/reader.py

"""Fine-grained control of reading data files."""
from abc import ABC, abstractmethod
from dataclasses import dataclass
import enum
from functools import wraps
import os
import pathlib
from typing import Any, Callable, List, Union
from xml.etree import ElementTree

import numpy as np

import pyvista
from pyvista import _vtk
from pyvista.utilities import abstract_class, wrap

from .fileio import _get_ext_force, _process_filename

HDF_HELP = 'https://kitware.github.io/vtk-examples/site/VTKFileFormats/#hdf-file-formats'


def get_reader(filename, force_ext=None):
    """Get a reader for fine-grained control of reading data files.

    Supported file types and Readers:

    +----------------+---------------------------------------------+
    | File Extension | Class                                       |
    +================+=============================================+
    | ``.bmp``       | :class:`pyvista.BMPReader`                  |
    +----------------+---------------------------------------------+
    | ``.cas``       | :class:`pyvista.FluentReader`               |
    +----------------+---------------------------------------------+
    | ``.case``      | :class:`pyvista.EnSightReader`              |
    +----------------+---------------------------------------------+
    | ``.cgns``      | :class:`pyvista.CGNSReader`                 |
    +----------------+---------------------------------------------+
    | ``.dat``       | :class:`pyvista.TecplotReader`              |
    +----------------+---------------------------------------------+
    | ``.dcm``       | :class:`pyvista.DICOMReader`                |
    +----------------+---------------------------------------------+
    | ``.dem``       | :class:`pyvista.DEMReader`                  |
    +----------------+---------------------------------------------+
    | ``.facet``     | :class:`pyvista.FacetReader`                |
    +----------------+---------------------------------------------+
    | ``.foam``      | :class:`pyvista.POpenFOAMReader`            |
    +----------------+---------------------------------------------+
    | ``.g``         | :class:`pyvista.BYUReader`                  |
    +----------------+---------------------------------------------+
    | ``.gif``       | :class:`pyvista.GIFReader`                  |
    +----------------+---------------------------------------------+
    | ``.glb``       | :class:`pyvista.GLTFReader`                 |
    +----------------+---------------------------------------------+
    | ``.gltf``      | :class:`pyvista.GLTFReader`                 |
    +----------------+---------------------------------------------+
    | ``.hdf``       | :class:`pyvista.HDFReader`                  |
    +----------------+---------------------------------------------+
    | ``.img``       | :class:`pyvista.DICOMReader`                |
    +----------------+---------------------------------------------+
    | ``.inp``       | :class:`pyvista.AVSucdReader`               |
    +----------------+---------------------------------------------+
    | ``.jpg``       | :class:`pyvista.JPEGReader`                 |
    +----------------+---------------------------------------------+
    | ``.jpeg``      | :class:`pyvista.JPEGReader`                 |
    +----------------+---------------------------------------------+
    | ``.hdr``       | :class:`pyvista.HDRReader`                  |
    +----------------+---------------------------------------------+
    | ``.mha``       | :class:`pyvista.MetaImageReader`            |
    +----------------+---------------------------------------------+
    | ``.mhd``       | :class:`pyvista.MetaImageReader`            |
    +----------------+---------------------------------------------+
    | ``.nii``       | :class:`pyvista.NIFTIReader`                |
    +----------------+---------------------------------------------+
    | ``.nii.gz``    | :class:`pyvista.NIFTIReader`                |
    +----------------+---------------------------------------------+
    | ``.nhdr``      | :class:`pyvista.NRRDReader`                 |
    +----------------+---------------------------------------------+
    | ``.nrrd``      | :class:`pyvista.NRRDReader`                 |
    +----------------+---------------------------------------------+
    | ``.obj``       | :class:`pyvista.OBJReader`                  |
    +----------------+---------------------------------------------+
    | ``.p3d``       | :class:`pyvista.Plot3DMetaReader`           |
    +----------------+---------------------------------------------+
    | ``.ply``       | :class:`pyvista.PLYReader`                  |
    +----------------+---------------------------------------------+
    | ``.png``       | :class:`pyvista.PNGReader`                  |
    +----------------+---------------------------------------------+
    | ``.pnm``       | :class:`pyvista.PNMReader`                  |
    +----------------+---------------------------------------------+
    | ``.pts``       | :class:`pyvista.PTSReader`                  |
    +----------------+---------------------------------------------+
    | ``.pvd``       | :class:`pyvista.PVDReader`                  |
    +----------------+---------------------------------------------+
    | ``.pvti``      | :class:`pyvista.XMLPImageDataReader`        |
    +----------------+---------------------------------------------+
    | ``.pvtk``      | :class:`pyvista.VTKPDataSetReader`          |
    +----------------+---------------------------------------------+
    | ``.pvtr``      | :class:`pyvista.XMLPRectilinearGridReader`  |
    +----------------+---------------------------------------------+
    | ``.pvtu``      | :class:`pyvista.XMLPUnstructuredGridReader` |
    +----------------+---------------------------------------------+
    | ``.res``       | :class:`pyvista.MFIXReader`                 |
    +----------------+---------------------------------------------+
    | ``.segy``      | :class:`pyvista.SegYReader`                 |
    +----------------+---------------------------------------------+
    | ``.sgy``       | :class:`pyvista.SegYReader`                 |
    +----------------+---------------------------------------------+
    | ``.slc``       | :class:`pyvista.SLCReader`                  |
    +----------------+---------------------------------------------+
    | ``.stl``       | :class:`pyvista.STLReader`                  |
    +----------------+---------------------------------------------+
    | ``.tif``       | :class:`pyvista.TIFFReader`                 |
    +----------------+---------------------------------------------+
    | ``.tiff``      | :class:`pyvista.TIFFReader`                 |
    +----------------+---------------------------------------------+
    | ``.tri``       | :class:`pyvista.BinaryMarchingCubesReader`  |
    +----------------+---------------------------------------------+
    | ``.vti``       | :class:`pyvista.XMLImageDataReader`         |
    +----------------+---------------------------------------------+
    | ``.vtk``       | :class:`pyvista.VTKDataSetReader`           |
    +----------------+---------------------------------------------+
    | ``.vtm``       | :class:`pyvista.XMLMultiBlockDataReader`    |
    +----------------+---------------------------------------------+
    | ``.vtmb``      | :class:`pyvista.XMLMultiBlockDataReader`    |
    +----------------+---------------------------------------------+
    | ``.vtp``       | :class:`pyvista.XMLPolyDataReader`          |
    +----------------+---------------------------------------------+
    | ``.vtr``       | :class:`pyvista.XMLRectilinearGridReader`   |
    +----------------+---------------------------------------------+
    | ``.vts``       | :class:`pyvista.XMLStructuredGridReader`    |
    +----------------+---------------------------------------------+
    | ``.vtu``       | :class:`pyvista.XMLUnstructuredGridReader`  |
    +----------------+---------------------------------------------+

    Parameters
    ----------
    filename : str
        The string path to the file to read.

    force_ext : str, optional
        An extension to force a specific reader to be chosen.

    Returns
    -------
    pyvista.BaseReader
        A subclass of :class:`pyvista.BaseReader` is returned based on file type.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_human(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'Human.vtp'
    >>> reader = pyvista.get_reader(filename)
    >>> reader  # doctest: +ELLIPSIS
    XMLPolyDataReader('.../Human.vtp')
    >>> mesh = reader.read()
    >>> mesh # doctest: +ELLIPSIS
    PolyData ...
    >>> mesh.plot(color='tan')

    """
    ext = _get_ext_force(filename, force_ext)

    try:
        Reader = CLASS_READERS[ext]
    except KeyError:
        raise ValueError(f"`pyvista.get_reader` does not support a file with the {ext} extension")

    return Reader(filename)


@abstract_class
class BaseReader:
    """The Base Reader class.

    The base functionality includes reading data from a file,
    and allowing access to the underlying vtk reader. See
    :func:`pyvista.get_reader` for an example using
    a built-in subclass.

    Parameters
    ----------
    path : str
        Path of the file to read.
    """

    _class_reader: Any = None

    def __init__(self, path):
        """Initialize Reader by setting path."""
        self._reader = self._class_reader()
        self._filename = None
        self._progress_bar = False
        self._progress_msg = None
        self.__directory = None
        self._set_defaults()
        self.path = path
        self._set_defaults_post()

    def __repr__(self):
        """Representation of a Reader object."""
        return f"{self.__class__.__name__}('{self.path}')"

    def show_progress(self, msg=None):
        """Show a progress bar when loading the file.

        Parameters
        ----------
        msg : str, optional
            Progress bar message. Defaults to ``"Reading <file base name>"``.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.show_progress()

        """
        self._progress_bar = True
        if msg is None:
            msg = f"Reading {os.path.basename(self.path)}"
        self._progress_msg = msg

    def hide_progress(self):
        """Hide the progress bar when loading the file.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.hide_progress()

        """
        self._progress_bar = False

    @property
    def reader(self):
        """Return the vtk Reader object.

        Returns
        -------
        pyvista.BaseReader
            An instance of the Reader object.

        """
        if self._reader is None:  # pragma: no cover
            raise NotImplementedError
        return self._reader

    @property
    def path(self) -> str:
        """Return or set the filename or directory of the reader.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_human(load=False)
        >>> reader = pyvista.XMLPolyDataReader(filename)
        >>> reader.path  # doctest:+SKIP
        '/home/user/.local/share/pyvista/examples/Human.vtp'

        """
        if self._filename is not None:
            return self._filename
        return self.__directory

    @path.setter
    def path(self, path: str):
        if os.path.isdir(path):
            self._set_directory(path)
        elif os.path.isfile(path):
            self._set_filename(path)
        else:
            raise FileNotFoundError(f"Path '{path}' is invalid or does not exist.")

    def _set_directory(self, directory):
        """Set directory and update reader."""
        self._filename = None
        self.__directory = directory
        self.reader.SetDirectoryName(directory)
        self._update_information()

    def _set_filename(self, filename):
        """Set filename and update reader."""
        # Private method since changing file type requires a
        # different subclass.
        self.__directory = None
        self._filename = filename
        self.reader.SetFileName(filename)
        self._update_information()

    def read(self):
        """Read data in file.

        Returns
        -------
        pyvista.DataSet
            PyVista Dataset.
        """
        from pyvista.core.filters import _update_alg  # avoid circular import

        _update_alg(self.reader, progress_bar=self._progress_bar, message=self._progress_msg)
        data = wrap(self.reader.GetOutputDataObject(0))
        if data is None:  # pragma: no cover
            raise RuntimeError("Failed to read file.")
        data._post_file_load_processing()

        # check for any pyvista metadata
        data._restore_metadata()
        return data

    def _update_information(self):
        self.reader.UpdateInformation()

    def _set_defaults(self):
        """Set defaults on reader, if needed."""
        pass

    def _set_defaults_post(self):
        """Set defaults on reader post setting file, if needed."""
        pass


class PointCellDataSelection:
    """Mixin for readers that support data array selections.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_backward_facing_step(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'foam_case_0_0_0_0.case'
    >>> reader = pyvista.get_reader(filename)
    >>> reader  # doctest: +ELLIPSIS
    EnSightReader('.../foam_case_0_0_0_0.case')
    >>> reader.cell_array_names
    ['v2', 'nut', 'k', 'nuTilda', 'p', 'omega', 'f', 'epsilon', 'U']
    >>> reader.point_array_names
    []
    >>> reader.all_cell_arrays_status  # doctest: +NORMALIZE_WHITESPACE
    {'v2': True, 'nut': True, 'k': True, 'nuTilda': True, 'p': True, 'omega': True, 'f': True, 'epsilon': True, 'U': True}
    >>> reader.disable_all_cell_arrays()
    >>> reader.enable_cell_array('U')
    >>> mesh = reader.read()  # MultiBlock mesh
    >>> mesh[0].array_names
    ['U']

    """

    @property
    def number_point_arrays(self):
        """Return the number of point arrays.

        Returns
        -------
        int

        """
        return self.reader.GetNumberOfPointArrays()

    @property
    def point_array_names(self):
        """Return the list of all point array names.

        Returns
        -------
        list[str]

        """
        return [self.reader.GetPointArrayName(i) for i in range(self.number_point_arrays)]

    def enable_point_array(self, name):
        """Enable point array with name.

        Parameters
        ----------
        name : str
            Point array name.

        """
        self.reader.SetPointArrayStatus(name, 1)

    def disable_point_array(self, name):
        """Disable point array with name.

        Parameters
        ----------
        name : str
            Point array name.

        """
        self.reader.SetPointArrayStatus(name, 0)

    def point_array_status(self, name):
        """Get status of point array with name.

        Parameters
        ----------
        name : str
            Point array name.

        Returns
        -------
        bool
            Whether reading the cell array is enabled.

        """
        if self.reader.GetPointArrayStatus(name):
            return True
        return False

    def enable_all_point_arrays(self):
        """Enable all point arrays."""
        for name in self.point_array_names:
            self.enable_point_array(name)

    def disable_all_point_arrays(self):
        """Disable all point arrays."""
        for name in self.point_array_names:
            self.disable_point_array(name)

    @property
    def all_point_arrays_status(self):
        """Return the status of all point arrays.

        Returns
        -------
        dict[str, bool]

        """
        return {name: self.point_array_status(name) for name in self.point_array_names}

    @property
    def number_cell_arrays(self):
        """Return the number of cell arrays.

        Returns
        -------
        int

        """
        return self.reader.GetNumberOfCellArrays()

    @property
    def cell_array_names(self):
        """Return the list of all cell array names.

        Returns
        -------
        list[str]

        """
        return [self.reader.GetCellArrayName(i) for i in range(self.number_cell_arrays)]

    def enable_cell_array(self, name):
        """Enable cell array with name.

        Parameters
        ----------
        name : str
            Cell array name.

        """
        self.reader.SetCellArrayStatus(name, 1)

    def disable_cell_array(self, name):
        """Disable cell array with name.

        Parameters
        ----------
        name : str
            Cell array name.

        """
        self.reader.SetCellArrayStatus(name, 0)

    def cell_array_status(self, name):
        """Get status of cell array with name.

        Parameters
        ----------
        name : str
            Cell array name.

        Returns
        -------
        bool
            Whether reading the cell array is enabled.

        """
        return bool(self.reader.GetCellArrayStatus(name))

    def enable_all_cell_arrays(self):
        """Enable all cell arrays."""
        for name in self.cell_array_names:
            self.enable_cell_array(name)

    def disable_all_cell_arrays(self):
        """Disable all cell arrays."""
        for name in self.cell_array_names:
            self.disable_cell_array(name)

    @property
    def all_cell_arrays_status(self):
        """Return the status of all cell arrays.

        Returns
        -------
        dict[str, bool]
        """
        return {name: self.cell_array_status(name) for name in self.cell_array_names}


class TimeReader(ABC):
    """Abstract class for readers supporting time."""

    @property
    @abstractmethod
    def number_time_points(self):
        """Return number of time points or iterations available to read.

        Returns
        -------
        int

        """

    @abstractmethod
    def time_point_value(self, time_point):
        """Value of time point or iteration by index.

        Parameters
        ----------
        time_point: int
            Time point index.

        Returns
        -------
        float

        """

    @property
    def time_values(self):
        """All time or iteration values.

        Returns
        -------
        list[float]

        """
        return [self.time_point_value(idx) for idx in range(self.number_time_points)]

    @property
    @abstractmethod
    def active_time_value(self):
        """Active time or iteration value.

        Returns
        -------
        float

        """

    @abstractmethod
    def set_active_time_value(self, time_value):
        """Set active time or iteration value.

        Parameters
        ----------
        time_value: float
            Time or iteration value to set as active.

        """

    @abstractmethod
    def set_active_time_point(self, time_point):
        """Set active time or iteration by index.

        Parameters
        ----------
        time_point: int
            Time or iteration point index for setting active time.

        """


class XMLImageDataReader(BaseReader, PointCellDataSelection):
    """XML Image Data Reader for .vti files."""

    _class_reader = _vtk.vtkXMLImageDataReader


class XMLPImageDataReader(BaseReader, PointCellDataSelection):
    """Parallel XML Image Data Reader for .pvti files."""

    _class_reader = _vtk.vtkXMLPImageDataReader


class XMLRectilinearGridReader(BaseReader, PointCellDataSelection):
    """XML RectilinearGrid Reader for .vtr files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_rectilinear_grid(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'RectilinearGrid.vtr'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> sliced_mesh = mesh.slice('y')
    >>> sliced_mesh.plot(scalars='Void Volume Fraction', cpos='xz',
    ...                  show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkXMLRectilinearGridReader


class XMLPRectilinearGridReader(BaseReader, PointCellDataSelection):
    """Parallel XML RectilinearGrid Reader for .pvtr files."""

    _class_reader = _vtk.vtkXMLPRectilinearGridReader


class XMLUnstructuredGridReader(BaseReader, PointCellDataSelection):
    """XML UnstructuredGrid Reader for .vtu files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_notch_displacement(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'notch_disp.vtu'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(scalars="Nodal Displacement", component=0,
    ...           cpos='xy', show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkXMLUnstructuredGridReader


class XMLPUnstructuredGridReader(BaseReader, PointCellDataSelection):
    """Parallel XML UnstructuredGrid Reader for .pvtu files."""

    _class_reader = _vtk.vtkXMLPUnstructuredGridReader


class XMLPolyDataReader(BaseReader, PointCellDataSelection):
    """XML PolyData Reader for .vtp files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_cow_head(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'cowHead.vtp'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(
    ...    cpos=((12, 3.5, -4.5), (4.5, 1.6, 0), (0, 1, 0.3)),
    ...    clim=[0, 100], show_scalar_bar=False
    ... )

    """

    _class_reader = _vtk.vtkXMLPolyDataReader


class XMLStructuredGridReader(BaseReader, PointCellDataSelection):
    """XML StructuredGrid Reader for .vts files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_structured_grid(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'StructuredGrid.vts'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(style='wireframe', line_width=4, show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkXMLStructuredGridReader


class XMLMultiBlockDataReader(BaseReader, PointCellDataSelection):
    """XML MultiBlock Data Reader for .vtm or .vtmb files."""

    _class_reader = _vtk.vtkXMLMultiBlockDataReader


# skip pydocstyle D102 check since docstring is taken from TimeReader


class EnSightReader(BaseReader, PointCellDataSelection, TimeReader):
    """EnSight Reader for .case files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_cylinder_crossflow(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'cylinder_Re35.case'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(scalars="velocity", component=1, clim=[-20, 20],
    ...           cpos='xy', cmap='RdBu', show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkGenericEnSightReader

    def _set_filename(self, filename):
        """Set filename and update reader."""
        # Private method since changing file type requires a
        # different subclass.
        self._filename = filename
        self.reader.SetCaseFileName(filename)
        self._update_information()

    @property
    def number_time_points(self):  # noqa: D102
        return self.reader.GetTimeSets().GetItem(0).GetSize()

    def time_point_value(self, time_point):  # noqa: D102
        return self.reader.GetTimeSets().GetItem(0).GetValue(time_point)

    @property
    def active_time_value(self):  # noqa: D102
        return self.reader.GetTimeValue()

    def set_active_time_value(self, time_value):  # noqa: D102
        if time_value not in self.time_values:
            raise ValueError(
                f"Not a valid time {time_value} from available time values: {self.reader_time_values}"
            )
        self.reader.SetTimeValue(time_value)

    def set_active_time_point(self, time_point):  # noqa: D102
        self.reader.SetTimeValue(self.time_point_value(time_point))


# skip pydocstyle D102 check since docstring is taken from TimeReader
class OpenFOAMReader(BaseReader, PointCellDataSelection, TimeReader):
    """OpenFOAM Reader for .foam files.

    By default, pyvista enables all patch arrays.  This is a deviation
    from the vtk default.

    """

    _class_reader = _vtk.vtkOpenFOAMReader

    def _set_defaults_post(self):
        self.enable_all_patch_arrays()

    @property
    def number_time_points(self):  # noqa: D102
        return self.reader.GetTimeValues().GetNumberOfValues()

    def time_point_value(self, time_point):  # noqa: D102
        return self.reader.GetTimeValues().GetValue(time_point)

    @property
    def active_time_value(self):  # noqa: D102
        try:
            value = self.reader.GetTimeValue()
        except AttributeError as err:  # pragma: no cover
            raise AttributeError(
                "Inspecting active time value only supported for vtk versions >9.1.0"
            ) from err
        return value

    def set_active_time_value(self, time_value):  # noqa: D102
        if time_value not in self.time_values:
            raise ValueError(
                f"Not a valid time {time_value} from available time values: {self.time_values}"
            )
        self.reader.UpdateTimeStep(time_value)

    def set_active_time_point(self, time_point):  # noqa: D102
        self.reader.UpdateTimeStep(self.time_point_value(time_point))

    @property
    def decompose_polyhedra(self):
        """Whether polyhedra are to be decomposed when read.

        Returns
        -------
        bool
            If ``True``, decompose polyhedra into tetrahedra and pyramids.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.decompose_polyhedra = False
        >>> reader.decompose_polyhedra
        False

        """
        return bool(self.reader.GetDecomposePolyhedra())

    @decompose_polyhedra.setter
    def decompose_polyhedra(self, value):
        self.reader.SetDecomposePolyhedra(value)

    @property
    def skip_zero_time(self):
        """Indicate whether or not to ignore the '/0' time directory.

        Returns
        -------
        bool
            If ``True``, ignore the '/0' time directory.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.skip_zero_time = False
        >>> reader.skip_zero_time
        False

        """
        return bool(self.reader.GetSkipZeroTime())

    @skip_zero_time.setter
    def skip_zero_time(self, value):
        self.reader.SetSkipZeroTime(value)
        self._update_information()
        self.reader.SetRefresh()

    @property
    def cell_to_point_creation(self):
        """Whether cell data is translated to point data when read.

        Returns
        -------
        bool
            If ``True``, translate cell data to point data.

        Warnings
        --------
        When ``True``, cell and point data arrays will have
        duplicate names.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.cell_to_point_creation = False
        >>> reader.cell_to_point_creation
        False

        """
        return bool(self.reader.GetCreateCellToPoint())

    @cell_to_point_creation.setter
    def cell_to_point_creation(self, value):
        self.reader.SetCreateCellToPoint(value)

    @property
    def number_patch_arrays(self):
        """Return number of patch arrays in dataset.

        Returns
        -------
        int

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.number_patch_arrays
        4

        """
        return self.reader.GetNumberOfPatchArrays()

    @property
    def patch_array_names(self):
        """Names of patch arrays in a list.

        Returns
        -------
        list[str]

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.patch_array_names
        ['internalMesh', 'patch/movingWall', 'patch/fixedWalls', 'patch/frontAndBack']

        """
        return [self.reader.GetPatchArrayName(i) for i in range(self.number_patch_arrays)]

    def enable_patch_array(self, name):
        """Enable reading of patch array.

        Parameters
        ----------
        name : str
            Which patch array to enable.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.enable_patch_array("patch/movingWall")
        >>> reader.patch_array_status("patch/movingWall")
        True

        """
        self.reader.SetPatchArrayStatus(name, 1)

    def disable_patch_array(self, name):
        """Disable reading of patch array.

        Parameters
        ----------
        name : str
            Which patch array to disable.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.disable_patch_array("internalMesh")
        >>> reader.patch_array_status("internalMesh")
        False

        """
        self.reader.SetPatchArrayStatus(name, 0)

    def patch_array_status(self, name):
        """Return status of reading patch array.

        Parameters
        ----------
        name : str
            Which patch array to report status.
        Returns
        -------
        bool
                Whether the patch with the given name is to be read.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.enable_patch_array("patch/movingWall")
        >>> reader.patch_array_status("patch/movingWall")
        True

        """
        return bool(self.reader.GetPatchArrayStatus(name))

    def enable_all_patch_arrays(self):
        """Enable reading of all patch arrays.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.enable_all_patch_arrays()
        >>> assert reader.patch_array_status("patch/movingWall")
        >>> assert reader.patch_array_status("patch/fixedWalls")

        """
        self.reader.EnableAllPatchArrays()

    def disable_all_patch_arrays(self):
        """Disable reading of all patch arrays.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.disable_all_patch_arrays()
        >>> assert not reader.patch_array_status("patch.movingWall")
        >>> assert not reader.patch_array_status("internalMesh")

        """
        self.reader.DisableAllPatchArrays()

    @property
    def all_patch_arrays_status(self):
        """Status of reading all patch arrays.

        Returns
        -------
        dict[str, bool]
            dict key is the patch name and the value is whether it will be read.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.OpenFOAMReader(filename)
        >>> reader.all_patch_arrays_status  #doctest: +NORMALIZE_WHITESPACE
        {'internalMesh': True, 'patch/movingWall': True, 'patch/fixedWalls': True,
         'patch/frontAndBack': True}

        """
        return {name: self.patch_array_status(name) for name in self.patch_array_names}


class POpenFOAMReader(OpenFOAMReader):
    """Parallel OpenFOAM Reader for .foam files.

    Can read parallel-decomposed mesh information and time dependent data.
    This reader can be used for serial generated data,
    parallel reconstructed data, and decomposed data.
    """

    _class_reader = staticmethod(_vtk.lazy_vtkPOpenFOAMReader)

    @property
    def case_type(self):
        """Indicate whether decomposed mesh or reconstructed mesh should be read.

        Returns
        -------
        str
            If ``'reconstructed'``, reconstructed mesh should be read.
            If ``'decomposed'``, decomposed mesh should be read.

        Raises
        ------
        ValueError
            If the value is not in ['reconstructed', 'decomposed']

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cavity(load=False)
        >>> reader = pyvista.POpenFOAMReader(filename)
        >>> reader.case_type = 'reconstructed'
        >>> reader.case_type
        'reconstructed'
        """
        return 'reconstructed' if self.reader.GetCaseType() else 'decomposed'

    @case_type.setter
    def case_type(self, value):
        if value == 'reconstructed':
            self.reader.SetCaseType(1)
        elif value == 'decomposed':
            self.reader.SetCaseType(0)
        else:
            raise ValueError(f"Unknown case type '{value}'.")

        self._update_information()


class PLYReader(BaseReader):
    """PLY Reader for reading .ply files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_lobster(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'lobster.ply'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkPLYReader


class OBJReader(BaseReader):
    """OBJ Reader for reading .obj files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_trumpet(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'trumpet.obj'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(cpos='yz', show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkOBJReader


class STLReader(BaseReader):
    """STL Reader for .stl files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_cad_model(load=False)
    >>> filename.split("/")[-1]  # omit the path
    '42400-IDGH.stl'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkSTLReader


class TecplotReader(BaseReader):
    """Tecplot Reader for ascii .dat files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_tecplot_ascii(load=False)
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh[0].plot()

    """

    _class_reader = _vtk.vtkTecplotReader


class VTKDataSetReader(BaseReader):
    """VTK Data Set Reader for .vtk files.

    Notes
    -----
    This reader calls ``ReadAllScalarsOn``, ``ReadAllColorScalarsOn``,
    ``ReadAllNormalsOn``, ``ReadAllTCoordsOn``, ``ReadAllVectorsOn``,
    and ``ReadAllFieldsOn`` on the underlying ``vtkDataSetReader``.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_brain(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'brain.vtk'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> sliced_mesh = mesh.slice('x')
    >>> sliced_mesh.plot(cpos='yz', show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkDataSetReader

    def _set_defaults_post(self):
        self.reader.ReadAllScalarsOn()
        self.reader.ReadAllColorScalarsOn()
        self.reader.ReadAllNormalsOn()
        self.reader.ReadAllTCoordsOn()
        self.reader.ReadAllVectorsOn()
        self.reader.ReadAllFieldsOn()
        self.reader.ReadAllTensorsOn()


class VTKPDataSetReader(BaseReader):
    """Parallel VTK Data Set Reader for .pvtk files."""

    _class_reader = staticmethod(_vtk.lazy_vtkPDataSetReader)


class BYUReader(BaseReader):
    """BYU Reader for .g files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_teapot(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'teapot.g'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(cpos='xy', show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkBYUReader


class FacetReader(BaseReader):
    """Facet Reader for .facet files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_clown(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'clown.facet'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(color="red")

    """

    _class_reader = staticmethod(_vtk.lazy_vtkFacetReader)


class Plot3DMetaReader(BaseReader):
    """Plot3DMeta Reader for .p3d files."""

    _class_reader = staticmethod(_vtk.lazy_vtkPlot3DMetaReader)


class Plot3DFunctionEnum(enum.IntEnum):
    """An enumeration for the functions used in :class:`MultiBlockPlot3DReader`."""

    DENSITY = 100
    PRESSURE = 110
    PRESSURE_COEFFICIENT = 111
    MACH = 112
    SPEED_OF_SOUND = 113
    TEMPERATURE = 120
    ENTHALPY = 130
    INTERNAL_ENERGY = 140
    KINETIC_ENERGY = 144
    VELOCITY_MAGNITUDE = 153
    STAGNATION_ENERGY = 163
    ENTROPY = 170
    SWIRL = 184
    VELOCITY = 200
    VORTICITY = 201
    MOMENTUM = 202
    PRESSURE_GRADIENT = 210
    STRAIN_RATE = 212
    VORTICITY_MAGNITUDE = 211


class MultiBlockPlot3DReader(BaseReader):
    """MultiBlock Plot3D Reader.

    The methods :meth:`add_function()` and :meth:`remove_function()` accept values from
    :class:`Plot3DFunctionEnum`. For convenience, the values of that enumeration are available as class variables,
    as shown below.

        - ``MultiBlockPlot3DReader.DENSITY = Plot3DFunctionEnum.DENSITY``
        - ``MultiBlockPlot3DReader.PRESSURE = Plot3DFunctionEnum.PRESSURE``
        - ``MultiBlockPlot3DReader.PRESSURE_COEFFICIENT = Plot3DFunctionEnum.PRESSURE_COEFFICIENT``
        - ``MultiBlockPlot3DReader.MACH = Plot3DFunctionEnum.MACH``
        - ``MultiBlockPlot3DReader.SPEED_OF_SOUND = Plot3DFunctionEnum.SPEED_OF_SOUND``
        - ``MultiBlockPlot3DReader.TEMPERATURE = Plot3DFunctionEnum.TEMPERATURE``
        - ``MultiBlockPlot3DReader.ENTHALPY = Plot3DFunctionEnum.ENTHALPY``
        - ``MultiBlockPlot3DReader.INTERNAL_ENERGY = Plot3DFunctionEnum.INTERNAL_ENERGY``
        - ``MultiBlockPlot3DReader.KINETIC_ENERGY = Plot3DFunctionEnum.KINETIC_ENERGY``
        - ``MultiBlockPlot3DReader.VELOCITY_MAGNITUDE = Plot3DFunctionEnum.VELOCITY_MAGNITUDE``
        - ``MultiBlockPlot3DReader.STAGNATION_ENERGY = Plot3DFunctionEnum.STAGNATION_ENERGY``
        - ``MultiBlockPlot3DReader.ENTROPY = Plot3DFunctionEnum.ENTROPY``
        - ``MultiBlockPlot3DReader.SWIRL = Plot3DFunctionEnum.SWIRL``
        - ``MultiBlockPlot3DReader.VELOCITY = Plot3DFunctionEnum.VELOCITY``
        - ``MultiBlockPlot3DReader.VORTICITY = Plot3DFunctionEnum.VORTICITY``
        - ``MultiBlockPlot3DReader.MOMENTUM = Plot3DFunctionEnum.MOMENTUM``
        - ``MultiBlockPlot3DReader.PRESSURE_GRADIENT = Plot3DFunctionEnum.PRESSURE_GRADIENT``
        - ``MultiBlockPlot3DReader.STRAIN_RATE = Plot3DFunctionEnum.STRAIN_RATE``
        - ``MultiBlockPlot3DReader.VORTICITY_MAGNITUDE = Plot3DFunctionEnum.VORTICITY_MAGNITUDE``
    """

    _class_reader = staticmethod(_vtk.lazy_vtkMultiBlockPLOT3DReader)

    # pull in function name enum values as class constants
    DENSITY = Plot3DFunctionEnum.DENSITY
    PRESSURE = Plot3DFunctionEnum.PRESSURE
    PRESSURE_COEFFICIENT = Plot3DFunctionEnum.PRESSURE_COEFFICIENT
    MACH = Plot3DFunctionEnum.MACH
    SPEED_OF_SOUND = Plot3DFunctionEnum.SPEED_OF_SOUND
    TEMPERATURE = Plot3DFunctionEnum.TEMPERATURE
    ENTHALPY = Plot3DFunctionEnum.ENTHALPY
    INTERNAL_ENERGY = Plot3DFunctionEnum.INTERNAL_ENERGY
    KINETIC_ENERGY = Plot3DFunctionEnum.KINETIC_ENERGY
    VELOCITY_MAGNITUDE = Plot3DFunctionEnum.VELOCITY_MAGNITUDE
    STAGNATION_ENERGY = Plot3DFunctionEnum.STAGNATION_ENERGY
    ENTROPY = Plot3DFunctionEnum.ENTROPY
    SWIRL = Plot3DFunctionEnum.SWIRL
    VELOCITY = Plot3DFunctionEnum.VELOCITY
    VORTICITY = Plot3DFunctionEnum.VORTICITY
    MOMENTUM = Plot3DFunctionEnum.MOMENTUM
    PRESSURE_GRADIENT = Plot3DFunctionEnum.PRESSURE_GRADIENT
    STRAIN_RATE = Plot3DFunctionEnum.STRAIN_RATE
    VORTICITY_MAGNITUDE = Plot3DFunctionEnum.VORTICITY_MAGNITUDE

    def _set_defaults(self):
        self.auto_detect_format = True

    def add_q_files(self, files):
        """Add q file(s).

        Parameters
        ----------
        files : str or Iterable(str)
            Solution file or files to add.

        """
        # files may be a list or a single filename
        if files:
            if isinstance(files, (str, pathlib.Path)):
                files = [files]
        files = [_process_filename(f) for f in files]

        if hasattr(self.reader, 'AddFileName'):
            # AddFileName was added to vtkMultiBlockPLOT3DReader sometime around
            # VTK 8.2. This method supports reading multiple q files.
            for q_filename in files:
                self.reader.AddFileName(q_filename)
        else:
            # SetQFileName is used to add a single q file to be read, and is still
            # supported in VTK9.
            if len(files) > 0:
                if len(files) > 1:
                    raise RuntimeError(
                        'Reading of multiple q files is not supported with this version of VTK.'
                    )
                self.reader.SetQFileName(files[0])

    @property
    def auto_detect_format(self):
        """Whether to try to automatically detect format such as byte order, etc."""
        return bool(self.reader.GetAutoDetectFormat())

    @auto_detect_format.setter
    def auto_detect_format(self, value):
        self.reader.SetAutoDetectFormat(value)

    def add_function(self, value: Union[int, Plot3DFunctionEnum]):
        """Specify additional functions to compute.

        The available functions are enumerated in :class:`Plot3DFunctionEnum`. The members of this enumeration are most
        easily accessed by their aliases as class variables.

        Multiple functions may be requested by calling this method multiple times.

        Parameters
        ----------
        value : int or Plot3DFunctionEnum
            The function to add.

        Examples
        --------
        >>> import pyvista
        >>> from pyvista import examples
        >>> filename, _  = examples.downloads._download_file('multi-bin.xyz')
        >>> reader = pyvista.reader.MultiBlockPlot3DReader(filename)
        >>> reader.add_function(112)  # add a function by its integer value
        >>> reader.add_function(reader.PRESSURE_COEFFICIENT)  # add a function by enumeration via class variable alias

        """
        if isinstance(value, enum.Enum):
            value = value.value
        self.reader.AddFunction(value)

    def remove_function(self, value: Union[int, Plot3DFunctionEnum]):
        """Remove one function from list of functions to compute.

        For details on the types of accepted values, see :meth:``add_function``.

        Parameters
        ----------
        value : int or Plot3DFunctionEnum
            The function to remove.
        """
        if isinstance(value, enum.Enum):
            value = value.value
        self.reader.RemoveFunction(value)

    def remove_all_functions(self):
        """Remove all functions from list of functions to compute."""
        self.reader.RemoveAllFunctions()

    @property
    def preserve_intermediate_functions(self):
        """When ``True`` (default), intermediate computed quantities will be preserved.

        For example, if ``VelocityMagnitude`` is enabled, but not ``Velocity``, the reader still needs to compute
        ``Velocity``. If `preserve_intermediate_functions` is ``False``, then the output will not have ``Velocity``
        array, only the requested ``VelocityMagnitude``.

        This is useful to avoid using up memory for arrays that are not relevant for the analysis.
        """
        return self.reader.GetPreserveIntermediateFunctions()

    @preserve_intermediate_functions.setter
    def preserve_intermediate_functions(self, val):
        self.reader.SetPreserveIntermediateFunctions(val)

    @property
    def gamma(self):
        """Ratio of specific heats."""
        return self.reader.GetGamma()

    @gamma.setter
    def gamma(self, val):
        self.reader.SetGamma(val)

    @property
    def r_gas_constant(self):
        """Gas constant."""
        return self.reader.GetR()

    @r_gas_constant.setter
    def r_gas_constant(self, val):
        self.reader.SetR(val)


class CGNSReader(BaseReader, PointCellDataSelection):
    """CGNS Reader for .cgns files.

    Creates a multi-block dataset and reads unstructured grids and structured
    meshes from binary files stored in CGNS file format, with data stored at
    the nodes, cells or faces.

    By default, all point and cell arrays are loaded as well as the boundary
    patch. This varies from VTK's defaults. For more details, see
    `vtkCGNSReader <https://vtk.org/doc/nightly/html/classvtkCGNSReader.html>`_

    Examples
    --------
    Load a CGNS file.  All arrays are loaded by default.

    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_cgns_multi(load=False)
    >>> reader = pyvista.CGNSReader(filename)
    >>> reader.load_boundary_patch = False
    >>> ds = reader.read()
    >>> ds[0][0].cell_data
    pyvista DataSetAttributes
    Association     : CELL
    Active Scalars  : None
    Active Vectors  : Momentum
    Active Texture  : None
    Active Normals  : None
    Contains arrays :
        Density                 float64    (2928,)
        Momentum                float64    (2928, 3)            VECTORS
        EnergyStagnationDensity float64    (2928,)
        ViscosityEddy           float64    (2928,)
        TurbulentDistance       float64    (2928,)
        TurbulentSANuTilde      float64    (2928,)

    """

    _class_reader = staticmethod(_vtk.lazy_vtkCGNSReader)

    def _set_defaults_post(self):
        self.enable_all_point_arrays()
        self.enable_all_cell_arrays()
        self.load_boundary_patch = True

    @property
    def distribute_blocks(self) -> bool:
        """Distribute each block in each zone across ranks.

        To make the reader disregard the piece request and read all blocks in the
        zone, set this to ``False``. The default is ``True``.

        Returns
        -------
        bool
            If ``True``, distribute each block in each zone across ranks.

        Examples
        --------
        Disable distributing blocks.

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.distribute_blocks = False
        >>> reader.distribute_blocks
        False

        """
        return bool(self._reader.GetDistributeBlocks())

    @distribute_blocks.setter
    def distribute_blocks(self, value: str):
        self._reader.SetDistributeBlocks(value)

    def base_array_status(self, name: str) -> bool:
        """Get status of base array with name.

        Parameters
        ----------
        name : str
            Base array name.

        Returns
        -------
        bool
            Whether reading the base array is enabled.

        """
        return bool(self.reader.GetBaseArrayStatus(name))

    @property
    def base_array_names(self):
        """Return the list of all base array names.

        Returns
        -------
        list[int]

        """
        return [self.reader.GetBaseArrayName(i) for i in range(self.number_base_arrays)]

    @property
    def number_base_arrays(self) -> int:
        """Return the number of base arrays.

        Returns
        -------
        int

        """
        return self.reader.GetNumberOfBaseArrays()

    def enable_all_bases(self):
        """Enable reading all bases.

        By default only the 0th base is read.

        Examples
        --------
        Read all bases.

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.enable_all_bases()
        """
        self._reader.EnableAllBases()

    def disable_all_bases(self):
        """Disable reading all bases.

        By default only the 0th base is read.

        Examples
        --------
        Disable reading all bases.

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.disable_all_bases()
        """
        self._reader.DisableAllBases()

    def family_array_status(self, name) -> bool:
        """Get status of family array with name.

        Parameters
        ----------
        name : str
            Family array name.

        Returns
        -------
        bool
            Whether reading the family array is enabled.

        """
        return bool(self.reader.GetFamilyArrayStatus(name))

    @property
    def family_array_names(self) -> List[str]:
        """Return the list of all family array names.

        Returns
        -------
        list[str]

        """
        return [self.reader.GetFamilyArrayName(i) for i in range(self.number_family_arrays)]

    @property
    def number_family_arrays(self) -> int:
        """Return the number of face arrays.

        Returns
        -------
        int

        """
        return self.reader.GetNumberOfFamilyArrays()

    def enable_all_families(self):
        """Enable reading all families.

        By default only the 0th family is read.

        Examples
        --------
        Read all bases.

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.enable_all_families()
        """
        self._reader.EnableAllFamilies()

    def disable_all_families(self):
        """Disable reading all families.

        Examples
        --------
        Disable reading all bases.

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.disable_all_families()
        """
        self._reader.DisableAllFamilies()

    @property
    def unsteady_pattern(self) -> bool:
        """Return or set using an unsteady pattern.

        When set to ``True`` (default is ``False``), the reader will try to
        determine FlowSolution_t nodes to read with a pattern
        matching This can be useful for unsteady solutions when
        FlowSolutionPointers are not reliable.

        Examples
        --------
        Set reading the unsteady pattern to ``True``.

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.unsteady_pattern = True
        >>> reader.unsteady_pattern
        True

        """
        return self._reader.GetUseUnsteadyPattern()

    @unsteady_pattern.setter
    def unsteady_pattern(self, enabled: bool):
        self._reader.SetUseUnsteadyPattern(bool(enabled))

    @property
    def vector_3d(self) -> bool:
        """Return or set adding an empty dimension to vectors in case of 2D solutions.

        Examples
        --------
        Set adding an empty physical dimension to vectors to ``True``.

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.vector_3d = True
        >>> reader.vector_3d
        True

        """
        return self._reader.GetUse3DVector()

    @vector_3d.setter
    def vector_3d(self, enabled: bool):
        self._reader.SetUse3DVector(bool(enabled))

    @property
    def load_boundary_patch(self) -> bool:
        """Return or set loading boundary patches.

        Notes
        -----
        VTK default is ``False``, but PyVista uses ``True``.

        Examples
        --------
        Enable loading boundary patches .

        >>> import pyvista
        >>> from pyvista import examples
        >>> filename = examples.download_cgns_multi(load=False)
        >>> reader = pyvista.CGNSReader(filename)
        >>> reader.load_boundary_patch = True
        >>> reader.load_boundary_patch
        True

        """
        return self._reader.GetLoadBndPatch()

    @load_boundary_patch.setter
    def load_boundary_patch(self, enabled: bool):
        self._reader.SetLoadBndPatch(bool(enabled))


class BinaryMarchingCubesReader(BaseReader):
    """BinaryMarchingCubes Reader for .tri files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_pine_roots(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'pine_root.tri'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(color="brown")

    """

    _class_reader = _vtk.vtkMCubesReader


@dataclass(order=True)
class PVDDataSet:
    """Class for storing dataset info from PVD file."""

    time: float
    part: int
    path: str
    group: str


# skip pydocstyle D102 check since docstring is taken from TimeReader
class PVDReader(BaseReader, TimeReader):
    """PVD Reader for .pvd files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_wavy(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'wavy.pvd'
    >>> reader = pyvista.get_reader(filename)
    >>> reader.time_values  # doctest: +ELLIPSIS
    [0.0, 1.0, 2.0, 3.0, ... 12.0, 13.0, 14.0]
    >>> reader.set_active_time_point(5)
    >>> reader.active_time_value
    5.0
    >>> mesh = reader.read()[0]  # MultiBlock mesh with only 1 block
    >>> mesh.plot(scalars='z')

    """

    def __init__(self, filename):
        """Initialize PVD file reader."""
        self._reader = None
        self.__directory = None
        self._datasets = []
        self._active_datasets = []
        self._active_readers = []
        self._time_values = []

        self._set_filename(filename)

    @property
    def reader(self):
        """Return the PVDReader.

        .. note::
            This Reader does not have an underlying vtk Reader.

        """
        return self

    @property
    def active_readers(self):
        """Return the active readers.

        Returns
        -------
        list[pyvista.BaseReader]

        """
        return self._active_readers

    @property
    def datasets(self):
        """Return all datasets.

        Returns
        -------
        list[pyvista.PVDDataSet]

        """
        return self._datasets

    @property
    def active_datasets(self):
        """Return all active datasets.

        Returns
        -------
        list[pyvista.PVDDataSet]

        """
        return self._active_datasets

    def _set_filename(self, filename):
        """Set filename and update reader."""
        self._filename = filename
        self.__directory = os.path.join(os.path.dirname(filename))
        self._datasets = None
        self._active_datasets = None
        self._update_information()

    def read(self):
        """Read data from PVD timepoint.

        Overrides :func:`pyvista.BaseReader.read`.

        Returns
        -------
        pyvista.MultiBlock

        """
        return pyvista.MultiBlock([reader.read() for reader in self.active_readers])

    def _update_information(self):
        """If dataset information is unavailable, parse file."""
        if self.datasets is None:
            self._parse_file()

    def _parse_file(self):
        """Parse PVD file."""
        if self.path is None:
            raise ValueError("Filename must be set")
        tree = ElementTree.parse(self.path)
        root = tree.getroot()
        dataset_elements = root[0].findall("DataSet")
        datasets = []
        for element in dataset_elements:
            element_attrib = element.attrib
            datasets.append(
                PVDDataSet(
                    float(element_attrib.get('timestep', 0)),
                    int(element_attrib['part']),
                    element_attrib['file'],
                    element_attrib.get('group'),
                )
            )
        self._datasets = sorted(datasets)
        self._time_values = sorted(list(set([dataset.time for dataset in self.datasets])))

        self.set_active_time_value(self.time_values[0])

    @property
    def time_values(self):  # noqa: D102
        return self._time_values

    @property
    def number_time_points(self):  # noqa: D102
        return len(self.time_values)

    def time_point_value(self, time_point):  # noqa: D102
        return self.time_values[time_point]

    @property
    def active_time_value(self):  # noqa: D102
        # all active datasets have the same time
        return self.active_datasets[0].time

    def set_active_time_value(self, time_value):  # noqa: D102
        self._active_datasets = [dataset for dataset in self.datasets if dataset.time == time_value]
        self._active_readers = [
            get_reader(os.path.join(self.__directory, dataset.path))
            for dataset in self.active_datasets
        ]

    def set_active_time_point(self, time_point):  # noqa: D102
        self.set_active_time_value(self.time_values[time_point])


class DICOMReader(BaseReader):
    """DICOM Reader for reading ``.dcm`` files.

    This reader reads a single file or a path containing a several ``.dcm``
    files (DICOM stack).

    Parameters
    ----------
    path : str
        Path to the single DICOM (``.dcm``) file to be opened or the directory
        containing a stack of DICOM files.

    Examples
    --------
    Read a DICOM stack.

    >>> import pyvista
    >>> from pyvista import examples
    >>> path = examples.download_dicom_stack(load=False)
    >>> reader = pyvista.DICOMReader(path)
    >>> dataset = reader.read()
    >>> dataset.plot(volume=True, zoom=3, show_scalar_bar=False)

    """

    _class_reader = _vtk.vtkDICOMImageReader


class BMPReader(BaseReader):
    """BMP Reader for .bmp files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_masonry_texture(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'masonry.bmp'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkBMPReader


class DEMReader(BaseReader):
    """DEM Reader for .dem files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_st_helens(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'SainteHelens.dem'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkDEMReader


class JPEGReader(BaseReader):
    """JPEG Reader for .jpeg and .jpg files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.planets.download_mars_surface(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'mars.jpg'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkJPEGReader


class MetaImageReader(BaseReader):
    """Meta Image Reader for .mha and .mhd files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_chest(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'ChestCT-SHORT.mha'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkMetaImageReader


class NIFTIReader(BaseReader):
    """NIFTI Reader for .nii and .nii.gz files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_brain_atlas_with_sides(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'avg152T1_RL_nifti.nii.gz'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkNIFTIImageReader


class NRRDReader(BaseReader):
    """NRRDReader for .nrrd and .nhdr files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_beach(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'beach.nrrd'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkNrrdReader


class PNGReader(BaseReader):
    """PNGReader for .png files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_vtk_logo(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'vtk.png'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkPNGReader


class PNMReader(BaseReader):
    """PNMReader for .pnm files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_gourds_pnm(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'Gourds.pnm'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkPNMReader


class SLCReader(BaseReader):
    """SLCReader for .slc files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_knee_full(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'vw_knee.slc'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkSLCReader


class TIFFReader(BaseReader):
    """TIFFReader for .tif and .tiff files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_crater_imagery(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'BJ34_GeoTifv1-04_crater_clip.tif'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkTIFFReader


class HDRReader(BaseReader):
    """HDRReader for .hdr files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_parched_canal_4k(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'parched_canal_4k.hdr'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = _vtk.vtkHDRReader


class PTSReader(BaseReader):
    """PTSReader for .pts files."""

    _class_reader = _vtk.vtkPTSReader


class AVSucdReader(BaseReader):
    """AVSucdReader for .inp files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_cells_nd(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'cellsnd.ascii.inp'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(cpos="xy")

    """

    _class_reader = _vtk.vtkAVSucdReader


class HDFReader(BaseReader):
    """HDFReader for .hdf files.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_can_crushed_hdf(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'can-vtu.hdf'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot()

    """

    _class_reader = staticmethod(_vtk.lazy_vtkHDFReader)

    @wraps(BaseReader.read)
    def read(self):
        """Wrap the base reader to handle the vtk 9.1 --> 9.2 change."""
        try:
            with pyvista.VtkErrorCatcher(raise_errors=True):
                return super().read()
        except RuntimeError as err:  # pragma: no cover
            if "Can't find the `Type` attribute." in str(err):
                raise RuntimeError(
                    f'{self.path} is missing the Type attribute. '
                    'The VTKHDF format has changed as of 9.2.0, '
                    f'see {HDF_HELP} for more details.'
                )
            else:
                raise err


class GLTFReader(BaseReader):
    """GLTFeader for .gltf and .glb files."""

    _class_reader = _vtk.vtkGLTFReader


class FluentReader(BaseReader):
    """FluentReader for .cas files."""

    _class_reader = _vtk.vtkFLUENTReader


class MFIXReader(BaseReader):
    """MFIXReader for .res files."""

    _class_reader = _vtk.vtkMFIXReader


class SegYReader(BaseReader):
    """SegYReader for .sgy and .segy files."""

    _class_reader = staticmethod(_vtk.lazy_vtkSegYReader)


class _GIFReader:
    """Simulate a VTK reader for GIF files."""

    _data_object = None
    _observers: List[Union[int, Callable]] = []
    _n_frames = 0
    _current_frame = 0

    def SetFileName(self, filename):
        """Needed for VTK-like compatibility with BaseReader."""
        self._filename = filename

    def UpdateInformation(self):
        """Needed for VTK-like compatibility with BaseReader."""
        pass

    def AddObserver(self, event_type, callback):
        """Needed for VTK-like compatibility with BaseReader."""
        self._observers.append([event_type, callback])

    def RemoveObservers(self, *args):
        """Needed for VTK-like compatibility with BaseReader."""
        self._observers = []

    def GetProgress(self):
        return self._current_frame / self._n_frames

    def UpdateObservers(self, event_type):
        for event_type_allowed, observer in self._observers:
            if event_type_allowed == event_type:
                observer(self, event_type)

    def Update(self):
        """Read the GIF and store internally to `_data_object`."""
        from PIL import Image, ImageSequence

        img = Image.open(self._filename)
        self._data_object = pyvista.UniformGrid(dims=(img.size[0], img.size[1], 1))

        # load each frame to the grid (RGB since gifs do not support transparency
        self._n_frames = img.n_frames
        for i, frame in enumerate(ImageSequence.Iterator(img)):
            self._current_frame = i
            data = np.array(frame.convert('RGB').getdata(), dtype=np.uint8)
            self._data_object.point_data.set_array(data, f'frame{i}')
            self.UpdateObservers(6)

        if 'frame0' in self._data_object.point_data:
            self._data_object.point_data.active_scalars_name = 'frame0'

    def GetOutputDataObject(self, *args):
        """Needed for VTK-like compatibility with BaseReader."""
        return self._data_object


class GIFReader(BaseReader):
    """GIFReader for .gif files.

    Parameters
    ----------
    path : str
        Path of the GIF to read.

    Examples
    --------
    >>> import pyvista
    >>> from pyvista import examples
    >>> filename = examples.download_gif_simple(load=False)
    >>> filename.split("/")[-1]  # omit the path
    'sample.gif'
    >>> reader = pyvista.get_reader(filename)
    >>> mesh = reader.read()
    >>> mesh.plot(rgba=True, zoom='tight', border=True, border_width=2)

    """

    _class_reader = _GIFReader


CLASS_READERS = {
    # Standard dataset readers:
    '.bmp': BMPReader,
    '.cas': FluentReader,
    '.case': EnSightReader,
    '.cgns': CGNSReader,
    '.dat': TecplotReader,
    '.dcm': DICOMReader,
    '.dem': DEMReader,
    '.facet': FacetReader,
    '.foam': POpenFOAMReader,
    '.g': BYUReader,
    '.gif': GIFReader,
    '.glb': GLTFReader,
    '.gltf': GLTFReader,
    '.img': DICOMReader,
    '.inp': AVSucdReader,
    '.jpg': JPEGReader,
    '.jpeg': JPEGReader,
    '.hdf': HDFReader,
    '.hdr': HDRReader,
    '.mha': MetaImageReader,
    '.mhd': MetaImageReader,
    '.nii': NIFTIReader,
    '.nii.gz': NIFTIReader,
    '.nhdr': NRRDReader,
    '.nrrd': NRRDReader,
    '.obj': OBJReader,
    '.p3d': Plot3DMetaReader,
    '.ply': PLYReader,
    '.png': PNGReader,
    '.pnm': PNMReader,
    '.pts': PTSReader,
    '.pvd': PVDReader,
    '.pvti': XMLPImageDataReader,
    '.pvtk': VTKPDataSetReader,
    '.pvtr': XMLPRectilinearGridReader,
    '.pvtu': XMLPUnstructuredGridReader,
    '.res': MFIXReader,
    '.segy': SegYReader,
    '.sgy': SegYReader,
    '.slc': SLCReader,
    '.stl': STLReader,
    '.tif': TIFFReader,
    '.tiff': TIFFReader,
    '.tri': BinaryMarchingCubesReader,
    '.vti': XMLImageDataReader,
    '.vtk': VTKDataSetReader,
    '.vtm': XMLMultiBlockDataReader,
    '.vtmb': XMLMultiBlockDataReader,
    '.vtp': XMLPolyDataReader,
    '.vtr': XMLRectilinearGridReader,
    '.vts': XMLStructuredGridReader,
    '.vtu': XMLUnstructuredGridReader,
}


# pyvista/utilities/regression.py

"""Image regression module."""
import numpy as np

import pyvista
from pyvista import _vtk


def remove_alpha(img):
    """Remove the alpha channel from ``vtk.vtkImageData``."""
    ec = _vtk.vtkImageExtractComponents()
    ec.SetComponents(0, 1, 2)
    ec.SetInputData(img)
    ec.Update()
    return pyvista.wrap(ec.GetOutput())


def wrap_image_array(arr):
    """Wrap a numpy array as a pyvista.UniformGrid.

    Parameters
    ----------
    arr : np.ndarray
        A ``np.uint8`` ``(X, Y, (3 or 4)`` array.  For example
        ``(768, 1024, 3)``.

    """
    if arr.ndim != 3:
        raise ValueError('Expecting a X by Y by (3 or 4) array')
    if arr.shape[2] not in [3, 4]:
        raise ValueError('Expecting a X by Y by (3 or 4) array')
    if arr.dtype != np.uint8:
        raise ValueError('Expecting a np.uint8 array')

    img = _vtk.vtkImageData()
    img.SetDimensions(arr.shape[1], arr.shape[0], 1)
    wrap_img = pyvista.wrap(img)
    wrap_img.point_data['PNGImage'] = arr[::-1].reshape(-1, arr.shape[2])
    return wrap_img


def image_from_window(ren_win, as_vtk=False, ignore_alpha=False):
    """Extract the image from the render window as an array."""
    width, height = ren_win.GetSize()
    arr = _vtk.vtkUnsignedCharArray()
    ren_win.GetRGBACharPixelData(0, 0, width - 1, height - 1, 0, arr)
    data = _vtk.vtk_to_numpy(arr).reshape(height, width, -1)[::-1]
    if ignore_alpha:
        data = data[:, :, :-1]
    if as_vtk:
        return wrap_image_array(data)
    return data


def compare_images(im1, im2, threshold=1, use_vtk=True):
    """Compare two different images of the same size.

    Parameters
    ----------
    im1 : filename, numpy.ndarray, vtkRenderWindow, or vtkImageData
        Render window, numpy array representing the output of a render
        window, or ``vtkImageData``.

    im2 : filename, numpy.ndarray, vtkRenderWindow, or vtkImageData
        Render window, numpy array representing the output of a render
        window, or ``vtkImageData``.

    threshold : int, optional
        Threshold tolerance for pixel differences.  This should be
        greater than 0, otherwise it will always return an error, even
        on identical images.

    use_vtk : bool, optional
        When disabled, computes the mean pixel error over the entire
        image using numpy.  The difference between pixel is calculated
        for each RGB channel, summed, and then divided by the number
        of pixels.  This is faster than using
        ``vtk.vtkImageDifference`` but potentially less accurate.

    Returns
    -------
    float
        Total error between the images if using ``use_vtk=True``, and
        the mean pixel error when ``use_vtk=False``.

    Examples
    --------
    Compare two active plotters.

    >>> import pyvista
    >>> pl1 = pyvista.Plotter()
    >>> _ = pl1.add_mesh(pyvista.Sphere(), smooth_shading=True)
    >>> pl2 = pyvista.Plotter()
    >>> _ = pl2.add_mesh(pyvista.Sphere(), smooth_shading=False)
    >>> error = pyvista.compare_images(pl1, pl2)

    Compare images from file.

    >>> import pyvista
    >>> img1 = pyvista.read('img1.png')  # doctest:+SKIP
    >>> img2 = pyvista.read('img2.png')  # doctest:+SKIP
    >>> pyvista.compare_images(img1, img2)  # doctest:+SKIP

    """
    from pyvista import Plotter, UniformGrid, read, wrap

    def to_img(img):
        if isinstance(img, UniformGrid):  # pragma: no cover
            return img
        elif isinstance(img, _vtk.vtkImageData):
            return wrap(img)
        elif isinstance(img, str):
            return read(img)
        elif isinstance(img, np.ndarray):
            return wrap_image_array(img)
        elif isinstance(img, Plotter):
            if img._first_time:  # must be rendered first else segfault
                img._on_first_render_request()
                img.render()
            if not hasattr(img, 'ren_win'):
                raise RuntimeError(
                    'Unable to extract image from Plotter as it has already been closed.'
                )
            return image_from_window(img.ren_win, True, ignore_alpha=True)
        else:
            raise TypeError(
                f'Unsupported data type {type(img)}.  Should be '
                'Either a np.ndarray, vtkRenderWindow, or vtkImageData'
            )

    im1 = remove_alpha(to_img(im1))
    im2 = remove_alpha(to_img(im2))

    if im1.GetDimensions() != im2.GetDimensions():
        raise RuntimeError('Input images are not the same size.')

    if use_vtk:
        img_diff = _vtk.vtkImageDifference()
        img_diff.SetThreshold(threshold)
        img_diff.SetInputData(im1)
        img_diff.SetImageData(im2)
        img_diff.AllowShiftOff()  # vastly increases compute time when enabled
        # img_diff.AveragingOff()  # increases compute time
        img_diff.Update()
        return img_diff.GetThresholdedError()

    # otherwise, simply compute the mean pixel difference
    diff = np.abs(im1.point_data[0] - im2.point_data[0])
    return np.sum(diff) / im1.point_data[0].shape[0]


# pyvista/utilities/sphinx_gallery.py

"""Utilities for using pyvista with sphinx-gallery."""

import shutil

import pyvista


def _get_sg_image_scraper():
    """Return the callable scraper to be used by Sphinx-Gallery.

    It allows PyVista users to just use strings as they already can for
    'matplotlib' and 'mayavi'. Details on this implementation can be found in
    `sphinx-gallery/sphinx-gallery/494`_

    .. _sphinx-gallery/sphinx-gallery/494: https://github.com/sphinx-gallery/sphinx-gallery/pull/494
    """
    return Scraper()


class Scraper:
    """
    Save ``pyvista.Plotter`` objects.

    Used by sphinx-gallery to generate the plots from the code in the examples.

    Pass an instance of this class to ``sphinx_gallery_conf`` in your
    ``conf.py`` as the ``"image_scrapers"`` argument.

    Be sure to set ``pyvista.BUILDING_GALLERY = True`` in your ``conf.py``.

    """

    def __call__(self, block, block_vars, gallery_conf):
        """Save the figures generated after running example code.

        Called by sphinx-gallery.

        """
        try:
            from sphinx_gallery.scrapers import figure_rst
        except ImportError:  # pragma: no cover
            raise ImportError('You must install `sphinx_gallery` to use this feature.')

        if not pyvista.BUILDING_GALLERY:
            raise RuntimeError(
                'pyvista.BUILDING_GALLERY must be set to True in your conf.py to capture '
                'images within sphinx_gallery or when building documentation using the '
                'pyvista-plot directive.'
            )

        image_names = list()
        image_path_iterator = block_vars["image_path_iterator"]
        figures = pyvista.plotting._ALL_PLOTTERS
        for _, plotter in figures.items():
            fname = next(image_path_iterator)
            if hasattr(plotter, '_gif_filename'):
                # move gif to fname
                shutil.move(plotter._gif_filename, fname)
            else:
                plotter.screenshot(fname)
            image_names.append(fname)
        pyvista.close_all()  # close and clear all plotters
        return figure_rst(image_names, gallery_conf["src_dir"])


# pyvista/utilities/transformations.py

"""Module implementing point transformations and their matrices."""
import numpy as np


def axis_angle_rotation(axis, angle, point=None, deg=True):
    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 : 3-length sequence
        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 : 3-length sequence, 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, optional
        Whether the angle is specified in degrees. ``False`` implies
        radians.

    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 = np.asarray(axis, dtype='float64')
    if axis.shape != (3,):
        raise ValueError('Axis must be a 3-length array-like.')
    if point is not None:
        point = np.asarray(point)
        if point.shape != (3,):
            raise ValueError('Rotation center must be a 3-length array-like.')

    # check and normalize
    axis_norm = np.linalg.norm(axis)
    if np.isclose(axis_norm, 0):
        raise ValueError('Cannot rotate around zero vector axis.')
    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
    R = np.eye(3) + np.sin(angle) * K + (1 - np.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, point=None):
    """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 : 3-length sequence
        The normal vector of the reflection plane. It need not be a unit
        vector, but it must not be a zero vector.

    point : 3-length sequence, optional
        The origin of the reflection (a reference point through which
        the reflection plane passes). By default the reflection plane
        contains the origin.

    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,):
        raise ValueError('Normal must be a 3-length array-like.')
    if point is not None:
        point = np.asarray(point)
        if point.shape != (3,):
            raise ValueError('Plane reference point must be a 3-length array-like.')

    # check and normalize
    normal_norm = np.linalg.norm(normal)
    if np.isclose(normal_norm, 0):
        raise ValueError('Plane normal cannot be zero.')
    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


def apply_transformation_to_points(transformation, points, inplace=False):
    """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, optional
        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
    >>> 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
    >>> pyvista.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)):
        raise ValueError('`transformation` must be of shape (3, 3) or (4, 4).')

    if points.shape[1] != 3:
        raise ValueError('`points` must be of shape (N, 3).')

    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

    # 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
    else:
        # otherwise return the new points
        return points_2


# pyvista/utilities/wrappers.py

"""Wrapper mapping.

Setting ``pyvista._wrappers`` allows for developers to override the default class used
to coerce a ``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
``vtkPolyData`` objects.

>>> import pyvista
>>> class Foo(pyvista.PolyData):
...     pass  # Extend PolyData here
>>> pyvista._wrappers['vtkPolyData'] = Foo
>>> uniform_grid = pyvista.UniformGrid()
>>> surface = uniform_grid.extract_surface()
>>> assert isinstance(surface, Foo)

"""

import pyvista

_wrappers = {
    'vtkExplicitStructuredGrid': pyvista.ExplicitStructuredGrid,
    'vtkUnstructuredGrid': pyvista.UnstructuredGrid,
    'vtkRectilinearGrid': pyvista.RectilinearGrid,
    'vtkStructuredGrid': pyvista.StructuredGrid,
    'vtkPolyData': pyvista.PolyData,
    'vtkImageData': pyvista.UniformGrid,
    'vtkStructuredPoints': pyvista.UniformGrid,
    'vtkMultiBlockDataSet': pyvista.MultiBlock,
    'vtkTable': pyvista.Table,
    'vtkPointSet': pyvista.PointSet,
    # 'vtkParametricSpline': pyvista.Spline,
}


# pyvista/utilities/xvfb.py

"""Start xvfb from Python."""
import os
import time

XVFB_INSTALL_NOTES = """Please install Xvfb with:

Debian
$ sudo apt install libgl1-mesa-glx xvfb

CentOS / RHL
$ sudo yum install libgl1-mesa-glx xvfb

"""


def start_xvfb(wait=3, window_size=None):
    """Start the virtual framebuffer Xvfb.

    Parameters
    ----------
    wait : float, optional
        Time to wait for the virtual framebuffer to start.  Set to 0
        to disable wait.

    window_size : list, optional
        Window size of the virtual frame buffer.  Defaults to
        :attr:`pyvista.global_theme.window_size
        <pyvista.themes.DefaultTheme.window_size>`.

    Notes
    -----
    Only available on Linux.  Be sure to install ``libgl1-mesa-glx
    xvfb`` in your package manager.

    Examples
    --------
    >>> import pyvista
    >>> pyvista.start_xvfb()  # doctest:+SKIP

    """
    from pyvista import global_theme

    if os.name != 'posix':
        raise OSError('`start_xvfb` is only supported on Linux')

    if os.system('which Xvfb > /dev/null'):
        raise OSError(XVFB_INSTALL_NOTES)

    # use current default window size
    if window_size is None:
        window_size = global_theme.window_size
    window_size_parm = f'{window_size[0]:d}x{window_size[1]:d}x24'
    display_num = ':99'
    os.system(f'Xvfb {display_num} -screen 0 {window_size_parm} > /dev/null 2>&1 &')
    os.environ['DISPLAY'] = display_num
    if wait:
        time.sleep(wait)


# doc/conf.py

import datetime
import locale
import os
import sys

# Otherwise VTK reader issues on some systems, causing sphinx to crash. See also #226.
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")

if sys.version_info >= (3, 0):
    import faulthandler

    faulthandler.enable()

sys.path.insert(0, os.path.abspath("."))
import make_external_gallery
import make_tables

make_external_gallery.make_example_gallery()
make_tables.make_all_tables()

# -- pyvista configuration ---------------------------------------------------
import pyvista

# Manage errors
pyvista.set_error_output_file("errors.txt")
# Ensure that offscreen rendering is used for docs generation
pyvista.OFF_SCREEN = True  # Not necessary - simply an insurance policy
# Preferred plotting style for documentation
pyvista.set_plot_theme("document")
pyvista.global_theme.window_size = [1024, 768]
pyvista.global_theme.font.size = 22
pyvista.global_theme.font.label_size = 22
pyvista.global_theme.font.title_size = 22
pyvista.global_theme.return_cpos = False
pyvista.set_jupyter_backend(None)
# Save figures in specified directory
pyvista.FIGURE_PATH = os.path.join(os.path.abspath("./images/"), "auto-generated/")
if not os.path.exists(pyvista.FIGURE_PATH):
    os.makedirs(pyvista.FIGURE_PATH)

# necessary when building the sphinx gallery
pyvista.BUILDING_GALLERY = True
os.environ['PYVISTA_BUILDING_GALLERY'] = 'true'

# SG warnings
import warnings

warnings.filterwarnings(
    "ignore",
    category=UserWarning,
    message="Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure.",
)


# -- General configuration ------------------------------------------------
numfig = False
html_logo = "./_static/pyvista_logo_sm.png"

sys.path.append(os.path.abspath("./_ext"))

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    "sphinx.ext.intersphinx",
    "sphinx.ext.autodoc",
    "sphinx.ext.doctest",
    "sphinx.ext.autosummary",
    "enum_tools.autoenum",
    "notfound.extension",
    "sphinx_copybutton",
    "sphinx_gallery.gen_gallery",
    "sphinx.ext.extlinks",
    "jupyter_sphinx",
    "sphinx_panels",
    "pyvista.ext.plot_directive",
    "pyvista.ext.coverage",
    "numpydoc",
    "sphinx.ext.viewcode",
]

# Configuration of pyvista.ext.coverage
coverage_additional_modules = [
    'pyvista',
    'pyvista.themes',
    'pyvista.plotting.axes',
    'pyvista.plotting.camera',
    'pyvista.plotting.charts',
    'pyvista.plotting.helpers',
    'pyvista.plotting.lights',
    'pyvista.plotting.picking',
    'pyvista.plotting.plotting',
    'pyvista.plotting.renderer',
    'pyvista.plotting.renderers',
    'pyvista.plotting.scalar_bars',
    'pyvista.plotting.theme',
    'pyvista.plotting.tools',
    'pyvista.plotting.widgets',
    'pyvista.core.composite',
    'pyvista.core.dataobject',
    'pyvista.core.datasetattributes',
    'pyvista.core.dataset',
    'pyvista.core.errors',
    'pyvista.core.grid',
    'pyvista.core.objects',
    'pyvista.core.pointset',
    'pyvista.core.pyvista_ndarray',
    'pyvista.core.filters.composite',
    'pyvista.core.filters.data_set',
    'pyvista.core.filters.poly_data',
    'pyvista.core.filters.structured_grid',
    'pyvista.core.filters.uniform_grid',
    'pyvista.core.filters.unstructured_grid',
    'pyvista.demos',
    'pyvista.examples.examples',
    'pyvista.utilities.common',
    'pyvista.utilities.features',
    'pyvista.utilities.fileio',
    'pyvista.utilities.geometric_objects',
    'pyvista.utilities.parametric_objects',
    'pyvista.utilities.reader',
    'pyvista.utilities.xvfb',
    'pyvista.utilities.transformations',
    'pyvista.utilities.regression',
]
coverage_ignore_modules = [
    r'\.plot_directive$',  # Issue with class parameter documentation
]


# Configuration for sphinx.ext.autodoc
# Do not expand following type aliases when generating the docs
autodoc_type_aliases = {"color_like": "pyvista.color_like"}


# See https://numpydoc.readthedocs.io/en/latest/install.html
numpydoc_use_plots = True
numpydoc_show_class_members = False
numpydoc_xref_param_type = True

# see https://github.com/pyvista/pyvista/pull/1612
numpydoc_validate = True
numpydoc_validation_checks = {
    "all",  # all but the following:
    "GL01",  # Contradicts numpydoc examples
    "GL02",  # Permit a blank line after the end of our docstring
    "GL03",  # Considering enforcing
    "SA01",  # Not all docstrings need a see also
    "SA04",  # See also section does not need descriptions
    "SS05",  # Appears to be broken.
    "ES01",  # Not all docstrings need an extend summary.
    "EX01",  # Examples: Will eventually enforce
    "YD01",  # Yields: No plan to enforce
}
numpydoc_validation_exclude = {  # set of regex
    r'\.Plotter$',  # Issue with class parameter documentation
    r'\.WidgetHelper$',
    r'\.from_dict$',
    r'\.to_dict$',
    r'\.__init__$',
    r'\.__new__$',
    # parm of abstract classes
    r'\.CompositeFilters$',
    r'\.DataObject$',
    r'\.DataSet$',
    r'\.DataSetFilters$',
    r'\.ExplicitStructuredGrid$',
    r'\.Grid$',
    r'\.MultiBlock$',
    r'\.PointGrid$',
    r'\.PointSet$',
    r'\.PolyDataFilters$',
    r'\.RectilinearGrid$',
    r'\.StructuredGrid$',
    r'\.Table$',
    r'\.Table\.save$',
    r'\.UniformGrid$',
    r'\.UniformGridFilters$',
    r'\.UnstructuredGrid$',
    r'\.UnstructuredGridFilters$',
    # classes inherit from BaseReader
    r'.*Reader(\.|$)',
    # internal
    r'\.Renderer$',
    # deprecated
    r'\.boolean_add$',
    r'\.boolean_cut$',
    r'\.add_field_array$',
    r'\.DataSetAttributes\.append$',
    # methods we probably should make private
    r'\.store_click_position$',
    r'\.store_mouse_position$',
    r'\.fly_to_mouse_position$',
    r'\.key_press_event$',
    r'\.left_button_down$',
    # MISC
    r'\.PlotterITK$',
    r'\.MultiBlock\.copy_meta_from$',
    r'\.DataObject\.copy_meta_from$',
    # wraps
    r'\.Plotter\.enable_depth_peeling$',
    r'\.add_scalar_bar$',
    # called from inherited
    r'\.Table\.copy_meta_from$',
    # Type alias
    r'\.color_like$',
    # Mixin methods from collections.abc
    r'\.MultiBlock\.clear$',
    r'\.MultiBlock\.count$',
    r'\.MultiBlock\.index$',
    r'\.MultiBlock\.remove$',
    # Enumerations
    r'\.Plot3DFunctionEnum$',
}


add_module_names = False

# Intersphinx mapping
# NOTE: if these are changed, then doc/intersphinx/update.sh
# must be changed accordingly to keep auto-updated mappings working
intersphinx_mapping = {
    'python': ('https://docs.python.org/3', (None, 'intersphinx/python-objects.inv')),
    'scipy': (
        'https://docs.scipy.org/doc/scipy/',
        (None, 'intersphinx/scipy-objects.inv'),
    ),
    'numpy': ('https://numpy.org/doc/stable', (None, 'intersphinx/numpy-objects.inv')),
    'matplotlib': ('https://matplotlib.org/stable', (None, 'intersphinx/matplotlib-objects.inv')),
    'imageio': (
        'https://imageio.readthedocs.io/en/stable',
        (None, 'intersphinx/imageio-objects.inv'),
    ),
    'pandas': (
        'https://pandas.pydata.org/pandas-docs/stable',
        (None, 'intersphinx/pandas-objects.inv'),
    ),
    'pytest': ('https://docs.pytest.org/en/stable', (None, 'intersphinx/pytest-objects.inv')),
    'pyvistaqt': ('https://qtdocs.pyvista.org/', (None, 'intersphinx/pyvistaqt-objects.inv')),
}
intersphinx_timeout = 10

linkcheck_retries = 3
linkcheck_timeout = 500

# Select if we want to generate production or dev documentation
#
# Generate class table auto-summary when enabled. This generates one page per
# class method or attribute and should be used with the production
# documentation, but local builds and PR commits can get away without this as
# it takes ~4x as long to generate the documentation.
templates_path = ["_templates"]
if os.environ.get('FULL_DOC_BUILD', '').upper() == 'TRUE':
    templates_path.append("_templates_full")
else:
    theme = pyvista.themes.DocumentTheme()
    theme.window_size = [400, 300]
    pyvista.PLOT_DIRECTIVE_THEME = theme
    templates_path.append("_templates_basic")

# The suffix(es) of source filenames.
source_suffix = ".rst"

# The main toctree document.
master_doc = "index"


# General information about the project.
project = "PyVista"
year = datetime.date.today().year
copyright = f"2017-{year}, The PyVista Developers"
author = "Alex Kaszynski and Bane Sullivan"

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = pyvista.__version__

# The full version, including alpha/beta/rc tags.
release = pyvista.__version__

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints", "_templates*"]

# Pages are not detected correct by ``make linkcheck``
linkcheck_ignore = [
    'https://data.kitware.com/#collection/55f17f758d777f6ddc7895b7/folder/5afd932e8d777f15ebe1b183',
    'https://www.sciencedirect.com/science/article/abs/pii/S0309170812002564',
    'https://www.researchgate.net/publication/2926068_LightKit_A_lighting_system_for_effective_visualization',
]

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "friendly"

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- Sphinx Gallery Options
from sphinx_gallery.sorting import FileNameSortKey


class ResetPyVista:
    """Reset pyvista module to default settings."""

    def __call__(self, gallery_conf, fname):
        """Reset pyvista module to default settings

        If default documentation settings are modified in any example, reset here.
        """
        import pyvista

        pyvista._wrappers['vtkPolyData'] = pyvista.PolyData
        pyvista.set_plot_theme('document')

    def __repr__(self):
        return 'ResetPyVista'


reset_pyvista = ResetPyVista()


# skip building the osmnx example if osmnx is not installed
has_osmnx = False
try:
    import osmnx, fiona  # noqa: F401,E401 isort: skip

    has_osmnx = True
except:  # noqa: E722
    pass


sphinx_gallery_conf = {
    # convert rst to md for ipynb
    "pypandoc": True,
    # path to your examples scripts
    "examples_dirs": ["../examples/"],
    # path where to save gallery generated examples
    "gallery_dirs": ["examples"],
    # Pattern to search for example files
    "filename_pattern": r"\.py" if has_osmnx else r"(?!osmnx-example)\.py",
    # Remove the "Download all examples" button from the top level gallery
    "download_all_examples": False,
    # Remove sphinx configuration comments from code blocks
    "remove_config_comments": True,
    # Sort gallery example by file name instead of number of lines (default)
    "within_subsection_order": FileNameSortKey,
    # directory where function granular galleries are stored
    "backreferences_dir": None,
    # Modules for which function level galleries are created.  In
    "doc_module": "pyvista",
    "image_scrapers": ("pyvista", "matplotlib"),
    "first_notebook_cell": (
        "%matplotlib inline\n" "from pyvista import set_plot_theme\n" "set_plot_theme('document')\n"
    ),
    "reset_modules": (reset_pyvista,),
    "reset_modules_order": "both",
}

import re

# -- .. pyvista-plot:: directive ----------------------------------------------
from numpydoc.docscrape_sphinx import SphinxDocString

IMPORT_PYVISTA_RE = r'\b(import +pyvista|from +pyvista +import)\b'
IMPORT_MATPLOTLIB_RE = r'\b(import +matplotlib|from +matplotlib +import)\b'

plot_setup = """
from pyvista import set_plot_theme as __s_p_t
__s_p_t('document')
del __s_p_t
"""
plot_cleanup = plot_setup


def _str_examples(self):
    examples_str = "\n".join(self['Examples'])

    if (
        self.use_plots
        and re.search(IMPORT_MATPLOTLIB_RE, examples_str)
        and 'plot::' not in examples_str
    ):
        out = []
        out += self._str_header('Examples')
        out += ['.. plot::', '']
        out += self._str_indent(self['Examples'])
        out += ['']
        return out
    elif re.search(IMPORT_PYVISTA_RE, examples_str) and 'plot-pyvista::' not in examples_str:
        out = []
        out += self._str_header('Examples')
        out += ['.. pyvista-plot::', '']
        out += self._str_indent(self['Examples'])
        out += ['']
        return out
    else:
        return self._str_section('Examples')


SphinxDocString._str_examples = _str_examples


# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
import pydata_sphinx_theme  # noqa

html_theme = "pydata_sphinx_theme"
# html_theme_path = [pydata_sphinx_theme.get_html_theme_path()]
html_context = {
    "github_user": "pyvista",
    "github_repo": "pyvista",
    "github_version": "main",
    "doc_path": "doc",
}

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
    "google_analytics_id": "UA-140243896-1",
    "show_prev_next": False,
    "github_url": "https://github.com/pyvista/pyvista",
    "icon_links": [
        {
            "name": "Slack Community",
            "url": "http://slack.pyvista.org",
            "icon": "fab fa-slack",
        },
        {
            "name": "Support",
            "url": "https://github.com/pyvista/pyvista/discussions",
            "icon": "fa fa-comment fa-fw",
        },
        {
            "name": "Contributing",
            "url": "https://github.com/pyvista/pyvista/blob/main/CONTRIBUTING.rst",
            "icon": "fa fa-gavel fa-fw",
        },
        {
            "name": "The Paper",
            "url": "https://doi.org/10.21105/joss.01450",
            "icon": "fa fa-file-text fa-fw",
        },
    ],
}

# sphinx-panels shouldn't add bootstrap css since the pydata-sphinx-theme
# already loads it
panels_add_bootstrap_css = False

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]


# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = "pyvistadoc"


# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #
    # 'papersize': 'letterpaper',
    # The font size ('10pt', '11pt' or '12pt').
    #
    # 'point_size': '10pt',
    # Additional stuff for the LaTeX preamble.
    #
    # 'preamble': '',
    # Latex figure (float) alignment
    #
    # 'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, "pyvista.tex", "pyvista Documentation", author, "manual"),
]

# -- Options for gettext output -------------------------------------------

# To specify names to enable gettext extracting and translation applying for i18n additionally. You can specify below names:
gettext_additional_targets = ["raw"]

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "pyvista", "pyvista Documentation", [author], 1)]


# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (
        master_doc,
        "pyvista",
        "pyvista Documentation",
        author,
        "pyvista",
        "A Streamlined Python Interface for the Visualization Toolkit",
        "Miscellaneous",
    ),
]

# -- Custom 404 page

notfound_context = {
    "body": '<h1>Page not found.</h1>\n\nPerhaps try the <a href="http://docs.pyvista.org/examples/index.html">examples page</a>.',
}
notfound_no_urls_prefix = True


# Copy button customization ---------------------------------------------------
# exclude traditional Python prompts from the copied code
copybutton_prompt_text = r'>>> ?|\.\.\. '
copybutton_prompt_is_regexp = True


def setup(app):
    app.add_css_file("copybutton.css")
    app.add_css_file("no_search_highlight.css")
    app.add_css_file("summary.css")


# doc/make_external_gallery.py

"""A helper script to generate the external examples gallery."""
from io import StringIO
import os


def format_icon(title, description, link, image):
    body = r"""

.. raw:: html

    <div class="sphx-glr-thumbcontainer" tooltip="{}">

.. only:: html

    .. figure:: ../images/external-examples/{}
       :target: {}

       {}




.. raw:: html

    </div>


.. toctree::
   :hidden:

   {} <{}>



"""
    content = body.format(description, image, link, title, title, link)
    return content


class Example:
    def __init__(self, title, description, link, image):
        self.title = title
        self.description = description
        self.link = link
        self.image = image

    def format(self):
        return format_icon(self.title, self.description, self.link, self.image)


###############################################################################

articles = dict(
    omf=Example(
        title="3D visualization for the Open Mining Format (omf)",
        description="3D visualization for the Open Mining Format (omf)",
        link="https://opengeovis.github.io/omfvista/examples/index.html",
        image="omfvista.png",
    ),
    discretize=Example(
        title="3D Rendering with Discretize",
        description="3D Rendering with Discretize",
        link="http://discretize.simpeg.xyz/en/main/examples/plot_pyvista_laguna.html",
        image="discretize.png",
    ),
    open_foam=Example(
        title="OpenFOAM Rendering",
        description="OpenFOAM Rendering",
        link="https://pswpswpsw.github.io/posts/2018/09/blog-post-modify-vtk-openfoam/",
        image="open-foam.png",
    ),
    aero_sandbox=Example(
        title="AeroSandbox",
        description="AeroSandbox",
        link="https://peterdsharpe.github.io/AeroSandbox/",
        image="AeroSandbox.png",
    ),
    forge=Example(
        title="FORGE Geothermal Project",
        description="FORGE Geothermal Project",
        link="https://forge.pvgeo.org/project/index.html",
        image="forge.png",
    ),
    pvgeo=Example(
        title="PVGeo's example gallery",
        description="PVGeo's example gallery",
        link="https://pvgeo.org/examples/index.html",
        image="pvgeo.png",
    ),
    tetgen=Example(
        title="TetGen's example gallery",
        description="TetGen's example gallery",
        link="http://tetgen.pyvista.org/examples/index.html",
        image="tetgen.png",
    ),
    mesh_fix=Example(
        title="PyMeshFix's example gallery",
        description="PyMeshFix's example gallery",
        link="http://pymeshfix.pyvista.org/examples/index.html",
        image="pymeshfix.png",
    ),
    orvisu=Example(
        title="Orvisu Demo Application",
        description="Orvisu Demo Application",
        link="https://github.com/BartheG/Orvisu",
        image="orvisu.gif",
    ),
    flem=Example(
        title="FLEM: A diffusive landscape evolution model",
        description="FLEM: A diffusive landscape evolution model",
        link="https://github.com/johnjarmitage/flem",
        image="flem.png",
    ),
    optimization=Example(
        title="Optimization visualization with PyVista",
        description="Optimization visualization with PyVista",
        link="https://gist.github.com/hichamjanati/6668d91848283c31ac18d801552fb582",
        image="optimization.gif",
    ),
    anvil_cirrus_plumes=Example(
        title="Anvil Cirrus Plumes",
        description="Dr. Morgan O'Neill at Stanford University is researching Above Anvil Cirrus Plumes and its dynamics as a hydraulic jump.",
        link="https://www.youtube.com/watch?v=cCPjnF_vHxw&feature=youtu.be",
        image="anvil_cirrus_plumes.png",
    ),
    damavand=Example(
        title="Damavand Volcano",
        description="Visualize 3D models of Damavand Volcano, Alborz, Iran.",
        link="https://nbviewer.jupyter.org/github/banesullivan/damavand-volcano/blob/master/Damavand_Volcano.ipynb",
        image="damavand_volcano.gif",
    ),
    atmos_conv=Example(
        title="Atmospheric Convection",
        description="Atmospheric convection plays a key role in the climate of tidally-locked terrestrial exoplanets: insights from high-resolution simulations",
        link="https://dennissergeev.github.io/exoconvection-apj-2020/",
        image="atmospheric_convection.jpeg",
    ),
    vessel_vio=Example(
        title="VesselVio",
        description="An open-source application for vasculature dataset analysis and visualization",
        link="https://jacobbumgarner.github.io/VesselVio/",
        image="vessel_vio.png",
    ),
    pyvista_artworks=Example(
        title="Stéphane Laurent's artwork",
        description="Stéphane Laurent's blog showing a sample of the animations they realized with PyVista.",
        link="https://laustep.github.io/stlahblog/posts/MyPyVistaArtworks.html",
        image="Duoprism_3-30.gif",
    ),
    ptera_software=Example(
        title="PteraSoftware",
        description="Ptera Software is a fast, easy-to-use, and open-source software package for analyzing flapping-wing flight.",
        link="https://github.com/camurban/pterasoftware",
        image="ptera_software.gif",
    ),
    geemap=Example(
        title="geemap",
        description="A Python package for interactive mapping with Google Earth Engine, ipyleaflet, and ipywidgets.",
        link="https://geemap.org/",
        image="geemap.gif",
    ),
    gmshmodel=Example(
        title="GmshModel",
        description="A mesh modeling interface to the Gmsh-Python-API",
        link="https://gmshmodel.readthedocs.io/en/latest/",
        image="gmsh_model.png",
    ),
    grad_descent_visualizer=Example(
        title="Gradient Descent Visualizer",
        description="A Python package used to visualize the gradient descent of function landscapes.",
        link="https://github.com/JacobBumgarner/grad-descent-visualizer",
        image="grad_descent_visualizer.gif",
    ),
    # entry=Example(title="",
    #     description="",
    #     link="",
    #     image=""),
)


###############################################################################


def make_example_gallery():
    """Make the example gallery."""
    path = "./getting-started/external_examples.rst"

    with StringIO() as new_fid:
        new_fid.write(
            """
External Examples
=================

Here are a list of longer, more technical examples of what PyVista can do!

.. caution::

    Please note that these examples link to external websites.
    If any of these links are broken, please raise an issue on the repository.


Do you have a technical processing workflow or visualization routine you
would like to share?
If so, please consider sharing your work here submitting a PR at
https://github.com/pyvista and we would be glad to add it!



"""
        )
        # Reverse to put the latest items at the top
        for example in list(articles.values())[::-1]:
            new_fid.write(example.format())

        new_fid.write(
            """

.. raw:: html

    <div class="sphx-glr-clear"></div>


"""
        )
        new_fid.seek(0)
        new_text = new_fid.read()

    # check if it's necessary to overwrite the table
    existing = ""
    if os.path.exists(path):
        with open(path) as existing_fid:
            existing = existing_fid.read()

    # write if different or does not exist
    if new_text != existing:
        with open(path, "w") as fid:
            fid.write(new_text)

    return


# doc/make_tables.py

"""This is a helper module to generate tables that can be included in the documentation."""
import io
import os
import textwrap

import pyvista as pv

# Paths to directories in which resulting rst files and images are stored.
CHARTS_TABLE_DIR = "api/plotting/charts"
CHARTS_IMAGE_DIR = "images/charts"
COLORS_TABLE_DIR = "api/utilities"


def _aligned_dedent(txt):
    """Custom variant of `textwrap.dedent`.

    Helper method to dedent the provided text up to the special alignment character ``'|'``.
    """
    return textwrap.dedent(txt).replace('|', '')


class DocTable:
    """Helper class to create tables for the documentation.

    The ``generate`` method creates the table rst file (and possibly any other
    files, such as images, used by the table). This method internally calls
    the ``fetch_data``, ``get_header`` and ``get_row`` methods, which should be
    provided by any subclass.
    Each table is generated from a list of 'row_data' provided by the ``fetch_data``
    method. The ``get_header`` and ``get_row`` methods generate the required rst
    for the table's header and table's rows respectively.
    """

    path = None  # Path to the rst file to which the table will be written

    @classmethod
    def generate(cls):
        """Generate this table."""
        assert cls.path is not None, "Subclasses should specify a path."
        data = cls.fetch_data()

        with io.StringIO() as fnew:
            fnew.write(cls.get_header(data))
            for i, row_data in enumerate(data):
                row = cls.get_row(i, row_data)
                if row is not None:
                    fnew.write(row)

            # if file exists, verify that we have no new content
            fnew.seek(0)
            new_txt = fnew.read()

        # determine if existing file needs to be rewritten
        if os.path.exists(cls.path):
            with open(cls.path) as fold:
                orig_txt = fold.read()
            if orig_txt == new_txt:
                new_txt = ''

        # write if there is any text to write. This avoids resetting the documentation cache
        if new_txt:
            with open(cls.path, 'w') as fout:
                fout.write(new_txt)

        pv.close_all()

    @classmethod
    def fetch_data(cls):
        """Get a list of row_data used to generate the table."""
        raise NotImplementedError("Subclasses should specify a fetch_data method.")

    @classmethod
    def get_header(cls, data):
        """Get the table's header rst."""
        raise NotImplementedError("Subclasses should specify a table header.")

    @classmethod
    def get_row(cls, i, row_data):
        """Get the rst for the given row. Can return ``None`` if no row should
        be generated for the provided ``row_data``."""
        raise NotImplementedError("Subclasses should specify a get_row method.")


class LineStyleTable(DocTable):
    """Class to generate line style table."""

    path = f"{CHARTS_TABLE_DIR}/pen_line_styles.rst"
    header = _aligned_dedent(
        """
        |.. list-table:: Line styles
        |   :widths: 20 40 40
        |   :header-rows: 1
        |
        |   * - Style
        |     - Description
        |     - Example
        """
    )
    row_template = _aligned_dedent(
        """
        |   * - ``"{}"``
        |     - {}
        |     - .. image:: /{}
        """
    )

    @classmethod
    def fetch_data(cls):
        # Fetch table data from ``LINE_STYLES`` dictionary.
        return [{"style": ls, **data} for (ls, data) in pv.charts.Pen.LINE_STYLES.items()]

    @classmethod
    def get_header(cls, data):
        return cls.header

    @classmethod
    def get_row(cls, i, row_data):
        if row_data["descr"] is None:
            return None  # Skip line style if description is set to ``None``.
        else:
            # Create an image from the given line style and generate the row rst.
            img_path = f"{CHARTS_IMAGE_DIR}/ls_{i}.png"
            cls.generate_img(row_data["style"], img_path)
            return cls.row_template.format(row_data["style"], row_data["descr"], img_path)

    @staticmethod
    def generate_img(line_style, img_path):
        """Generate and save an image of the given line_style."""
        p = pv.Plotter(off_screen=True, window_size=[100, 50])
        p.background_color = 'w'
        chart = pv.Chart2D()
        chart.line([0, 1], [0, 0], color="b", width=3.0, style=line_style)
        chart.hide_axes()
        p.add_chart(chart)

        # Generate and crop the image
        _, img = p.show(screenshot=True, return_cpos=True)
        img = img[18:25, 22:85, :]

        # exit early if the image already exists and is the same
        if os.path.isfile(img_path) and pv.compare_images(img, img_path) < 1:
            return

        # save it
        p._save_image(img, img_path, False)


class MarkerStyleTable(DocTable):
    """Class to generate marker style table."""

    path = f"{CHARTS_TABLE_DIR}/scatter_marker_styles.rst"
    header = _aligned_dedent(
        """
        |.. list-table:: Marker styles
        |   :widths: 20 40 40
        |   :header-rows: 1
        |
        |   * - Style
        |     - Description
        |     - Example
        """
    )
    row_template = _aligned_dedent(
        """
        |   * - ``"{}"``
        |     - {}
        |     - .. image:: /{}
        """
    )

    @classmethod
    def fetch_data(cls):
        # Fetch table data from ``MARKER_STYLES`` dictionary.
        return [
            {"style": ms, **data} for (ms, data) in pv.charts.ScatterPlot2D.MARKER_STYLES.items()
        ]

    @classmethod
    def get_header(cls, data):
        return cls.header

    @classmethod
    def get_row(cls, i, row_data):
        if row_data["descr"] is None:
            return None  # Skip marker style if description is set to ``None``.
        else:
            # Create an image from the given marker style and generate the row rst.
            img_path = f"{CHARTS_IMAGE_DIR}/ms_{i}.png"
            cls.generate_img(row_data["style"], img_path)
            return cls.row_template.format(row_data["style"], row_data["descr"], img_path)

    @staticmethod
    def generate_img(marker_style, img_path):
        """Generate and save an image of the given marker_style."""
        p = pv.Plotter(off_screen=True, window_size=[100, 100])
        p.background_color = 'w'
        chart = pv.Chart2D()
        chart.scatter([0], [0], color="b", size=9, style=marker_style)
        chart.hide_axes()
        p.add_chart(chart)

        # generate and crop the image
        _, img = p.show(screenshot=True, return_cpos=True)
        img = img[40:53, 47:60, :]

        # exit early if the image already exists and is the same
        if os.path.isfile(img_path) and pv.compare_images(img, img_path) < 1:
            return

        # save it
        p._save_image(img, img_path, False)


class ColorSchemeTable(DocTable):
    """Class to generate color scheme table."""

    path = f"{CHARTS_TABLE_DIR}/plot_color_schemes.rst"
    header = _aligned_dedent(
        """
        |.. list-table:: Color schemes
        |   :widths: 15 50 5 30
        |   :header-rows: 1
        |
        |   * - Color scheme
        |     - Description
        |     - # colors
        |     - Example
        """
    )
    row_template = _aligned_dedent(
        """
        |   * - ``"{}"``
        |     - {}
        |     - {}
        |     - .. image:: /{}
        """
    )

    @classmethod
    def fetch_data(cls):
        # Fetch table data from ``COLOR_SCHEMES`` dictionary.
        return [
            {"scheme": cs, **data} for (cs, data) in pv.charts._MultiCompPlot.COLOR_SCHEMES.items()
        ]

    @classmethod
    def get_header(cls, data):
        return cls.header

    @classmethod
    def get_row(cls, i, row_data):
        if row_data["descr"] is None:
            return None  # Skip color scheme if description is set to ``None``.
        else:
            # Create an image from the given color scheme and generate the row rst.
            img_path = f"{CHARTS_IMAGE_DIR}/cs_{i}.png"
            n_colors = cls.generate_img(row_data["scheme"], img_path)
            return cls.row_template.format(
                row_data["scheme"], row_data["descr"], n_colors, img_path
            )

    @staticmethod
    def generate_img(color_scheme, img_path):
        """Generate and save an image of the given color_scheme."""
        p = pv.Plotter(off_screen=True, window_size=[240, 120])
        p.background_color = 'w'
        chart = pv.Chart2D()
        # Use a temporary plot to determine the total number of colors in this scheme
        tmp_plot = chart.bar([0], [[1]] * 2, color=color_scheme, orientation="H")
        n_colors = len(tmp_plot.colors)
        plot = chart.bar([0], [[1]] * n_colors, color=color_scheme, orientation="H")
        chart.remove_plot(tmp_plot)
        plot.pen.color = 'w'
        chart.x_range = [0, n_colors]
        chart.hide_axes()
        p.add_chart(chart)

        # Generate and crop the image
        _, img = p.show(screenshot=True, return_cpos=True)
        img = img[34:78, 22:225, :]

        # exit early if the image already exists and is the same
        if os.path.isfile(img_path) and pv.compare_images(img, img_path) < 1:
            return n_colors

        # save it
        p._save_image(img, img_path, False)

        return n_colors


class ColorTable(DocTable):
    """Class to generate colors table."""

    path = f"{COLORS_TABLE_DIR}/colors.rst"
    header = _aligned_dedent(
        """
        |.. list-table::
        |   :widths: 50 20 30
        |   :header-rows: 1
        |
        |   * - Name
        |     - Hex value
        |     - Example
        """
    )
    row_template = _aligned_dedent(
        """
        |   * - {}
        |     - ``{}``
        |     - .. raw:: html
        |
        |          <span style='width:100%; height:100%; display:block; background-color: {};'>&nbsp;</span>
        """
    )

    @classmethod
    def fetch_data(cls):
        # Fetch table data from ``hexcolors`` dictionary.
        colors = {
            name: {"name": name, "hex": hex, "synonyms": []} for name, hex in pv.hexcolors.items()
        }
        # Add synonyms defined in ``color_synonyms`` dictionary.
        for s, name in pv.colors.color_synonyms.items():
            colors[name]["synonyms"].append(s)
        return colors.values()

    @classmethod
    def get_header(cls, data):
        return cls.header

    @classmethod
    def get_row(cls, i, row_data):
        name_template = '``"{}"``'
        names = [row_data["name"]] + row_data["synonyms"]
        name = " or ".join(name_template.format(n) for n in names)
        return cls.row_template.format(name, row_data["hex"], row_data["hex"])


def make_all_tables():
    os.makedirs(CHARTS_IMAGE_DIR, exist_ok=True)
    LineStyleTable.generate()
    MarkerStyleTable.generate()
    ColorSchemeTable.generate()
    ColorTable.generate()


# pyvista/__init__.py

"""PyVista package for 3D plotting and mesh analysis."""

MAX_N_COLOR_BARS = 10

from typing import Optional
import warnings
import os
import appdirs

# Load default theme.  Must be loaded first.
from pyvista._version import __version__
from pyvista.plotting import *
from pyvista.utilities import *
from pyvista.core import *
from pyvista.utilities.misc import (
    _get_vtk_id_type,
    vtk_version_info,
    _set_plot_theme_from_env,
    set_pickle_format,
)
from pyvista import _vtk
from pyvista.jupyter import set_jupyter_backend, PlotterITK
from pyvista.themes import set_plot_theme, load_theme, _rcParams
from pyvista.themes import DefaultTheme as _GlobalTheme  # hide this

# Per contract with Sphinx-Gallery, this method must be available at top level
from pyvista.utilities.sphinx_gallery import _get_sg_image_scraper

from pyvista.utilities.wrappers import _wrappers

global_theme = _GlobalTheme()
rcParams = _rcParams()  # raises DeprecationError when used

# Set preferred plot theme
_set_plot_theme_from_env()

# get the int type from vtk
ID_TYPE = _get_vtk_id_type()

# determine if using at least vtk 5.0.0
if vtk_version_info.major < 5:  # pragma: no cover
    from pyvista.core.errors import VTKVersionError

    raise VTKVersionError('VTK version must be 5.0 or greater.')

# catch annoying numpy/vtk future warning:
warnings.simplefilter(action='ignore', category=FutureWarning)

# A simple flag to set when generating the documentation
OFF_SCREEN = os.environ.get("PYVISTA_OFF_SCREEN", "false").lower() == "true"

# If available, a local vtk-data instance will be used for examples
VTK_DATA_PATH: Optional[str] = None
if 'PYVISTA_VTK_DATA' in os.environ:
    VTK_DATA_PATH = os.environ['PYVISTA_VTK_DATA']
    if not os.path.isdir(VTK_DATA_PATH):
        warnings.warn(f"VTK_DATA_PATH: {VTK_DATA_PATH} is an invalid path")
    if not os.path.isdir(os.path.join(VTK_DATA_PATH, 'Data')):
        warnings.warn(f"VTK_DATA_PATH: {os.path.join(VTK_DATA_PATH, 'Data')} does not exist")

# flag for when building the sphinx_gallery
BUILDING_GALLERY = False
if 'PYVISTA_BUILDING_GALLERY' in os.environ:
    if os.environ['PYVISTA_BUILDING_GALLERY'].lower() == 'true':
        BUILDING_GALLERY = True

# A threshold for the max cells to compute a volume for when repr-ing
REPR_VOLUME_MAX_CELLS = 1e6

# Set where figures are saved
FIGURE_PATH = None


# allow user to override the examples path
if 'PYVISTA_USERDATA_PATH' in os.environ:
    USER_DATA_PATH = os.environ['PYVISTA_USERDATA_PATH']
    if not os.path.isdir(USER_DATA_PATH):
        raise FileNotFoundError(f'Invalid PYVISTA_USERDATA_PATH at {USER_DATA_PATH}')

else:
    USER_DATA_PATH = appdirs.user_data_dir('pyvista')
    try:
        # Set up data directory
        os.makedirs(USER_DATA_PATH, exist_ok=True)
    except Exception as e:
        warnings.warn(
            f'Unable to create `PYVISTA_USERDATA_PATH` at "{USER_DATA_PATH}"\n'
            f'Error: {e}\n\n'
            'Override the default path by setting the environmental variable '
            '`PYVISTA_USERDATA_PATH` to a writable path.'
        )
        USER_DATA_PATH = ''

EXAMPLES_PATH = os.path.join(USER_DATA_PATH, 'examples')
try:
    os.makedirs(EXAMPLES_PATH, exist_ok=True)
except Exception as e:
    warnings.warn(
        f'Unable to create `EXAMPLES_PATH` at "{EXAMPLES_PATH}"\n'
        f'Error: {e}\n\n'
        'Override the default path by setting the environmental variable '
        '`PYVISTA_USERDATA_PATH` to a writable path.'
    )
    EXAMPLES_PATH = ''

# Send VTK messages to the logging module:
send_errors_to_logging()

# theme to use by default for the plot directive
PLOT_DIRECTIVE_THEME = None

# Set a parameter to control default print format for floats outside
# of the plotter
FLOAT_FORMAT = "{:.3e}"

# Serialization format to be used when pickling `DataObject`
PICKLE_FORMAT = 'xml'


# pyvista/_typing.py

"""Type aliases for type hints."""

from __future__ import annotations

from typing import TYPE_CHECKING, Dict, List, Sequence, Tuple, Union

import numpy as np

from . import _vtk

if TYPE_CHECKING:  # pragma: no cover
    from .plotting.colors import Color

Vector = Union[List[float], Tuple[float, float, float], np.ndarray]
VectorArray = Union[np.ndarray, Sequence[Vector]]
Number = Union[float, int, np.number]
NumericArray = Union[Sequence[Number], np.ndarray]
color_like = Union[
    Tuple[int, int, int],
    Tuple[int, int, int, int],
    Tuple[float, float, float],
    Tuple[float, float, float, float],
    Sequence[int],
    Sequence[float],
    np.ndarray,
    Dict[str, Union[int, float, str]],
    str,
    "Color",
    _vtk.vtkColor3ub,
]
# Overwrite default docstring, as sphinx is not able to capture the docstring
# when it is put beneath the definition somehow?
color_like.__doc__ = """Any object convertible to a :class:`Color`."""


# pyvista/_version.py

"""Version info for pyvista.

On the ``main`` branch, use 'dev0' to denote a development version.
For example:

version_info = 0, 27, 'dev0'

---

When generating pre-release wheels, use '0rcN', for example:

version_info = 0, 28, '0rc1'

Denotes the first release candidate.

"""
# major, minor, patch
version_info = 0, 37, 'dev0'

# Nice string for the version
__version__ = '.'.join(map(str, version_info))


# pyvista/_vtk.py

"""
Import from vtk.

These are the modules within vtk that must be loaded across pyvista.
Here, we attempt to import modules using the VTK9 ``vtkmodules``
package, which lets us only have to import from select modules and not
the entire library.

"""
# flake8: noqa: F401

# Checking for VTK9 here even though 8.2 contains vtkmodules.  There
# are enough idiosyncrasies to VTK 8.2, and supporting it would lead
# to obscure code.

try:
    from vtkmodules.vtkCommonCore import vtkVersion

    VTK9 = vtkVersion().GetVTKMajorVersion() >= 9
except ImportError:  # pragma: no cover
    VTK9 = False

# for charts
_has_vtkRenderingContextOpenGL2 = False

if VTK9:
    # vtkExtractEdges moved from vtkFiltersExtraction to vtkFiltersCore in
    # VTK commit d9981b9aeb93b42d1371c6e295d76bfdc18430bd
    try:
        from vtkmodules.vtkFiltersCore import vtkExtractEdges
    except ImportError:
        from vtkmodules.vtkFiltersExtraction import vtkExtractEdges

    # vtkCellTreeLocator moved from vtkFiltersGeneral to vtkCommonDataModel in
    # VTK commit 4a29e6f7dd9acb460644fe487d2e80aac65f7be9
    try:
        from vtkmodules.vtkCommonDataModel import vtkCellTreeLocator
    except ImportError:
        from vtkmodules.vtkFiltersGeneral import vtkCellTreeLocator

    from vtkmodules.numpy_interface.dataset_adapter import (
        VTKArray,
        VTKObjectWrapper,
        numpyTovtkDataArray,
    )
    from vtkmodules.util.numpy_support import (
        get_vtk_array_type,
        numpy_to_vtk,
        numpy_to_vtkIdTypeArray,
        vtk_to_numpy,
    )
    from vtkmodules.vtkChartsCore import (
        vtkAxis,
        vtkChart,
        vtkChartBox,
        vtkChartPie,
        vtkChartXY,
        vtkChartXYZ,
        vtkPlotArea,
        vtkPlotBar,
        vtkPlotBox,
        vtkPlotLine,
        vtkPlotLine3D,
        vtkPlotPie,
        vtkPlotPoints,
        vtkPlotPoints3D,
        vtkPlotStacked,
        vtkPlotSurface,
    )
    from vtkmodules.vtkCommonColor import vtkColorSeries
    from vtkmodules.vtkCommonComputationalGeometry import (
        vtkKochanekSpline,
        vtkParametricBohemianDome,
        vtkParametricBour,
        vtkParametricBoy,
        vtkParametricCatalanMinimal,
        vtkParametricConicSpiral,
        vtkParametricCrossCap,
        vtkParametricDini,
        vtkParametricEllipsoid,
        vtkParametricEnneper,
        vtkParametricFigure8Klein,
        vtkParametricFunction,
        vtkParametricHenneberg,
        vtkParametricKlein,
        vtkParametricKuen,
        vtkParametricMobius,
        vtkParametricPluckerConoid,
        vtkParametricPseudosphere,
        vtkParametricRandomHills,
        vtkParametricRoman,
        vtkParametricSpline,
        vtkParametricSuperEllipsoid,
        vtkParametricSuperToroid,
        vtkParametricTorus,
    )
    from vtkmodules.vtkCommonCore import (
        VTK_ARIAL,
        VTK_COURIER,
        VTK_TIMES,
        VTK_UNSIGNED_CHAR,
        buffer_shared,
        mutable,
        reference,
        vtkAbstractArray,
        vtkBitArray,
        vtkCharArray,
        vtkCommand,
        vtkDataArray,
        vtkDoubleArray,
        vtkFileOutputWindow,
        vtkFloatArray,
        vtkIdList,
        vtkIdTypeArray,
        vtkLookupTable,
        vtkOutputWindow,
        vtkPoints,
        vtkSignedCharArray,
        vtkStringArray,
        vtkStringOutputWindow,
        vtkTypeInt32Array,
        vtkTypeInt64Array,
        vtkTypeUInt32Array,
        vtkUnsignedCharArray,
        vtkWeakReference,
    )
    from vtkmodules.vtkCommonDataModel import (
        VTK_BIQUADRATIC_QUAD,
        VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON,
        VTK_BIQUADRATIC_QUADRATIC_WEDGE,
        VTK_BIQUADRATIC_TRIANGLE,
        VTK_CONVEX_POINT_SET,
        VTK_CUBIC_LINE,
        VTK_EMPTY_CELL,
        VTK_HEXAGONAL_PRISM,
        VTK_HEXAHEDRON,
        VTK_HIGHER_ORDER_EDGE,
        VTK_HIGHER_ORDER_HEXAHEDRON,
        VTK_HIGHER_ORDER_POLYGON,
        VTK_HIGHER_ORDER_PYRAMID,
        VTK_HIGHER_ORDER_QUAD,
        VTK_HIGHER_ORDER_TETRAHEDRON,
        VTK_HIGHER_ORDER_TRIANGLE,
        VTK_HIGHER_ORDER_WEDGE,
        VTK_LAGRANGE_CURVE,
        VTK_LAGRANGE_HEXAHEDRON,
        VTK_LAGRANGE_PYRAMID,
        VTK_LAGRANGE_QUADRILATERAL,
        VTK_LAGRANGE_TETRAHEDRON,
        VTK_LAGRANGE_TRIANGLE,
        VTK_LAGRANGE_WEDGE,
        VTK_LINE,
        VTK_PARAMETRIC_CURVE,
        VTK_PARAMETRIC_HEX_REGION,
        VTK_PARAMETRIC_QUAD_SURFACE,
        VTK_PARAMETRIC_SURFACE,
        VTK_PARAMETRIC_TETRA_REGION,
        VTK_PARAMETRIC_TRI_SURFACE,
        VTK_PENTAGONAL_PRISM,
        VTK_PIXEL,
        VTK_POLY_LINE,
        VTK_POLY_VERTEX,
        VTK_POLYGON,
        VTK_POLYHEDRON,
        VTK_PYRAMID,
        VTK_QUAD,
        VTK_QUADRATIC_EDGE,
        VTK_QUADRATIC_HEXAHEDRON,
        VTK_QUADRATIC_LINEAR_QUAD,
        VTK_QUADRATIC_LINEAR_WEDGE,
        VTK_QUADRATIC_POLYGON,
        VTK_QUADRATIC_PYRAMID,
        VTK_QUADRATIC_QUAD,
        VTK_QUADRATIC_TETRA,
        VTK_QUADRATIC_TRIANGLE,
        VTK_QUADRATIC_WEDGE,
        VTK_TETRA,
        VTK_TRIANGLE,
        VTK_TRIANGLE_STRIP,
        VTK_TRIQUADRATIC_HEXAHEDRON,
        VTK_VERTEX,
        VTK_VOXEL,
        VTK_WEDGE,
        vtkCellArray,
        vtkCellLocator,
        vtkColor3ub,
        vtkCompositeDataSet,
        vtkDataObject,
        vtkDataSet,
        vtkDataSetAttributes,
        vtkExplicitStructuredGrid,
        vtkFieldData,
        vtkGenericCell,
        vtkImageData,
        vtkImplicitFunction,
        vtkMultiBlockDataSet,
        vtkNonMergingPointLocator,
        vtkPerlinNoise,
        vtkPiecewiseFunction,
        vtkPlane,
        vtkPlaneCollection,
        vtkPlanes,
        vtkPointLocator,
        vtkPointSet,
        vtkPolyData,
        vtkPolyLine,
        vtkPolyPlane,
        vtkPyramid,
        vtkRectf,
        vtkRectilinearGrid,
        vtkSelection,
        vtkSelectionNode,
        vtkStaticCellLocator,
        vtkStaticPointLocator,
        vtkStructuredGrid,
        vtkTable,
        vtkUnstructuredGrid,
    )

    try:
        from vtkmodules.vtkCommonDataModel import (
            VTK_BEZIER_CURVE,
            VTK_BEZIER_HEXAHEDRON,
            VTK_BEZIER_PYRAMID,
            VTK_BEZIER_QUADRILATERAL,
            VTK_BEZIER_TETRAHEDRON,
            VTK_BEZIER_TRIANGLE,
            VTK_BEZIER_WEDGE,
            VTK_TRIQUADRATIC_PYRAMID,
        )
    except ImportError:  # pragma: no cover
        pass

    from vtkmodules.vtkCommonExecutionModel import vtkImageToStructuredGrid
    from vtkmodules.vtkCommonMath import vtkMatrix3x3, vtkMatrix4x4
    from vtkmodules.vtkCommonTransforms import vtkTransform
    from vtkmodules.vtkFiltersCore import (
        VTK_BEST_FITTING_PLANE,
        vtkAppendArcLength,
        vtkAppendFilter,
        vtkAppendPolyData,
        vtkCellCenters,
        vtkCellDataToPointData,
        vtkCenterOfMass,
        vtkCleanPolyData,
        vtkClipPolyData,
        vtkConnectivityFilter,
        vtkContourFilter,
        vtkCutter,
        vtkDecimatePro,
        vtkDelaunay2D,
        vtkDelaunay3D,
        vtkElevationFilter,
        vtkExplicitStructuredGridToUnstructuredGrid,
        vtkFeatureEdges,
        vtkFlyingEdges3D,
        vtkGlyph3D,
        vtkImplicitPolyDataDistance,
        vtkMarchingCubes,
        vtkMassProperties,
        vtkPointDataToCellData,
        vtkPolyDataNormals,
        vtkProbeFilter,
        vtkQuadricDecimation,
        vtkResampleWithDataSet,
        vtkSmoothPolyDataFilter,
        vtkStripper,
        vtkThreshold,
        vtkTriangleFilter,
        vtkTubeFilter,
        vtkUnstructuredGridToExplicitStructuredGrid,
        vtkWindowedSincPolyDataFilter,
    )
    from vtkmodules.vtkFiltersExtraction import (
        vtkExtractGeometry,
        vtkExtractGrid,
        vtkExtractSelection,
    )
    from vtkmodules.vtkFiltersFlowPaths import vtkEvenlySpacedStreamlines2D, vtkStreamTracer
    from vtkmodules.vtkFiltersGeneral import (
        vtkAxes,
        vtkBooleanOperationPolyDataFilter,
        vtkBoxClipDataSet,
        vtkClipClosedSurface,
        vtkCursor3D,
        vtkCurvatures,
        vtkDataSetTriangleFilter,
        vtkGradientFilter,
        vtkIntersectionPolyDataFilter,
        vtkOBBTree,
        vtkRectilinearGridToPointSet,
        vtkRectilinearGridToTetrahedra,
        vtkShrinkFilter,
        vtkTableBasedClipDataSet,
        vtkTableToPolyData,
        vtkTessellatorFilter,
        vtkTransformFilter,
        vtkWarpScalar,
        vtkWarpVector,
    )
    from vtkmodules.vtkFiltersGeometry import (
        vtkCompositeDataGeometryFilter,
        vtkDataSetSurfaceFilter,
        vtkGeometryFilter,
        vtkStructuredGridGeometryFilter,
    )
    from vtkmodules.vtkFiltersHybrid import vtkPolyDataSilhouette
    from vtkmodules.vtkFiltersModeling import (
        vtkAdaptiveSubdivisionFilter,
        vtkButterflySubdivisionFilter,
        vtkCollisionDetectionFilter,
        vtkDijkstraGraphGeodesicPath,
        vtkFillHolesFilter,
        vtkLinearExtrusionFilter,
        vtkLinearSubdivisionFilter,
        vtkLoopSubdivisionFilter,
        vtkOutlineFilter,
        vtkRibbonFilter,
        vtkRotationalExtrusionFilter,
        vtkSelectEnclosedPoints,
        vtkSubdivideTetra,
        vtkTrimmedExtrusionFilter,
    )
    from vtkmodules.vtkFiltersParallel import vtkIntegrateAttributes

    try:
        from vtkmodules.vtkFiltersParallelDIY2 import vtkRedistributeDataSetFilter
    except ModuleNotFoundError:  # pragma: no cover
        # `vtkmodules.vtkFiltersParallelDIY2` is unavailable in some versions of `vtk` from conda-forge
        pass
    from vtkmodules.vtkFiltersPoints import vtkGaussianKernel, vtkPointInterpolator
    from vtkmodules.vtkFiltersSources import (
        vtkArcSource,
        vtkArrowSource,
        vtkConeSource,
        vtkCubeSource,
        vtkCylinderSource,
        vtkDiskSource,
        vtkFrustumSource,
        vtkLineSource,
        vtkOutlineCornerFilter,
        vtkOutlineCornerSource,
        vtkParametricFunctionSource,
        vtkPlaneSource,
        vtkPlatonicSolidSource,
        vtkPointSource,
        vtkRegularPolygonSource,
        vtkSphereSource,
        vtkSuperquadricSource,
        vtkTessellatedBoxSource,
    )
    from vtkmodules.vtkFiltersStatistics import vtkComputeQuartiles
    from vtkmodules.vtkFiltersTexture import vtkTextureMapToPlane, vtkTextureMapToSphere
    from vtkmodules.vtkFiltersVerdict import vtkCellQuality, vtkCellSizeFilter
    from vtkmodules.vtkIOEnSight import vtkGenericEnSightReader
    from vtkmodules.vtkIOGeometry import (
        vtkAVSucdReader,
        vtkBYUReader,
        vtkFLUENTReader,
        vtkGLTFReader,
        vtkMCubesReader,
        vtkMFIXReader,
        vtkOBJReader,
        vtkOpenFOAMReader,
        vtkPTSReader,
        vtkSTLReader,
        vtkSTLWriter,
        vtkTecplotReader,
    )
    from vtkmodules.vtkIOImage import (
        vtkBMPReader,
        vtkDEMReader,
        vtkDICOMImageReader,
        vtkHDRReader,
        vtkJPEGReader,
        vtkMetaImageReader,
        vtkNIFTIImageReader,
        vtkNrrdReader,
        vtkPNGReader,
        vtkPNMReader,
        vtkSLCReader,
        vtkTIFFReader,
    )
    from vtkmodules.vtkIOInfovis import vtkDelimitedTextReader
    from vtkmodules.vtkIOLegacy import (
        vtkDataReader,
        vtkDataSetReader,
        vtkDataSetWriter,
        vtkDataWriter,
        vtkPolyDataReader,
        vtkPolyDataWriter,
        vtkRectilinearGridReader,
        vtkRectilinearGridWriter,
        vtkSimplePointsWriter,
        vtkStructuredGridReader,
        vtkStructuredGridWriter,
        vtkUnstructuredGridReader,
        vtkUnstructuredGridWriter,
    )
    from vtkmodules.vtkIOPLY import vtkPLYReader, vtkPLYWriter
    from vtkmodules.vtkIOXML import (
        vtkXMLImageDataReader,
        vtkXMLImageDataWriter,
        vtkXMLMultiBlockDataReader,
        vtkXMLMultiBlockDataWriter,
        vtkXMLPImageDataReader,
        vtkXMLPolyDataReader,
        vtkXMLPolyDataWriter,
        vtkXMLPRectilinearGridReader,
        vtkXMLPUnstructuredGridReader,
        vtkXMLReader,
        vtkXMLRectilinearGridReader,
        vtkXMLRectilinearGridWriter,
        vtkXMLStructuredGridReader,
        vtkXMLStructuredGridWriter,
        vtkXMLTableReader,
        vtkXMLTableWriter,
        vtkXMLUnstructuredGridReader,
        vtkXMLUnstructuredGridWriter,
        vtkXMLWriter,
    )
    from vtkmodules.vtkImagingCore import (
        vtkExtractVOI,
        vtkImageDifference,
        vtkImageExtractComponents,
        vtkImageFlip,
        vtkImageThreshold,
        vtkRTAnalyticSource,
    )
    from vtkmodules.vtkImagingGeneral import vtkImageGaussianSmooth, vtkImageMedian3D
    from vtkmodules.vtkImagingHybrid import vtkSampleFunction, vtkSurfaceReconstructionFilter
    from vtkmodules.vtkImagingMorphological import vtkImageDilateErode3D
    from vtkmodules.vtkInteractionWidgets import (
        vtkBoxWidget,
        vtkButtonWidget,
        vtkImplicitPlaneWidget,
        vtkLineWidget,
        vtkOrientationMarkerWidget,
        vtkPlaneWidget,
        vtkScalarBarWidget,
        vtkSliderRepresentation2D,
        vtkSliderWidget,
        vtkSphereWidget,
        vtkSplineWidget,
        vtkTexturedButtonRepresentation2D,
    )
    from vtkmodules.vtkPythonContext2D import vtkPythonItem
    from vtkmodules.vtkRenderingAnnotation import (
        vtkAnnotatedCubeActor,
        vtkAxesActor,
        vtkAxisActor2D,
        vtkCornerAnnotation,
        vtkCubeAxesActor,
        vtkLegendBoxActor,
        vtkScalarBarActor,
    )
    from vtkmodules.vtkRenderingContext2D import (
        vtkBlockItem,
        vtkBrush,
        vtkContext2D,
        vtkContextActor,
        vtkContextScene,
        vtkImageItem,
        vtkPen,
    )

    try:
        # Necessary for displaying charts, otherwise crashes on rendering
        import vtkmodules.vtkRenderingContextOpenGL2

        _has_vtkRenderingContextOpenGL2 = True
    except ImportError:  # pragma: no cover
        pass

    from vtkmodules.vtkImagingFourier import (
        vtkImageButterworthHighPass,
        vtkImageButterworthLowPass,
        vtkImageFFT,
        vtkImageRFFT,
    )
    from vtkmodules.vtkRenderingCore import (
        vtkAbstractMapper,
        vtkActor,
        vtkActor2D,
        vtkCamera,
        vtkCellPicker,
        vtkColorTransferFunction,
        vtkCompositeDataDisplayAttributes,
        vtkCoordinate,
        vtkDataSetMapper,
        vtkImageActor,
        vtkLight,
        vtkLightActor,
        vtkLightKit,
        vtkMapper,
        vtkPointPicker,
        vtkPolyDataMapper,
        vtkPolyDataMapper2D,
        vtkPropAssembly,
        vtkProperty,
        vtkPropPicker,
        vtkRenderedAreaPicker,
        vtkRenderer,
        vtkRenderWindow,
        vtkRenderWindowInteractor,
        vtkSelectVisiblePoints,
        vtkSkybox,
        vtkTextActor,
        vtkTexture,
        vtkVolume,
        vtkVolumeProperty,
        vtkWindowToImageFilter,
        vtkWorldPointPicker,
    )
    from vtkmodules.vtkRenderingFreeType import vtkVectorText
    from vtkmodules.vtkRenderingLabel import vtkLabelPlacementMapper, vtkPointSetToLabelHierarchy
    from vtkmodules.vtkRenderingOpenGL2 import (
        vtkCameraPass,
        vtkCompositePolyDataMapper2,
        vtkDepthOfFieldPass,
        vtkEDLShading,
        vtkGaussianBlurPass,
        vtkOpenGLFXAAPass,
        vtkOpenGLHardwareSelector,
        vtkOpenGLRenderer,
        vtkOpenGLTexture,
        vtkRenderPassCollection,
        vtkRenderStepsPass,
        vtkSequencePass,
        vtkShadowMapPass,
        vtkSSAAPass,
        vtkSSAOPass,
    )
    from vtkmodules.vtkRenderingUI import vtkGenericRenderWindowInteractor
    from vtkmodules.vtkRenderingVolume import (
        vtkFixedPointVolumeRayCastMapper,
        vtkGPUVolumeRayCastMapper,
    )
    from vtkmodules.vtkRenderingVolumeOpenGL2 import (
        vtkOpenGLGPUVolumeRayCastMapper,
        vtkSmartVolumeMapper,
    )
    from vtkmodules.vtkViewsContext2D import vtkContextInteractorStyle

    # lazy import for some of the less used readers
    def lazy_vtkOBJExporter():
        """Lazy import of the vtkOBJExporter."""
        from vtkmodules.vtkIOExport import vtkOBJExporter

        return vtkOBJExporter()

    def lazy_vtkVRMLImporter():
        """Lazy import of the vtkVRMLImporter."""
        from vtkmodules.vtkIOImport import vtkVRMLImporter

        return vtkVRMLImporter()

    def lazy_vtkVRMLExporter():
        """Lazy import of the vtkVRMLExporter."""
        from vtkmodules.vtkIOExport import vtkVRMLExporter

        return vtkVRMLExporter()

    def lazy_vtkGL2PSExporter():
        """Lazy import of the vtkGL2PSExporter."""
        from vtkmodules.vtkIOExportGL2PS import vtkGL2PSExporter

        return vtkGL2PSExporter()

    def lazy_vtkFacetReader():
        """Lazy import of the vtkFacetReader."""
        from vtkmodules.vtkFiltersHybrid import vtkFacetReader

        return vtkFacetReader()

    def lazy_vtkPDataSetReader():
        """Lazy import of the vtkPDataSetReader."""
        from vtkmodules.vtkIOParallel import vtkPDataSetReader

        return vtkPDataSetReader()

    def lazy_vtkMultiBlockPLOT3DReader():
        """Lazy import of the vtkMultiBlockPLOT3DReader."""
        from vtkmodules.vtkIOParallel import vtkMultiBlockPLOT3DReader

        return vtkMultiBlockPLOT3DReader()

    def lazy_vtkPlot3DMetaReader():
        """Lazy import of the vtkPlot3DMetaReader."""
        from vtkmodules.vtkIOParallel import vtkPlot3DMetaReader

        return vtkPlot3DMetaReader()

    def lazy_vtkSegYReader():
        """Lazy import of the vtkSegYReader."""
        from vtkmodules.vtkIOSegY import vtkSegYReader

        return vtkSegYReader()

    def lazy_vtkHDFReader():
        """Lazy import of the vtkHDFReader."""
        from vtkmodules.vtkIOHDF import vtkHDFReader

        return vtkHDFReader()

    def lazy_vtkCGNSReader():
        """Lazy import of the vtkCGNSReader."""
        from vtkmodules.vtkIOCGNSReader import vtkCGNSReader

        return vtkCGNSReader()

    def lazy_vtkPOpenFOAMReader():
        """Lazy import of the vtkPOpenFOAMReader."""
        from vtkmodules.vtkIOParallel import vtkPOpenFOAMReader
        from vtkmodules.vtkParallelCore import vtkDummyController

        # Workaround waiting for the fix to be upstream (MR 9195 gitlab.kitware.com/vtk/vtk)
        reader = vtkPOpenFOAMReader()
        reader.SetController(vtkDummyController())
        return reader

else:  # pragma: no cover

    # maintain VTK 8.2 compatibility
    try:
        from vtk.vtkCommonKitPython import buffer_shared, vtkAbstractArray, vtkWeakReference
    except ImportError:
        from vtk.vtkCommonCore import buffer_shared, vtkAbstractArray, vtkWeakReference

    import vtk

    # vtk8 already has an import all, so we can just mirror it here at
    # no cost
    from vtk import *
    from vtk.numpy_interface.dataset_adapter import VTKArray, VTKObjectWrapper, numpyTovtkDataArray
    from vtk.util.numpy_support import (
        get_vtk_array_type,
        numpy_to_vtk,
        numpy_to_vtkIdTypeArray,
        vtk_to_numpy,
    )

    # match the imports for VTK9
    def lazy_vtkOBJExporter():
        """Lazy import of the vtkOBJExporter."""
        return vtk.vtkOBJExporter()

    def lazy_vtkVRMLImporter():
        """Lazy import of the vtkVRMLImporter."""
        return vtk.vtkVRMLImporter()

    def lazy_vtkVRMLExporter():
        """Lazy import of the vtkVRMLExporter."""
        return vtk.vtkVRMLExporter()

    def lazy_vtkGL2PSExporter():
        """Lazy import of the vtkGL2PSExporter."""
        return vtk.vtkGL2PSExporter()

    def lazy_vtkFacetReader():
        """Lazy import of the vtkFacetReader."""
        return vtk.vtkFacetReader()

    def lazy_vtkPDataSetReader():
        """Lazy import of the vtkPDataSetReader."""
        return vtk.vtkPDataSetReader()

    def lazy_vtkMultiBlockPLOT3DReader():
        """Lazy import of the vtkMultiBlockPLOT3DReader."""
        return vtk.vtkMultiBlockPLOT3DReader()

    def lazy_vtkPlot3DMetaReader():
        """Lazy import of the vtkPlot3DMetaReader."""
        return vtk.vtkPlot3DMetaReader()

    def lazy_vtkCGNSReader():
        """Lazy import of the vtkCGNSReader."""
        raise VTKVersionError('vtk.CGNSReader requires VTK v9.1.0 or newer')

    def lazy_vtkPOpenFOAMReader():
        """Lazy import of the vtkPOpenFOAMReader."""
        # Workaround to fix the following issue: https://gitlab.kitware.com/vtk/vtk/-/issues/18143
        # Fixed in vtk > 9.1.0
        reader = vtk.vtkPOpenFOAMReader()
        reader.SetController(vtk.vtkDummyController())
        return reader

    def lazy_vtkHDFReader():
        """Lazy import of the vtkHDFReader."""
        raise VTKVersionError('vtk.HDFReader requires VTK v9.1.0 or newer')

    def lazy_vtkSegYReader():
        """Lazy import of the vtkSegYReader."""
        return vtk.vtkSegYReader()

    class vtkExplicitStructuredGrid:  # type: ignore
        """Empty placeholder for VTK9 compatibility."""

        def __init__(self):  # pragma: no cover
            """Raise version error on init."""
            from pyvista.core.errors import VTKVersionError

            raise VTKVersionError('vtkHDRReader requires VTK v9 or newer')

    class vtkHDRReader:  # type: ignore
        """Empty placeholder for VTK9 compatibility."""

        def __init__(self):  # pragma: no cover
            """Raise version error on init."""
            from pyvista.core.errors import VTKVersionError

            raise VTKVersionError('vtkHDRReader requires VTK v9 or newer')

    class vtkGLTFReader:  # type: ignore
        """Empty placeholder for VTK9 compatibility."""

        def __init__(self):  # pragma: no cover
            """Raise version error on init."""
            from pyvista.core.errors import VTKVersionError

            raise VTKVersionError('vtkGLTFReader requires VTK v9 or newer')

    class vtkPythonItem:  # type: ignore
        """Empty placeholder for VTK9 compatibility."""

        def __init__(self):  # pragma: no cover
            """Raise version error on init."""
            from pyvista.core.errors import VTKVersionError

            raise VTKVersionError('Charts requires VTK v9 or newer')

    class vtkHDFReader:  # type: ignore
        """Empty placeholder for VTK9 compatibility."""

        def __init__(self):  # pragma: no cover
            """Raise version error on init."""
            from pyvista.core.errors import VTKVersionError

            raise VTKVersionError('vtkHDFReader requires VTK v9 or newer')


# lazy import as this was added in 9.1.0
def lazy_vtkCameraOrientationWidget():
    """Lazy import of the vtkCameraOrientationWidget."""
    try:
        from vtkmodules.vtkInteractionWidgets import vtkCameraOrientationWidget
    except ImportError:  # pragma: no cover
        from pyvista.core.errors import VTKVersionError

        raise VTKVersionError('vtkCameraOrientationWidget requires vtk>=9.1.0')
    return vtkCameraOrientationWidget()


# pyvista/conftest.py

"""Close all plotters to help control memory usage for our doctests."""

import pytest

import pyvista


@pytest.fixture(autouse=True)
def autoclose_plotters():
    """Close all plotters."""
    yield
    pyvista.close_all()


@pytest.fixture(autouse=True)
def reset_gloal_theme():
    """Reset global_theme."""
    # this stops any doctest-module tests from overriding the global theme and
    # creating test side effects
    yield
    pyvista.global_theme.restore_defaults()


# pyvista/errors.py

"""PyVista specific errors."""


class MissingDataError(ValueError):
    """Exception when data is missing, e.g. no active scalars can be set."""

    def __init__(self, message='No data available.'):
        """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."""

    def __init__(self, message="Multiple data available."):
        """Call the base class constructor with the custom message."""
        super().__init__(message)


# pyvista/themes.py

"""API description for managing plotting theme parameters in pyvista.

Examples
--------
Apply a built-in theme

>>> import pyvista
>>> pyvista.set_plot_theme('default')
>>> pyvista.set_plot_theme('document')
>>> pyvista.set_plot_theme('dark')
>>> pyvista.set_plot_theme('paraview')

Load a theme into pyvista

>>> theme = pyvista.themes.DefaultTheme()
>>> theme.save('my_theme.json')  # doctest:+SKIP
>>> loaded_theme = pyvista.load_theme('my_theme.json')  # doctest:+SKIP

Create a custom theme from the default theme and load it into
pyvista.

>>> my_theme = pyvista.themes.DefaultTheme()
>>> my_theme.font.size = 20
>>> my_theme.font.title_size = 40
>>> my_theme.cmap = 'jet'
...
>>> pyvista.global_theme.load_theme(my_theme)
>>> pyvista.global_theme.font.size
20

"""

from enum import Enum
import json
import os
from typing import List, Optional, Union
import warnings

from ._typing import color_like
from .plotting.colors import Color, get_cmap_safe
from .plotting.tools import parse_font_family
from .utilities.misc import PyVistaDeprecationWarning


class _rcParams(dict):  # pragma: no cover
    """Reference to the deprecated rcParams dictionary."""

    def __getitem__(self, key):
        import pyvista  # avoids circular import

        warnings.warn(
            'rcParams is deprecated.  Please use ``pyvista.global_theme``.', DeprecationWarning
        )
        return getattr(pyvista.global_theme, key)

    def __setitem__(self, key, value):
        import pyvista  # avoids circular import

        warnings.warn(
            'rcParams is deprecated.  Please use ``pyvista.global_theme``.', DeprecationWarning
        )
        setattr(pyvista.global_theme, key, value)

    def __repr__(self):
        """Use the repr of global_theme."""
        import pyvista  # avoids circular import

        warnings.warn(
            'rcParams is deprecated.  Please use ``pyvista.global_theme``', DeprecationWarning
        )
        return repr(pyvista.global_theme)


def _check_between_zero_and_one(value: float, value_name: str = 'value'):
    """Check if a value is between zero and one."""
    if value < 0 or value > 1:
        raise ValueError('{value_name} must be between 0 and 1.')


def load_theme(filename):
    """Load a theme from a file.

    Examples
    --------
    >>> import pyvista
    >>> theme = pyvista.themes.DefaultTheme()
    >>> theme.save('my_theme.json')  # doctest:+SKIP
    >>> loaded_theme = pyvista.load_theme('my_theme.json')  # doctest:+SKIP

    """
    with open(filename) as f:
        theme_dict = json.load(f)
    return DefaultTheme.from_dict(theme_dict)


def set_plot_theme(theme):
    """Set the plotting parameters to a predefined theme using a string.

    Parameters
    ----------
    theme : str
        Theme name.  Either ``'default'``, ``'document'``, ``'dark'``,
        or ``'paraview'``.

    Examples
    --------
    Set to the default theme.

    >>> import pyvista
    >>> pyvista.set_plot_theme('default')

    Set to the document theme.

    >>> pyvista.set_plot_theme('document')

    Set to the dark theme.

    >>> pyvista.set_plot_theme('dark')

    Set to the ParaView theme.

    >>> pyvista.set_plot_theme('paraview')

    """
    import pyvista

    if isinstance(theme, str):
        theme = theme.lower()
        if theme == 'night':  # pragma: no cover
            warnings.warn('use "dark" instead of "night" theme', PyVistaDeprecationWarning)
            theme = 'dark'
        new_theme_type = _ALLOWED_THEMES[theme].value
        pyvista.global_theme.load_theme(new_theme_type())
    elif isinstance(theme, DefaultTheme):
        pyvista.global_theme.load_theme(theme)
    else:
        raise TypeError(
            f'Expected a ``pyvista.themes.DefaultTheme`` or ``str``, not '
            f'a {type(theme).__name__}'
        )


class _ThemeConfig:
    """Provide common methods for theme configuration classes."""

    __slots__: List[str] = []

    @classmethod
    def from_dict(cls, dict_):
        """Create from a dictionary."""
        inst = cls()
        for key, value in dict_.items():
            attr = getattr(inst, key)
            if hasattr(attr, 'from_dict'):
                setattr(inst, key, attr.from_dict(value))
            else:
                setattr(inst, key, value)
        return inst

    def to_dict(self) -> dict:
        """Return theme config parameters as a dictionary.

        Returns
        -------
        dict
            This theme parameter represented as a dictionary.

        """
        # remove the first underscore in each entry
        dict_ = {}
        for key in self.__slots__:
            value = getattr(self, key)
            key = key[1:]
            if hasattr(value, 'to_dict'):
                dict_[key] = value.to_dict()
            else:
                dict_[key] = value
        return dict_

    def __eq__(self, other):
        if not isinstance(self, type(other)):
            return False

        for attr_name in other.__slots__:
            attr = getattr(self, attr_name)
            other_attr = getattr(other, attr_name)
            if isinstance(attr, (tuple, list)):
                if tuple(attr) != tuple(other_attr):
                    return False
            else:
                if not attr == other_attr:
                    return False

        return True

    def __getitem__(self, key):
        """Get a value via a key.

        Implemented here for backwards compatibility.
        """
        return getattr(self, key)

    def __setitem__(self, key, value):
        """Set a value via a key.

        Implemented here for backwards compatibility.
        """
        setattr(self, key, value)


class _DepthPeelingConfig(_ThemeConfig):
    """PyVista depth peeling configuration.

    Examples
    --------
    Set global depth peeling parameters.

    >>> import pyvista
    >>> pyvista.global_theme.depth_peeling.number_of_peels = 1
    >>> pyvista.global_theme.depth_peeling.occlusion_ratio = 0.0
    >>> pyvista.global_theme.depth_peeling.enabled = True

    """

    __slots__ = ['_number_of_peels', '_occlusion_ratio', '_enabled']

    def __init__(self):
        self._number_of_peels = 4
        self._occlusion_ratio = 0.0
        self._enabled = False

    @property
    def number_of_peels(self) -> int:
        """Return or set the number of peels in depth peeling.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.depth_peeling.number_of_peels = 1

        """
        return self._number_of_peels

    @number_of_peels.setter
    def number_of_peels(self, number_of_peels: int):
        self._number_of_peels = int(number_of_peels)

    @property
    def occlusion_ratio(self) -> float:
        """Return or set the occlusion ratio in depth peeling.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.depth_peeling.occlusion_ratio = 0.0

        """
        return self._occlusion_ratio

    @occlusion_ratio.setter
    def occlusion_ratio(self, occlusion_ratio: float):
        self._occlusion_ratio = float(occlusion_ratio)

    @property
    def enabled(self) -> bool:
        """Return or set if depth peeling is enabled.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.depth_peeling.enabled = True

        """
        return self._enabled

    @enabled.setter
    def enabled(self, enabled: bool):
        self._enabled = bool(enabled)

    def __repr__(self):
        txt = ['']
        parm = {
            'Number': 'number_of_peels',
            'Occlusion ratio': 'occlusion_ratio',
            'Enabled': 'enabled',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'    {name:<21}: {setting}')
        return '\n'.join(txt)


class _SilhouetteConfig(_ThemeConfig):
    """PyVista silhouette configuration.

    Examples
    --------
    Set global silhouette parameters.

    >>> import pyvista
    >>> pyvista.global_theme.silhouette.color = 'grey'
    >>> pyvista.global_theme.silhouette.line_width = 2
    >>> pyvista.global_theme.silhouette.feature_angle = 20

    """

    __slots__ = ['_color', '_line_width', '_opacity', '_feature_angle', '_decimate']

    def __init__(self):
        self._color = Color('black')
        self._line_width = 2
        self._opacity = 1.0
        self._feature_angle = None
        self._decimate = 0.9

    @property
    def color(self) -> Color:
        """Return or set the silhouette color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.silhouette.color = 'red'

        """
        return self._color

    @color.setter
    def color(self, color: color_like):
        self._color = Color(color)

    @property
    def line_width(self) -> float:
        """Return or set the silhouette line width.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.silhouette.line_width = 2.0

        """
        return self._line_width

    @line_width.setter
    def line_width(self, line_width: float):
        self._line_width = float(line_width)

    @property
    def opacity(self) -> float:
        """Return or set the silhouette opacity.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.silhouette.opacity = 1.0

        """
        return self._opacity

    @opacity.setter
    def opacity(self, opacity: float):
        _check_between_zero_and_one(opacity, 'opacity')
        self._opacity = float(opacity)

    @property
    def feature_angle(self) -> Union[float, None]:
        """Return or set the silhouette feature angle.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.silhouette.feature_angle = 20.0

        """
        return self._feature_angle

    @feature_angle.setter
    def feature_angle(self, feature_angle: Union[float, None]):
        self._feature_angle = feature_angle

    @property
    def decimate(self) -> float:
        """Return or set the amount to decimate the silhouette.

        Parameter must be between 0 and 1.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.silhouette.decimate = 0.9

        """
        return self._decimate

    @decimate.setter
    def decimate(self, decimate: float):
        _check_between_zero_and_one(decimate, 'decimate')
        self._decimate = float(decimate)

    def __repr__(self):
        txt = ['']
        parm = {
            'Color': 'color',
            'Line width': 'line_width',
            'Opacity': 'opacity',
            'Feature angle': 'feature_angle',
            'Decimate': 'decimate',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'    {name:<21}: {setting}')
        return '\n'.join(txt)


class _ColorbarConfig(_ThemeConfig):
    """PyVista colorbar configuration.

    Examples
    --------
    Set the colorbar width.

    >>> import pyvista
    >>> pyvista.global_theme.colorbar_horizontal.width = 0.2

    """

    __slots__ = ['_width', '_height', '_position_x', '_position_y']

    def __init__(self):
        self._width = None
        self._height = None
        self._position_x = None
        self._position_y = None

    @property
    def width(self) -> float:
        """Return or set colorbar width.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.colorbar_horizontal.width = 0.2

        """
        return self._width

    @width.setter
    def width(self, width: float):
        self._width = float(width)

    @property
    def height(self) -> float:
        """Return or set colorbar height.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.colorbar_horizontal.height = 0.2

        """
        return self._height

    @height.setter
    def height(self, height: float):
        self._height = float(height)

    @property
    def position_x(self) -> float:
        """Return or set colorbar x position.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.colorbar_horizontal.position_x = 0.2

        """
        return self._position_x

    @position_x.setter
    def position_x(self, position_x: float):
        self._position_x = float(position_x)

    @property
    def position_y(self) -> float:
        """Return or set colorbar y position.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.colorbar_horizontal.position_y = 0.2

        """
        return self._position_y

    @position_y.setter
    def position_y(self, position_y: float):
        self._position_y = float(position_y)

    def __repr__(self):
        txt = ['']
        parm = {
            'Width': 'width',
            'Height': 'height',
            'X Position': 'position_x',
            'Y Position': 'position_y',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'    {name:<21}: {setting}')

        return '\n'.join(txt)


class _AxesConfig(_ThemeConfig):
    """PyVista axes configuration.

    Examples
    --------
    Set the x axis color to black.

    >>> import pyvista
    >>> pyvista.global_theme.axes.x_color = 'black'

    Show axes by default.

    >>> pyvista.global_theme.axes.show = True

    Use the ``vtk.vtkCubeAxesActor``.

    >>> pyvista.global_theme.axes.box = True

    """

    __slots__ = ['_x_color', '_y_color', '_z_color', '_box', '_show']

    def __init__(self):
        self._x_color = Color('tomato')
        self._y_color = Color('seagreen')
        self._z_color = Color('mediumblue')
        self._box = False
        self._show = True

    def __repr__(self):
        txt = ['Axes configuration']
        parm = {
            'X Color': 'x_color',
            'Y Color': 'y_color',
            'Z Color': 'z_color',
            'Use Box': 'box',
            'Show': 'show',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'    {name:<21}: {setting}')

        return '\n'.join(txt)

    @property
    def x_color(self) -> Color:
        """Return or set x axis color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.axes.x_color = 'red'
        """
        return self._x_color

    @x_color.setter
    def x_color(self, color: color_like):
        self._x_color = Color(color)

    @property
    def y_color(self) -> Color:
        """Return or set y axis color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.axes.y_color = 'red'
        """
        return self._y_color

    @y_color.setter
    def y_color(self, color: color_like):
        self._y_color = Color(color)

    @property
    def z_color(self) -> Color:
        """Return or set z axis color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.axes.z_color = 'red'
        """
        return self._z_color

    @z_color.setter
    def z_color(self, color: color_like):
        self._z_color = Color(color)

    @property
    def box(self) -> bool:
        """Use the ``vtk.vtkCubeAxesActor`` instead of the default ``vtk.vtkAxesActor``.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.axes.box = True

        """
        return self._box

    @box.setter
    def box(self, box: bool):
        self._box = bool(box)

    @property
    def show(self) -> bool:
        """Show or hide the axes actor.

        Examples
        --------
        Hide the axes by default.

        >>> import pyvista
        >>> pyvista.global_theme.axes.show = False

        """
        return self._show

    @show.setter
    def show(self, show: bool):
        self._show = bool(show)


class _Font(_ThemeConfig):
    """PyVista plotter font configuration.

    Examples
    --------
    Set the default font family to 'arial'.  Must be either
    'arial', 'courier', or 'times'.

    >>> import pyvista
    >>> pyvista.global_theme.font.family = 'arial'

    Set the default font size to 20.

    >>> pyvista.global_theme.font.size = 20

    Set the default title size to 40

    >>> pyvista.global_theme.font.title_size = 40

    Set the default label size to 10

    >>> pyvista.global_theme.font.label_size = 10

    Set the default text color to 'grey'

    >>> pyvista.global_theme.font.color = 'grey'

    Set the string formatter used to format numerical data to '%.6e'

    >>> pyvista.global_theme.font.fmt = '%.6e'

    """

    __slots__ = ['_family', '_size', '_title_size', '_label_size', '_color', '_fmt']

    def __init__(self):
        self._family = 'arial'
        self._size = 12
        self._title_size = None
        self._label_size = None
        self._color = Color('white')
        self._fmt = None

    def __repr__(self):
        txt = ['']
        parm = {
            'Family': 'family',
            'Size': 'size',
            'Title size': 'title_size',
            'Label size': 'label_size',
            'Color': 'color',
            'Float format': 'fmt',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'    {name:<21}: {setting}')

        return '\n'.join(txt)

    @property
    def family(self) -> str:
        """Return or set the font family.

        Must be one of the following:

        * ``"arial"``
        * ``"courier"``
        * ``"times"``

        Examples
        --------
        Set the default global font family to 'courier'.

        >>> import pyvista
        >>> pyvista.global_theme.font.family = 'courier'

        """
        return self._family

    @family.setter
    def family(self, family: str):
        parse_font_family(family)  # check valid font
        self._family = family

    @property
    def size(self) -> int:
        """Return or set the font size.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.font.size = 20

        """
        return self._size

    @size.setter
    def size(self, size: int):
        self._size = int(size)

    @property
    def title_size(self) -> int:
        """Return or set the title size.

        If ``None``, then VTK uses ``UnconstrainedFontSizeOn`` for titles.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.font.title_size = 20
        """
        return self._title_size

    @title_size.setter
    def title_size(self, title_size: int):
        if title_size is None:
            self._title_size = None
        else:
            self._title_size = int(title_size)

    @property
    def label_size(self) -> int:
        """Return or set the label size.

        If ``None``, then VTK uses ``UnconstrainedFontSizeOn`` for labels.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.font.label_size = 20
        """
        return self._label_size

    @label_size.setter
    def label_size(self, label_size: int):
        if label_size is None:
            self._label_size = None
        else:
            self._label_size = int(label_size)

    @property
    def color(self) -> Color:
        """Return or set the font color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.font.color = 'black'
        """
        return self._color

    @color.setter
    def color(self, color: color_like):
        self._color = Color(color)

    @property
    def fmt(self) -> str:
        """Return or set the string formatter used to format numerical data.

        Examples
        --------
        Set the string formatter used to format numerical data to '%.6e'.

        >>> import pyvista
        >>> pyvista.global_theme.font.fmt = '%.6e'

        """
        return self._fmt

    @fmt.setter
    def fmt(self, fmt: str):
        self._fmt = fmt


class _SliderStyleConfig(_ThemeConfig):
    """PyVista configuration for a single slider style."""

    __slots__ = [
        '_name',
        '_slider_length',
        '_slider_width',
        '_slider_color',
        '_tube_width',
        '_tube_color',
        '_cap_opacity',
        '_cap_length',
        '_cap_width',
    ]

    def __init__(self):
        """Initialize the slider style configuration."""
        self._name = None
        self._slider_length = None
        self._slider_width = None
        self._slider_color = None
        self._tube_width = None
        self._tube_color = None
        self._cap_opacity = None
        self._cap_length = None
        self._cap_width = None

    @property
    def name(self) -> str:
        """Return the name of the slider style configuration."""
        return self._name

    @name.setter
    def name(self, name: str):
        self._name = name

    @property
    def cap_width(self) -> float:
        """Return or set the cap width.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.cap_width = 0.02

        """
        return self._cap_width

    @cap_width.setter
    def cap_width(self, cap_width: float):
        self._cap_width = float(cap_width)

    @property
    def cap_length(self) -> float:
        """Return or set the cap length.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.cap_length = 0.01

        """
        return self._cap_length

    @cap_length.setter
    def cap_length(self, cap_length: float):
        self._cap_length = float(cap_length)

    @property
    def cap_opacity(self) -> float:
        """Return or set the cap opacity.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.cap_opacity = 1.0

        """
        return self._cap_opacity

    @cap_opacity.setter
    def cap_opacity(self, cap_opacity: float):
        self._cap_opacity = float(cap_opacity)

    @property
    def tube_color(self) -> Color:
        """Return or set the tube color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.tube_color = 'black'
        """
        return self._tube_color

    @tube_color.setter
    def tube_color(self, tube_color: color_like):
        self._tube_color = Color(tube_color)

    @property
    def tube_width(self) -> float:
        """Return or set the tube_width.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.tube_width = 0.005

        """
        return self._tube_width

    @tube_width.setter
    def tube_width(self, tube_width: float):
        self._tube_width = float(tube_width)

    @property
    def slider_color(self) -> Color:
        """Return or set the slider color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.slider_color = 'grey'

        """
        return self._slider_color

    @slider_color.setter
    def slider_color(self, slider_color: color_like):
        self._slider_color = Color(slider_color)

    @property
    def slider_width(self) -> float:
        """Return or set the slider width.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.slider_width = 0.04

        """
        return self._slider_width

    @slider_width.setter
    def slider_width(self, slider_width: float):
        self._slider_width = float(slider_width)

    @property
    def slider_length(self) -> float:
        """Return or set the slider_length.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.slider_styles.modern.slider_length = 0.02

        """
        return self._slider_length

    @slider_length.setter
    def slider_length(self, slider_length: float):
        self._slider_length = float(slider_length)

    def __repr__(self):
        txt = ['']
        parm = {
            'Slider length': 'slider_length',
            'Slider width': 'slider_width',
            'Slider color': 'slider_color',
            'Tube width': 'tube_width',
            'Tube color': 'tube_color',
            'Cap opacity': 'cap_opacity',
            'Cap length': 'cap_length',
            'Cap width': 'cap_width',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'        {name:<17}: {setting}')
        return '\n'.join(txt)


class _SliderConfig(_ThemeConfig):
    """PyVista configuration encompassing all slider styles.

    Examples
    --------
    Set the classic slider configuration.

    >>> import pyvista
    >>> pyvista.global_theme.slider_styles.classic.slider_length = 0.02
    >>> pyvista.global_theme.slider_styles.classic.slider_width = 0.04
    >>> pyvista.global_theme.slider_styles.classic.slider_color = (0.5, 0.5, 0.5)
    >>> pyvista.global_theme.slider_styles.classic.tube_width = 0.005
    >>> pyvista.global_theme.slider_styles.classic.tube_color = (1.0, 1.0, 1.0)
    >>> pyvista.global_theme.slider_styles.classic.cap_opacity = 1
    >>> pyvista.global_theme.slider_styles.classic.cap_length = 0.01
    >>> pyvista.global_theme.slider_styles.classic.cap_width = 0.02

    Set the modern slider configuration.

    >>> pyvista.global_theme.slider_styles.modern.slider_length = 0.02
    >>> pyvista.global_theme.slider_styles.modern.slider_width = 0.04
    >>> pyvista.global_theme.slider_styles.modern.slider_color = (0.43, 0.44, 0.45)
    >>> pyvista.global_theme.slider_styles.modern.tube_width = 0.04
    >>> pyvista.global_theme.slider_styles.modern.tube_color = (0.69, 0.70, 0.709)
    >>> pyvista.global_theme.slider_styles.modern.cap_opacity = 0
    >>> pyvista.global_theme.slider_styles.modern.cap_length = 0.01
    >>> pyvista.global_theme.slider_styles.modern.cap_width = 0.02

    """

    __slots__ = ['_classic', '_modern']

    def __init__(self):
        """Initialize the slider configuration."""
        self._classic = _SliderStyleConfig()
        self._classic.name = 'classic'
        self._classic.slider_length = 0.02
        self._classic.slider_width = 0.04
        self._classic.slider_color = 'gray'
        self._classic.tube_width = 0.005
        self._classic.tube_color = 'white'
        self._classic.cap_opacity = 1
        self._classic.cap_length = 0.01
        self._classic.cap_width = 0.02

        self._modern = _SliderStyleConfig()
        self._modern.name = 'modern'
        self._modern.slider_length = 0.02
        self._modern.slider_width = 0.04
        self._modern.slider_color = (110, 113, 117)
        self._modern.tube_width = 0.04
        self._modern.tube_color = (178, 179, 181)
        self._modern.cap_opacity = 0
        self._modern.cap_length = 0.01
        self._modern.cap_width = 0.02

    @property
    def classic(self) -> _SliderStyleConfig:
        """Return the Classic slider configuration."""
        return self._classic

    @classic.setter
    def classic(self, config: _SliderStyleConfig):
        if not isinstance(config, _SliderStyleConfig):
            raise TypeError('Configuration type must be `_SliderStyleConfig`')
        self._classic = config

    @property
    def modern(self) -> _SliderStyleConfig:
        """Return the Modern slider configuration."""
        return self._modern

    @modern.setter
    def modern(self, config: _SliderStyleConfig):
        if not isinstance(config, _SliderStyleConfig):
            raise TypeError('Configuration type must be `_SliderStyleConfig`')
        self._modern = config

    def __repr__(self):
        txt = ['']
        parm = {
            'Classic': 'classic',
            'Modern': 'modern',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'    {name:<21}: {setting}')
        return '\n'.join(txt)

    def __iter__(self):
        for style in [self._classic, self._modern]:
            yield style.name


class DefaultTheme(_ThemeConfig):
    """PyVista default theme.

    Examples
    --------
    Change the global default background color to white.

    >>> import pyvista
    >>> pyvista.global_theme.color = 'white'

    Show edges by default.

    >>> pyvista.global_theme.show_edges = True

    Create a new theme from the default theme and apply it globally.

    >>> my_theme = pyvista.themes.DefaultTheme()
    >>> my_theme.color = 'red'
    >>> my_theme.background = 'white'
    >>> pyvista.global_theme.load_theme(my_theme)

    """

    __slots__ = [
        '_name',
        '_background',
        '_jupyter_backend',
        '_full_screen',
        '_window_size',
        '_camera',
        '_notebook',
        '_font',
        '_auto_close',
        '_cmap',
        '_color',
        '_nan_color',
        '_edge_color',
        '_outline_color',
        '_floor_color',
        '_colorbar_orientation',
        '_colorbar_horizontal',
        '_colorbar_vertical',
        '_show_scalar_bar',
        '_show_edges',
        '_lighting',
        '_interactive',
        '_render_points_as_spheres',
        '_transparent_background',
        '_title',
        '_axes',
        '_multi_samples',
        '_multi_rendering_splitting_position',
        '_volume_mapper',
        '_smooth_shading',
        '_depth_peeling',
        '_silhouette',
        '_slider_styles',
        '_return_cpos',
        '_hidden_line_removal',
        '_anti_aliasing',
        '_enable_camera_orientation_widget',
        '_split_sharp_edges',
        '_sharp_edges_feature_angle',
    ]

    def __init__(self):
        """Initialize the theme."""
        self._name = 'default'
        self._background = Color([0.3, 0.3, 0.3])
        self._full_screen = False
        self._camera = {
            'position': [1, 1, 1],
            'viewup': [0, 0, 1],
        }

        self._notebook = None
        self._window_size = [1024, 768]
        self._font = _Font()
        self._cmap = 'viridis'
        self._color = Color('white')
        self._nan_color = Color('darkgray')
        self._edge_color = Color('black')
        self._outline_color = Color('white')
        self._floor_color = Color('gray')
        self._colorbar_orientation = 'horizontal'

        self._colorbar_horizontal = _ColorbarConfig()
        self._colorbar_horizontal.width = 0.6
        self._colorbar_horizontal.height = 0.08
        self._colorbar_horizontal.position_x = 0.35
        self._colorbar_horizontal.position_y = 0.05

        self._colorbar_vertical = _ColorbarConfig()
        self._colorbar_vertical.width = 0.08
        self._colorbar_vertical.height = 0.45
        self._colorbar_vertical.position_x = 0.9
        self._colorbar_vertical.position_y = 0.02

        self._show_scalar_bar = True
        self._show_edges = False
        self._lighting = True
        self._interactive = False
        self._render_points_as_spheres = False
        self._transparent_background = False
        self._title = 'PyVista'
        self._axes = _AxesConfig()
        self._split_sharp_edges = False
        self._sharp_edges_feature_angle = 30.0

        # Grab system flag for anti-aliasing
        try:
            self._multi_samples = int(os.environ.get('PYVISTA_MULTI_SAMPLES', 4))
        except ValueError:  # pragma: no cover
            self._multi_samples = 4

        # Grab system flag for auto-closing
        self._auto_close = os.environ.get('PYVISTA_AUTO_CLOSE', '').lower() != 'false'

        self._jupyter_backend = os.environ.get('PYVISTA_JUPYTER_BACKEND', 'ipyvtklink')

        self._multi_rendering_splitting_position = None
        self._volume_mapper = 'fixed_point' if os.name == 'nt' else 'smart'
        self._smooth_shading = False
        self._depth_peeling = _DepthPeelingConfig()
        self._silhouette = _SilhouetteConfig()
        self._slider_styles = _SliderConfig()
        self._return_cpos = True
        self._hidden_line_removal = False
        self._anti_aliasing = None
        self._enable_camera_orientation_widget = False

    @property
    def hidden_line_removal(self) -> bool:
        """Return or set hidden line removal.

        Wireframe geometry will be drawn using hidden line removal if
        the rendering engine supports it.

        See Also
        --------
        pyvista.BasePlotter.enable_hidden_line_removal

        Examples
        --------
        Enable hidden line removal.

        >>> import pyvista
        >>> pyvista.global_theme.hidden_line_removal = True
        >>> pyvista.global_theme.hidden_line_removal
        True

        """
        return self._hidden_line_removal

    @hidden_line_removal.setter
    def hidden_line_removal(self, value: bool):
        self._hidden_line_removal = value

    @property
    def return_cpos(self) -> bool:
        """Return or set the default behavior of returning the camera position.

        Examples
        --------
        Disable returning camera position by ``show`` and ``plot`` methods.

        >>> import pyvista
        >>> pyvista.global_theme.return_cpos = False
        """

    @return_cpos.setter
    def return_cpos(self, value: bool):
        self._return_cpos = value

    @property
    def background(self) -> Color:
        """Return or set the default background color of pyvista plots.

        Examples
        --------
        Set the default global background of all plots to white.

        >>> import pyvista
        >>> pyvista.global_theme.background = 'white'
        """
        return self._background

    @background.setter
    def background(self, new_background: color_like):
        self._background = Color(new_background)

    @property
    def jupyter_backend(self) -> str:
        """Return or set the jupyter notebook plotting backend.

        Jupyter backend to use when plotting.  Must be one of the
        following:

        * ``'ipyvtklink'`` : Render remotely and stream the
          resulting VTK images back to the client.  Supports all VTK
          methods, but suffers from lag due to remote rendering.
          Requires that a virtual framebuffer be set up when displaying
          on a headless server.  Must have ``ipyvtklink`` installed.

        * ``'panel'`` : Convert the VTK render window to a vtkjs
          object and then visualize that within jupyterlab. Supports
          most VTK objects.  Requires that a virtual framebuffer be
          set up when displaying on a headless server.  Must have
          ``panel`` installed.

        * ``'ipygany'`` : Convert all the meshes into ``ipygany``
          meshes and streams those to be rendered on the client side.
          Supports VTK meshes, but few others.  Aside from ``none``,
          this is the only method that does not require a virtual
          framebuffer.  Must have ``ipygany`` installed.

        * ``'pythreejs'`` : Convert all the meshes into ``pythreejs``
          meshes and streams those to be rendered on the client side.
          Aside from ``ipygany``, this is the only method that does
          not require a virtual framebuffer.  Must have ``pythreejs``
          installed.

        * ``'static'`` : Display a single static image within the
          JupyterLab environment.  Still requires that a virtual
          framebuffer be set up when displaying on a headless server,
          but does not require any additional modules to be installed.

        * ``'none'`` : Do not display any plots within jupyterlab,
          instead display using dedicated VTK render windows.  This
          will generate nothing on headless servers even with a
          virtual framebuffer.

        Examples
        --------
        Enable the pythreejs backend.

        >>> import pyvista as pv
        >>> pv.set_jupyter_backend('pythreejs')

        Enable the ipygany backend.

        >>> import pyvista as pv
        >>> pv.set_jupyter_backend('ipygany')

        Enable the panel backend.

        >>> pv.set_jupyter_backend('panel')

        Enable the ipyvtklink backend.

        >>> pv.set_jupyter_backend('ipyvtklink')

        Just show static images.

        >>> pv.set_jupyter_backend('static')

        Disable all plotting within JupyterLab and display using a
        standard desktop VTK render window.

        >>> pv.set_jupyter_backend(None)  # or 'none'

        """
        return self._jupyter_backend

    @jupyter_backend.setter
    def jupyter_backend(self, backend: 'str'):
        from pyvista.jupyter import _validate_jupyter_backend

        self._jupyter_backend = _validate_jupyter_backend(backend)

    @property
    def auto_close(self) -> bool:
        """Automatically close the figures when finished plotting.

        .. DANGER::
           Set to ``False`` with extreme caution.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.auto_close = False

        """
        return self._auto_close

    @auto_close.setter
    def auto_close(self, value: bool):
        self._auto_close = value

    @property
    def full_screen(self) -> bool:
        """Return if figures are shown in full screen.

        Examples
        --------
        Set windows to be full screen by default.

        >>> import pyvista
        >>> pyvista.global_theme.full_screen = True
        """
        return self._full_screen

    @full_screen.setter
    def full_screen(self, value: bool):
        self._full_screen = value

    @property
    def enable_camera_orientation_widget(self) -> bool:
        """Enable the camera orientation widget in all plotters.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.enable_camera_orientation_widget = True
        >>> pyvista.global_theme.enable_camera_orientation_widget
        True

        """
        return self._enable_camera_orientation_widget

    @enable_camera_orientation_widget.setter
    def enable_camera_orientation_widget(self, value: bool):
        self._enable_camera_orientation_widget = value

    @property
    def camera(self):
        """Return or set the default camera position.

        Examples
        --------
        Set both the position and view of the camera.

        >>> import pyvista
        >>> pyvista.global_theme.camera = {'position': [1, 1, 1],
        ...                                'viewup': [0, 0, 1]}

        Set the default position of the camera.

        >>> pyvista.global_theme.camera['position'] = [1, 1, 1]

        Set the default view of the camera.

        >>> pyvista.global_theme.camera['viewup'] = [0, 0, 1]

        """
        return self._camera

    @camera.setter
    def camera(self, camera):
        if not isinstance(camera, dict):
            raise TypeError(f'Expected ``camera`` to be a dict, not {type(camera).__name__}.')

        if 'position' not in camera:
            raise KeyError('Expected the "position" key in the camera dict.')
        if 'viewup' not in camera:
            raise KeyError('Expected the "viewup" key in the camera dict.')

        self._camera = camera

    @property
    def notebook(self) -> Union[bool, None]:
        """Return or set the state of notebook plotting.

        Setting this to ``True`` always enables notebook plotting,
        while setting it to ``False`` disables plotting even when
        plotting within a jupyter notebook and plots externally.

        Examples
        --------
        Disable all jupyter notebook plotting.

        >>> import pyvista
        >>> pyvista.global_theme.notebook = False

        """
        return self._notebook

    @notebook.setter
    def notebook(self, value: Union[bool, None]):
        self._notebook = value

    @property
    def window_size(self) -> List[int]:
        """Return or set the default render window size.

        Examples
        --------
        Set window size to ``[400, 400]``.

        >>> import pyvista
        >>> pyvista.global_theme.window_size = [400, 400]

        """
        return self._window_size

    @window_size.setter
    def window_size(self, window_size: List[int]):
        if len(window_size) != 2:
            raise ValueError('Expected a length 2 iterable for ``window_size``.')

        # ensure positive size
        if window_size[0] < 0 or window_size[1] < 0:
            raise ValueError('Window size must be a positive value.')

        self._window_size = window_size

    @property
    def font(self) -> _Font:
        """Return or set the default font size, family, and/or color.

        Examples
        --------
        Set the default font family to 'arial'.  Must be either
        'arial', 'courier', or 'times'.

        >>> import pyvista
        >>> pyvista.global_theme.font.family = 'arial'

        Set the default font size to 20.

        >>> pyvista.global_theme.font.size = 20

        Set the default title size to 40.

        >>> pyvista.global_theme.font.title_size = 40

        Set the default label size to 10.

        >>> pyvista.global_theme.font.label_size = 10

        Set the default text color to 'grey'.

        >>> pyvista.global_theme.font.color = 'grey'

        String formatter used to format numerical data to '%.6e'.

        >>> pyvista.global_theme.font.fmt = '%.6e'

        """
        return self._font

    @font.setter
    def font(self, config: _Font):
        if not isinstance(config, _Font):
            raise TypeError('Configuration type must be `_Font`.')
        self._font = config

    @property
    def cmap(self):
        """Return or set the default colormap of pyvista.

        See available Matplotlib colormaps.  Only applicable for when
        displaying ``scalars``. Requires Matplotlib to be installed.
        If ``colorcet`` or ``cmocean`` are installed, their colormaps
        can be specified by name.

        You can also specify a list of colors to override an existing
        colormap with a custom one.  For example, to create a three
        color colormap you might specify ``['green', 'red', 'blue']``

        Examples
        --------
        Set the default global colormap to 'jet'.

        >>> import pyvista
        >>> pyvista.global_theme.cmap = 'jet'

        """
        return self._cmap

    @cmap.setter
    def cmap(self, cmap):
        try:
            get_cmap_safe(cmap)  # for validation
            self._cmap = cmap
        except ImportError:  # pragma: no cover
            warnings.warn(
                'Unable to set a default theme colormap without matplotlib. '
                'The builtin VTK "jet" colormap will be used.'
            )
            self._cmap = None

    @property
    def color(self) -> Color:
        """Return or set the default color of meshes in pyvista.

        Used for meshes without ``scalars``.

        When setting, the value must be either a string, rgb list,
        or hex color string.  For example:

        * ``color='white'``
        * ``color='w'``
        * ``color=[1.0, 1.0, 1.0]``
        * ``color='#FFFFFF'``

        Examples
        --------
        Set the default mesh color to 'red'.

        >>> import pyvista
        >>> pyvista.global_theme.color = 'red'

        """
        return self._color

    @color.setter
    def color(self, color: color_like):
        self._color = Color(color)

    @property
    def nan_color(self) -> Color:
        """Return or set the default NaN color.

        This color is used to plot all NaN values.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.nan_color = 'darkgray'

        """
        return self._nan_color

    @nan_color.setter
    def nan_color(self, nan_color: color_like):
        self._nan_color = Color(nan_color)

    @property
    def edge_color(self) -> Color:
        """Return or set the default edge color.

        Examples
        --------
        Set the global edge color to 'blue'.

        >>> import pyvista
        >>> pyvista.global_theme.edge_color = 'blue'

        """
        return self._edge_color

    @edge_color.setter
    def edge_color(self, edge_color: color_like):
        self._edge_color = Color(edge_color)

    @property
    def outline_color(self) -> Color:
        """Return or set the default outline color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.outline_color = 'white'

        """
        return self._outline_color

    @outline_color.setter
    def outline_color(self, outline_color: color_like):
        self._outline_color = Color(outline_color)

    @property
    def floor_color(self) -> Color:
        """Return or set the default floor color.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.floor_color = 'black'

        """
        return self._floor_color

    @floor_color.setter
    def floor_color(self, floor_color: color_like):
        self._floor_color = Color(floor_color)

    @property
    def colorbar_orientation(self) -> str:
        """Return or set the default colorbar orientation.

        Must be either ``'vertical'`` or ``'horizontal'``.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.colorbar_orientation = 'horizontal'

        """
        return self._colorbar_orientation

    @colorbar_orientation.setter
    def colorbar_orientation(self, colorbar_orientation: str):
        if colorbar_orientation not in ['vertical', 'horizontal']:
            raise ValueError('Colorbar orientation must be either "vertical" or "horizontal"')
        self._colorbar_orientation = colorbar_orientation

    @property
    def colorbar_horizontal(self) -> _ColorbarConfig:
        """Return or set the default parameters of a horizontal colorbar.

        Examples
        --------
        Set the default horizontal colorbar width to 0.6.

        >>> import pyvista
        >>> pyvista.global_theme.colorbar_horizontal.width = 0.6

        Set the default horizontal colorbar height to 0.2.

        >>> pyvista.global_theme.colorbar_horizontal.height = 0.2

        """
        return self._colorbar_horizontal

    @colorbar_horizontal.setter
    def colorbar_horizontal(self, config: _ColorbarConfig):
        if not isinstance(config, _ColorbarConfig):
            raise TypeError('Configuration type must be `_ColorbarConfig`.')
        self._colorbar_horizontal = config

    @property
    def colorbar_vertical(self) -> _ColorbarConfig:
        """Return or set the default parameters of a vertical colorbar.

        Examples
        --------
        Set the default colorbar width to 0.45.

        >>> import pyvista
        >>> pyvista.global_theme.colorbar_vertical.width = 0.45

        Set the default colorbar height to 0.8.

        >>> import pyvista
        >>> pyvista.global_theme.colorbar_vertical.height = 0.8

        """
        return self._colorbar_vertical

    @colorbar_vertical.setter
    def colorbar_vertical(self, config: _ColorbarConfig):
        if not isinstance(config, _ColorbarConfig):
            raise TypeError('Configuration type must be `_ColorbarConfig`.')
        self._colorbar_vertical = config

    @property
    def show_scalar_bar(self) -> bool:
        """Return or set the default color bar visibility.

        Examples
        --------
        Show the scalar bar by default when scalars are available.

        >>> import pyvista
        >>> pyvista.global_theme.show_scalar_bar = True

        """
        return self._show_scalar_bar

    @show_scalar_bar.setter
    def show_scalar_bar(self, show_scalar_bar: bool):
        self._show_scalar_bar = bool(show_scalar_bar)

    @property
    def show_edges(self) -> bool:
        """Return or set the default edge visibility.

        Examples
        --------
        Show edges globally by default.

        >>> import pyvista
        >>> pyvista.global_theme.show_edges = True

        """
        return self._show_edges

    @show_edges.setter
    def show_edges(self, show_edges: bool):
        self._show_edges = bool(show_edges)

    @property
    def lighting(self) -> bool:
        """Return or set the default ``lighting``.

        Examples
        --------
        Disable lighting globally.

        >>> import pyvista
        >>> pyvista.global_theme.lighting = False
        """
        return self._lighting

    @lighting.setter
    def lighting(self, lighting: bool):
        self._lighting = lighting

    @property
    def interactive(self) -> bool:
        """Return or set the default ``interactive`` parameter.

        Examples
        --------
        Make all plots non-interactive globally.

        >>> import pyvista
        >>> pyvista.global_theme.interactive = False
        """
        return self._interactive

    @interactive.setter
    def interactive(self, interactive: bool):
        self._interactive = bool(interactive)

    @property
    def render_points_as_spheres(self) -> bool:
        """Return or set the default ``render_points_as_spheres`` parameter.

        Examples
        --------
        Render points as spheres by default globally.

        >>> import pyvista
        >>> pyvista.global_theme.render_points_as_spheres = True
        """
        return self._render_points_as_spheres

    @render_points_as_spheres.setter
    def render_points_as_spheres(self, render_points_as_spheres: bool):
        self._render_points_as_spheres = bool(render_points_as_spheres)

    @property
    def transparent_background(self) -> bool:
        """Return or set the default ``transparent_background`` parameter.

        Examples
        --------
        Set transparent_background globally to ``True``.

        >>> import pyvista
        >>> pyvista.global_theme.transparent_background = True

        """
        return self._transparent_background

    @transparent_background.setter
    def transparent_background(self, transparent_background: bool):
        self._transparent_background = transparent_background

    @property
    def title(self) -> str:
        """Return or set the default ``title`` parameter.

        This is the VTK render window title.

        Examples
        --------
        Set title globally to 'plot'.

        >>> import pyvista
        >>> pyvista.global_theme.title = 'plot'

        """
        return self._title

    @title.setter
    def title(self, title: str):
        self._title = title

    @property
    def anti_aliasing(self) -> Optional[str]:
        """Enable or disable anti-aliasing.

        Should be either ``"ssaa"``, ``"msaa"``, ``"fxaa"``, or ``None``.

        Examples
        --------
        Use super-sampling anti-aliasing in the global theme.

        >>> import pyvista
        >>> pyvista.global_theme.anti_aliasing = 'ssaa'
        >>> pyvista.global_theme.anti_aliasing
        'ssaa'

        Disable anti-aliasing in the global theme.

        >>> import pyvista
        >>> pyvista.global_theme.anti_aliasing = None

        See :ref:`anti_aliasing_example` for more information regarding
        anti-aliasing.

        """
        return self._anti_aliasing

    @anti_aliasing.setter
    def anti_aliasing(self, anti_aliasing: Union[str, None]):
        if isinstance(anti_aliasing, bool):
            warnings.warn(
                '`anti_aliasing` is now a string or None and must be either "ssaa", '
                '"msaa", "fxaa", or None',
                PyVistaDeprecationWarning,
            )
            anti_aliasing = 'fxaa' if anti_aliasing else None

        if isinstance(anti_aliasing, str):
            if anti_aliasing not in ['ssaa', 'msaa', 'fxaa']:
                raise ValueError('anti_aliasing must be either "ssaa", "msaa", or "fxaa"')
        elif anti_aliasing is not None:
            raise TypeError('anti_aliasing must be either "ssaa", "msaa", "fxaa", or None')

        self._anti_aliasing = anti_aliasing

    @property
    def antialiasing(self):
        """Enable or disable anti-aliasing.

        .. deprecated:: 0.37.0
           Deprecated in favor of :attr:`anti_aliasing <DefaultTheme.anti_aliasing>`.
        """
        # Recommended removing at pyvista==0.40.0
        warnings.warn(
            'antialising is deprecated.  Please use `anti_aliasing` instead.',
            PyVistaDeprecationWarning,
        )
        return self.anti_aliasing

    @antialiasing.setter
    def antialiasing(self, value):  # pragma: no cover
        # Recommended removing at pyvista==0.40.0
        warnings.warn(
            'antialising is deprecated.  Please use `anti_aliasing` instead.',
            PyVistaDeprecationWarning,
        )
        self.anti_aliasing = value

    @property
    def multi_samples(self) -> int:
        """Return or set the default ``multi_samples`` parameter.

        Set the number of multisamples to used with hardware anti_aliasing. This
        is only used when :attr:`anti_aliasing <DefaultTheme.anti_aliasing>` is
        set to ``"msaa"``.

        Examples
        --------
        Set the default number of multisamples to 2 and enable ``"msaa"``

        >>> import pyvista
        >>> pyvista.global_theme.anti_aliasing = 'msaa'
        >>> pyvista.global_theme.multi_samples = 2

        """
        return self._multi_samples

    @multi_samples.setter
    def multi_samples(self, multi_samples: int):
        self._multi_samples = int(multi_samples)

    @property
    def multi_rendering_splitting_position(self) -> float:
        """Return or set the default ``multi_rendering_splitting_position`` parameter.

        Examples
        --------
        Set multi_rendering_splitting_position globally to 0.5 (the
        middle of the window).

        >>> import pyvista
        >>> pyvista.global_theme.multi_rendering_splitting_position = 0.5

        """
        return self._multi_rendering_splitting_position

    @multi_rendering_splitting_position.setter
    def multi_rendering_splitting_position(self, multi_rendering_splitting_position: float):
        self._multi_rendering_splitting_position = multi_rendering_splitting_position

    @property
    def volume_mapper(self) -> str:
        """Return or set the default ``volume_mapper`` parameter.

        Must be one of the following strings, which are mapped to the
        following VTK volume mappers.

        * ``'fixed_point'`` : ``vtk.vtkFixedPointVolumeRayCastMapper``
        * ``'gpu'`` : ``vtk.vtkGPUVolumeRayCastMapper``
        * ``'open_gl'`` : ``vtk.vtkOpenGLGPUVolumeRayCastMapper``
        * ``'smart'`` : ``vtk.vtkSmartVolumeMapper``

        Examples
        --------
        Set default volume mapper globally to 'gpu'.

        >>> import pyvista
        >>> pyvista.global_theme.volume_mapper = 'gpu'

        """
        return self._volume_mapper

    @volume_mapper.setter
    def volume_mapper(self, mapper: str):
        mappers = ['fixed_point', 'gpu', 'open_gl', 'smart']
        if mapper not in mappers:
            raise ValueError(
                f"Mapper ({mapper}) unknown. Available volume mappers "
                f"include:\n {', '.join(mappers)}"
            )

        self._volume_mapper = mapper

    @property
    def smooth_shading(self) -> bool:
        """Return or set the default ``smooth_shading`` parameter.

        Examples
        --------
        Set the global smooth_shading parameter default to ``True``.

        >>> import pyvista
        >>> pyvista.global_theme.smooth_shading = True

        """
        return self._smooth_shading

    @smooth_shading.setter
    def smooth_shading(self, smooth_shading: bool):
        self._smooth_shading = bool(smooth_shading)

    @property
    def depth_peeling(self) -> _DepthPeelingConfig:
        """Return or set the default depth peeling parameters.

        Examples
        --------
        Set the global depth_peeling parameter default to be enabled
        with 8 peels.

        >>> import pyvista
        >>> pyvista.global_theme.depth_peeling.number_of_peels = 8
        >>> pyvista.global_theme.depth_peeling.occlusion_ratio = 0.0
        >>> pyvista.global_theme.depth_peeling.enabled = True

        """
        return self._depth_peeling

    @depth_peeling.setter
    def depth_peeling(self, config: _DepthPeelingConfig):
        if not isinstance(config, _DepthPeelingConfig):
            raise TypeError('Configuration type must be `_DepthPeelingConfig`.')
        self._depth_peeling = config

    @property
    def silhouette(self) -> _SilhouetteConfig:
        """Return or set the default ``silhouette`` configuration.

        Examples
        --------
        Set parameters of the silhouette.

        >>> import pyvista
        >>> pyvista.global_theme.silhouette.color = 'grey'
        >>> pyvista.global_theme.silhouette.line_width = 2.0
        >>> pyvista.global_theme.silhouette.feature_angle = 20

        """
        return self._silhouette

    @silhouette.setter
    def silhouette(self, config: _SilhouetteConfig):
        if not isinstance(config, _SilhouetteConfig):
            raise TypeError('Configuration type must be `_SilhouetteConfig`')
        self._silhouette = config

    @property
    def slider_styles(self) -> _SliderConfig:
        """Return the default slider style configurations."""
        return self._slider_styles

    @slider_styles.setter
    def slider_styles(self, config: _SliderConfig):
        if not isinstance(config, _SliderConfig):
            raise TypeError('Configuration type must be `_SliderConfig`.')
        self._slider_styles = config

    @property
    def axes(self) -> _AxesConfig:
        """Return or set the default ``axes`` configuration.

        Examples
        --------
        Set the x axis color to black.

        >>> import pyvista
        >>> pyvista.global_theme.axes.x_color = 'black'

        Show axes by default.

        >>> pyvista.global_theme.axes.show = True

        Use the ``vtk.vtkCubeAxesActor``.

        >>> pyvista.global_theme.axes.box = True

        """
        return self._axes

    @axes.setter
    def axes(self, config: _AxesConfig):
        if not isinstance(config, _AxesConfig):
            raise TypeError('Configuration type must be `_AxesConfig`.')
        self._axes = config

    def restore_defaults(self):
        """Restore the theme defaults.

        Examples
        --------
        >>> import pyvista
        >>> pyvista.global_theme.restore_defaults()

        """
        self.__init__()

    def __repr__(self):
        """User friendly representation of the current theme."""
        txt = [f'{self.name.capitalize()} Theme']
        txt.append('-' * len(txt[0]))
        parm = {
            'Background': 'background',
            'Jupyter backend': 'jupyter_backend',
            'Full screen': 'full_screen',
            'Window size': 'window_size',
            'Camera': 'camera',
            'Notebook': 'notebook',
            'Font': 'font',
            'Auto close': 'auto_close',
            'Colormap': 'cmap',
            'Color': 'color',
            'NaN color': 'nan_color',
            'Edge color': 'edge_color',
            'Outline color': 'outline_color',
            'Floor color': 'floor_color',
            'Colorbar orientation': 'colorbar_orientation',
            'Colorbar - horizontal': 'colorbar_horizontal',
            'Colorbar - vertical': 'colorbar_vertical',
            'Show scalar bar': 'show_scalar_bar',
            'Show edges': 'show_edges',
            'Lighting': 'lighting',
            'Interactive': 'interactive',
            'Render points as spheres': 'render_points_as_spheres',
            'Transparent Background': 'transparent_background',
            'Title': 'title',
            'Axes': 'axes',
            'Multi-samples': 'multi_samples',
            'Multi-renderer Split Pos': 'multi_rendering_splitting_position',
            'Volume mapper': 'volume_mapper',
            'Smooth shading': 'smooth_shading',
            'Depth peeling': 'depth_peeling',
            'Silhouette': 'silhouette',
            'Slider Styles': 'slider_styles',
            'Return Camera Position': 'return_cpos',
            'Hidden Line Removal': 'hidden_line_removal',
            'Anti-Aliasing': '_anti_aliasing',
            'Split sharp edges': '_split_sharp_edges',
            'Sharp edges feat. angle': '_sharp_edges_feature_angle',
        }
        for name, attr in parm.items():
            setting = getattr(self, attr)
            txt.append(f'{name:<25}: {setting}')

        return '\n'.join(txt)

    @property
    def name(self) -> str:
        """Return or set the name of the theme."""
        return self._name

    @name.setter
    def name(self, name: str):
        self._name = name

    def load_theme(self, theme):
        """Overwrite the current theme with a theme.

        Parameters
        ----------
        theme : pyvista.DefaultTheme
            Theme to use to overwrite this theme.

        Examples
        --------
        Create a custom theme from the default theme and load it into
        the global theme of pyvista.

        >>> import pyvista
        >>> from pyvista.themes import DefaultTheme
        >>> my_theme = DefaultTheme()
        >>> my_theme.font.size = 20
        >>> my_theme.font.title_size = 40
        >>> my_theme.cmap = 'jet'
        ...
        >>> pyvista.global_theme.load_theme(my_theme)
        >>> pyvista.global_theme.font.size
        20

        Create a custom theme from the dark theme and load it into
        pyvista.

        >>> from pyvista.themes import DarkTheme
        >>> my_theme = DarkTheme()
        >>> my_theme.show_edges = True
        >>> pyvista.global_theme.load_theme(my_theme)
        >>> pyvista.global_theme.show_edges
        True

        """
        if isinstance(theme, str):
            theme = load_theme(theme)

        if not isinstance(theme, DefaultTheme):
            raise TypeError(
                '``theme`` must be a pyvista theme like ``pyvista.themes.DefaultTheme``.'
            )

        for attr_name in theme.__slots__:
            setattr(self, attr_name, getattr(theme, attr_name))

    def save(self, filename):
        """Serialize this theme to a json file.

        Parameters
        ----------
        filename : str
            Path to save the theme to.  Should end in ``'.json'``.

        Examples
        --------
        Export and then load back in a theme.

        >>> import pyvista
        >>> theme = pyvista.themes.DefaultTheme()
        >>> theme.background = 'white'
        >>> theme.save('my_theme.json')  # doctest:+SKIP
        >>> loaded_theme = pyvista.load_theme('my_theme.json')  # doctest:+SKIP

        """
        with open(filename, 'w') as f:
            json.dump(self.to_dict(), f)

    @property
    def use_ipyvtk(self):  # pragma: no cover
        """Set or return the usage of "ipyvtk" as a jupyter backend.

        .. deprecated:: 0.35.0
           Deprecated in favor of ``jupyter_backend``.
        """
        warnings.warn(
            'use_ipyvtk is deprecated.  Please use ``pyvista.global_theme.jupyter_backend``',
            DeprecationWarning,
        )
        return self.jupyter_backend == 'ipyvtklink'

    @use_ipyvtk.setter
    def use_ipyvtk(self, value):  # pragma: no cover
        warnings.warn(
            'use_ipyvtk is deprecated.  Please use ``pyvista.global_theme.jupyter_backend``',
            DeprecationWarning,
        )

        if value:
            self.jupyter_backend = 'ipyvtklink'
        else:
            self.jupyter_backend = 'static'

    @property
    def split_sharp_edges(self) -> bool:
        """Set or return splitting sharp edges.

        See :ref:`shading_example` for an example showing split sharp edges.

        Examples
        --------
        Enable the splitting of sharp edges globally.

        >>> import pyvista
        >>> pyvista.global_theme.split_sharp_edges = True
        >>> pyvista.global_theme.split_sharp_edges
        True

        Disable the splitting of sharp edges globally.

        >>> import pyvista
        >>> pyvista.global_theme.split_sharp_edges = False
        >>> pyvista.global_theme.split_sharp_edges
        False

        """
        return self._split_sharp_edges

    @split_sharp_edges.setter
    def split_sharp_edges(self, value: bool):
        self._split_sharp_edges = value

    @property
    def sharp_edges_feature_angle(self) -> float:
        """Set or return the angle of the sharp edges feature angle.

        See :ref:`shading_example` for an example showing split sharp edges.

        Examples
        --------
        Change the sharp edges feature angle to 45 degrees.

        >>> import pyvista
        >>> pyvista.global_theme.sharp_edges_feature_angle = 45.0
        >>> pyvista.global_theme.sharp_edges_feature_angle
        45.0

        """
        return self._sharp_edges_feature_angle

    @sharp_edges_feature_angle.setter
    def sharp_edges_feature_angle(self, value: float):
        self._sharp_edges_feature_angle = float(value)


class DarkTheme(DefaultTheme):
    """Dark mode theme.

    Black background, "viridis" colormap, tan meshes, white (hidden) edges.

    Examples
    --------
    Make the dark theme the global default.

    >>> import pyvista
    >>> from pyvista import themes
    >>> pyvista.set_plot_theme(themes.DarkTheme())

    Alternatively, set via a string.

    >>> pyvista.set_plot_theme('dark')

    """

    def __init__(self):
        """Initialize the theme."""
        super().__init__()
        self.name = 'dark'
        self.background = 'black'
        self.cmap = 'viridis'
        self.font.color = 'white'
        self.show_edges = False
        self.color = 'tan'
        self.outline_color = 'white'
        self.edge_color = 'white'
        self.axes.x_color = 'tomato'
        self.axes.y_color = 'seagreen'
        self.axes.z_color = 'blue'


class ParaViewTheme(DefaultTheme):
    """A paraview-like theme.

    Examples
    --------
    Make the paraview-like theme the global default.

    >>> import pyvista
    >>> from pyvista import themes
    >>> pyvista.set_plot_theme(themes.ParaViewTheme())

    Alternatively, set via a string.

    >>> pyvista.set_plot_theme('paraview')

    """

    def __init__(self):
        """Initialize theme."""
        super().__init__()
        self.name = 'paraview'
        self.background = 'paraview'
        self.cmap = 'coolwarm'
        self.font.family = 'arial'
        self.font.label_size = 16
        self.font.color = 'white'
        self.show_edges = False
        self.color = 'white'
        self.outline_color = 'white'
        self.edge_color = 'black'
        self.axes.x_color = 'tomato'
        self.axes.y_color = 'gold'
        self.axes.z_color = 'green'


class DocumentTheme(DefaultTheme):
    """A document theme well suited for papers and presentations.

    This theme uses:

    * A white background
    * Black fonts
    * The "viridis" colormap
    * disables edges for surface plots
    * Hidden edge removal

    Best used for presentations, papers, etc.

    Examples
    --------
    Make the document theme the global default.

    >>> import pyvista
    >>> from pyvista import themes
    >>> pyvista.set_plot_theme(themes.DocumentTheme())

    Alternatively, set via a string.

    >>> pyvista.set_plot_theme('document')

    """

    def __init__(self):
        """Initialize the theme."""
        super().__init__()
        self.name = 'document'
        self.background = 'white'
        self.cmap = 'viridis'
        self.font.size = 18
        self.font.title_size = 18
        self.font.label_size = 18
        self.font.color = 'black'
        self.show_edges = False
        self.color = 'tan'
        self.outline_color = 'black'
        self.edge_color = 'black'
        self.axes.x_color = 'tomato'
        self.axes.y_color = 'seagreen'
        self.axes.z_color = 'blue'


class _TestingTheme(DefaultTheme):
    """Low resolution testing theme for ``pytest``.

    Necessary for image regression.  Xvfb doesn't support
    multi-sampling, it's disabled for consistency between desktops and
    remote testing.

    Also disables ``return_cpos`` to make it easier for us to write
    examples without returning camera positions.

    """

    def __init__(self):
        super().__init__()
        self.name = 'testing'
        self.multi_samples = 1
        self.window_size = [400, 400]
        self.axes.show = False
        self.return_cpos = False


class _ALLOWED_THEMES(Enum):
    """Global built-in themes available to PyVista."""

    paraview = ParaViewTheme
    document = DocumentTheme
    dark = DarkTheme
    default = DefaultTheme
    testing = _TestingTheme


# tests/examples/test_cells.py

from pyvista import CellType
from pyvista.examples import cells


def test_empty():
    grid = cells.Empty()
    assert grid.celltypes[0] == CellType.EMPTY_CELL
    assert grid.n_cells == 1
    assert grid.n_points == 0


def test_vertex():
    grid = cells.Vertex()
    assert grid.celltypes[0] == CellType.VERTEX
    assert grid.n_cells == 1
    assert grid.n_points == 1


def test_poly_vertex():
    grid = cells.PolyVertex()
    assert grid.celltypes[0] == CellType.POLY_VERTEX
    assert grid.n_cells == 1
    assert grid.n_points >= 1


def test_line():
    grid = cells.Line()
    assert grid.celltypes[0] == CellType.LINE
    assert grid.n_cells == 1
    assert grid.n_points == 2


def test_poly_line():
    grid = cells.PolyLine()
    assert grid.celltypes[0] == CellType.POLY_LINE
    assert grid.n_cells == 1
    assert grid.n_points >= 1


def test_triangle():
    grid = cells.Triangle()
    assert grid.celltypes[0] == CellType.TRIANGLE
    assert grid.n_cells == 1
    assert grid.n_points == 3


def test_triangle_strip():
    grid = cells.TriangleStrip()
    assert grid.celltypes[0] == CellType.TRIANGLE_STRIP
    assert grid.n_cells == 1
    assert grid.n_points >= 3


def test_polygon():
    grid = cells.Polygon()
    assert grid.celltypes[0] == CellType.POLYGON
    assert grid.n_cells == 1
    assert grid.n_points == 6


def test_Quadrilateral():
    grid = cells.Quadrilateral()
    assert grid.celltypes[0] == CellType.QUAD
    assert grid.n_cells == 1
    assert grid.n_points == 4


def test_tetrahedron():
    grid = cells.Tetrahedron()
    assert grid.celltypes[0] == CellType.TETRA
    assert grid.n_cells == 1
    assert grid.n_points == 4


def test_hexagonal_prism():
    grid = cells.HexagonalPrism()
    assert grid.celltypes[0] == CellType.HEXAGONAL_PRISM
    assert grid.n_cells == 1
    assert grid.n_points == 12


def test_hexahedron():
    grid = cells.Hexahedron()
    assert grid.celltypes[0] == CellType.HEXAHEDRON
    assert grid.n_cells == 1
    assert grid.n_points == 8


def test_pyramid():
    grid = cells.Pyramid()
    assert grid.celltypes[0] == CellType.PYRAMID
    assert grid.n_cells == 1
    assert grid.n_points == 5


def test_wedge():
    grid = cells.Wedge()
    assert grid.celltypes[0] == CellType.WEDGE
    assert grid.n_cells == 1
    assert grid.n_points == 6


def test_pentagonal_prism():
    grid = cells.PentagonalPrism()
    assert grid.celltypes[0] == CellType.PENTAGONAL_PRISM
    assert grid.n_cells == 1
    assert grid.n_points == 10


def test_voxel():
    grid = cells.Voxel()
    assert grid.celltypes[0] == CellType.VOXEL
    assert grid.n_cells == 1
    assert grid.n_points == 8


def test_pixel():
    grid = cells.Pixel()
    assert grid.celltypes[0] == CellType.PIXEL
    assert grid.n_cells == 1
    assert grid.n_points == 4


# tests/examples/test_downloads.py

import os
from zipfile import ZipFile

import pytest

import pyvista
from pyvista import examples
from pyvista.examples.downloads import _cache_version
from pyvista.utilities.misc import PyVistaDeprecationWarning


def write_sample_zip(path):
    """Write a sample zip file."""
    tmp_file = os.path.join(path, 'tmp.txt')
    with open(tmp_file, 'w') as fid:
        fid.write('test')

    tmp_file2 = os.path.join(path, 'tmp2.txt')
    with open(tmp_file2, 'w') as fid:
        fid.write('test')

    zipfilename = os.path.join(path, 'sample.zip')
    with ZipFile(zipfilename, 'w') as zip_obj:
        zip_obj.write(tmp_file, os.path.basename(tmp_file))
        zip_obj.write(tmp_file2, os.path.basename(tmp_file2))

    os.remove(tmp_file)
    os.remove(tmp_file2)
    return zipfilename, tmp_file, tmp_file2


def test_cache_version(tmpdir):
    tmp_cache_file = str(tmpdir.join('VERSION'))

    # non-existent file should return 0
    assert not os.path.isfile(tmp_cache_file)
    assert _cache_version(tmp_cache_file) == 0

    # invalid file returns 0
    with open(tmp_cache_file, 'w') as fid:
        fid.write('aaa')
    assert _cache_version(tmp_cache_file) == 0

    # read it correctly
    ver = 1
    with open(tmp_cache_file, 'w') as fid:
        fid.write(str(ver))
    assert _cache_version(tmp_cache_file) == ver


def test_invalid_dir():
    old_path = pyvista.EXAMPLES_PATH
    try:
        pyvista.EXAMPLES_PATH = None
        with pytest.raises(FileNotFoundError):
            examples.downloads._check_examples_path()
    finally:
        pyvista.EXAMPLES_PATH = old_path


def test_decompress(tmpdir):
    old_path = pyvista.EXAMPLES_PATH
    try:
        pyvista.EXAMPLES_PATH = str(tmpdir.mkdir("tmpdir"))
        assert os.path.isdir(pyvista.EXAMPLES_PATH)
        zipfilename, tmp_file, tmp_file2 = write_sample_zip(pyvista.EXAMPLES_PATH)

        examples.downloads._decompress(zipfilename, pyvista.EXAMPLES_PATH)
        assert os.path.isfile(tmp_file)
        assert os.path.isfile(tmp_file2)

    finally:
        pyvista.EXAMPLES_PATH = old_path


def test_retrieve_zip(tmpdir):
    old_path = pyvista.EXAMPLES_PATH
    try:
        pyvista.EXAMPLES_PATH = str(tmpdir.mkdir("tmpdir"))
        assert os.path.isdir(pyvista.EXAMPLES_PATH)

        different_path = str(tmpdir.mkdir("different_tmpdir"))
        zipfilename, tmp_file, tmp_file2 = write_sample_zip(different_path)

        def retriever():
            return zipfilename, None

        path, _ = examples.downloads._retrieve_zip(retriever, zipfilename)
        assert os.path.isfile(os.path.join(path, os.path.basename(tmp_file)))
        assert os.path.isfile(os.path.join(path, os.path.basename(tmp_file2)))

    finally:
        pyvista.EXAMPLES_PATH = old_path


def test_delete_downloads(tmpdir):
    # change the path so we don't delete the examples cache
    old_path = pyvista.EXAMPLES_PATH
    try:
        pyvista.EXAMPLES_PATH = str(tmpdir.mkdir("tmpdir"))
        assert os.path.isdir(pyvista.EXAMPLES_PATH)
        tmp_file = os.path.join(pyvista.EXAMPLES_PATH, 'tmp.txt')
        with open(tmp_file, 'w') as fid:
            fid.write('test')
        examples.delete_downloads()
        assert os.path.isdir(pyvista.EXAMPLES_PATH)
        assert not os.path.isfile(tmp_file)
    finally:
        pyvista.EXAMPLES_PATH = old_path


def test_delete_downloads_does_not_exist(tmpdir):
    # change the path so we don't delete the examples cache
    old_path = pyvista.EXAMPLES_PATH
    new_path = str(tmpdir.join('doesnotexist'))

    try:
        # delete_downloads for a missing directory should not fail.
        pyvista.EXAMPLES_PATH = new_path
        assert not os.path.isdir(pyvista.EXAMPLES_PATH)
        examples.delete_downloads()
    finally:
        pyvista.EXAMPLES_PATH = old_path


def test_planets_deprecate_warnings():
    with pytest.warns(PyVistaDeprecationWarning):
        examples.download_mars_jpg()
    with pytest.warns(PyVistaDeprecationWarning):
        examples.download_stars_jpg()


# tests/examples/test_examples.py

import os

import numpy as np
import vtk

import pyvista
from pyvista import examples

ffmpeg_failed = False
try:
    try:
        import imageio_ffmpeg

        imageio_ffmpeg.get_ffmpeg_exe()
    except ImportError:
        import imageio

        imageio.plugins.ffmpeg.download()
except:  # noqa: E722
    ffmpeg_failed = True

TEST_DOWNLOADS = False
try:
    if os.environ['TEST_DOWNLOADS'].lower() == 'true':
        TEST_DOWNLOADS = True
except KeyError:
    pass


def test_load_nut():
    mesh = examples.load_nut()
    assert mesh.n_points


def test_load_ant():
    """Load ply ant mesh"""
    mesh = examples.load_ant()
    assert mesh.n_points


def test_load_airplane():
    """Load ply airplane mesh"""
    mesh = examples.load_airplane()
    assert mesh.n_points


def test_load_sphere():
    """Loads sphere ply mesh"""
    mesh = examples.load_sphere()
    assert mesh.n_points


def test_load_channels():
    """Loads geostat training image"""
    mesh = examples.load_channels()
    assert mesh.n_points


def test_load_spline():
    mesh = examples.load_spline()
    assert mesh.n_points


def test_load_random_hills():
    mesh = examples.load_random_hills()
    assert mesh.n_cells


def test_load_tetbeam():
    mesh = examples.load_tetbeam()
    assert mesh.n_cells
    assert (mesh.celltypes == 10).all()


def test_sphere_with_texture_map():
    sphere = pyvista.examples.planets._sphere_with_texture_map()
    assert isinstance(sphere, pyvista.PolyData)
    assert 'Texture Coordinates' in sphere.point_data
    assert sphere['Texture Coordinates'].shape == (sphere.n_points, 2)


def test_load_earth():
    mesh = pyvista.examples.planets.load_earth()
    assert isinstance(mesh, pyvista.PolyData)
    assert mesh.n_cells
    assert mesh.textures["surface"]


if TEST_DOWNLOADS:

    def test_download_single_sphere_animation():
        data = examples.download_single_sphere_animation()
        assert data.n_blocks

    def test_download_masonry_texture():
        data = examples.download_masonry_texture()
        assert isinstance(data, vtk.vtkTexture)

    def test_download_usa_texture():
        data = examples.download_usa_texture()
        assert isinstance(data, vtk.vtkTexture)

    def test_download_usa():
        data = examples.download_usa()
        assert np.any(data.points)

    def test_download_st_helens():
        data = examples.download_st_helens()
        assert data.n_points

    def test_download_bunny():
        data = examples.download_bunny()
        assert data.n_points

    def test_download_cow():
        data = examples.download_cow()
        assert data.n_points

    def test_download_faults():
        data = examples.download_faults()
        assert data.n_points

    def test_download_tensors():
        data = examples.download_tensors()
        assert data.n_points

    def test_download_head():
        data = examples.download_head()
        assert data.n_points

    def test_download_bolt_nut():
        data = examples.download_bolt_nut()
        assert isinstance(data, pyvista.MultiBlock)

    def test_download_clown():
        data = examples.download_clown()
        assert data.n_points

    def test_download_exodus():
        data = examples.download_exodus()
        assert data.n_blocks

    def test_download_nefertiti():
        data = examples.download_nefertiti()
        assert data.n_cells

    def test_download_blood_vessels():
        """Tests the parallel VTU reader"""
        data = examples.download_blood_vessels()
        assert isinstance(data, pyvista.UnstructuredGrid)

    def test_download_bunny_coarse():
        data = examples.download_bunny_coarse()
        assert data.n_cells

    def test_download_cow_head():
        data = examples.download_cow_head()
        assert data.n_cells

    def test_download_knee_full():
        data = examples.download_knee_full()
        assert data.n_cells

    def test_download_iron_protein():
        data = examples.download_iron_protein()
        assert data.n_cells

    def test_download_tetra_dc_mesh():
        data = examples.download_tetra_dc_mesh()
        assert data.n_blocks

    def test_download_tetrahedron():
        data = examples.download_tetrahedron()
        assert data.n_cells

    def test_download_saddle_surface():
        data = examples.download_saddle_surface()
        assert data.n_cells

    def test_download_foot_bones():
        data = examples.download_foot_bones()
        assert data.n_cells

    def test_download_guitar():
        data = examples.download_guitar()
        assert data.n_cells

    def test_download_quadratic_pyramid():
        data = examples.download_quadratic_pyramid()
        assert data.n_cells

    def test_download_bird():
        data = examples.download_bird()
        assert data.n_cells

    def test_download_bird_texture():
        data = examples.download_bird_texture()
        assert isinstance(data, vtk.vtkTexture)

    def test_download_office():
        data = examples.download_office()
        assert data.n_cells

    def test_download_horse_points():
        data = examples.download_horse_points()
        assert data.n_points

    def test_download_horse():
        data = examples.download_horse()
        assert data.n_cells

    def test_download_cake_easy():
        data = examples.download_cake_easy()
        assert data.n_cells

    def test_download_cake_easy_texture():
        data = examples.download_cake_easy_texture()
        assert isinstance(data, vtk.vtkTexture)

    def test_download_can_crushed_hdf():
        path = examples.download_can_crushed_hdf(load=False)
        assert os.path.isfile(path)
        dataset = examples.download_can_crushed_hdf()
        assert isinstance(dataset, pyvista.UnstructuredGrid)

    def test_download_rectilinear_grid():
        data = examples.download_rectilinear_grid()
        assert data.n_cells

    def test_download_gourds():
        data = examples.download_gourds()
        assert data.n_cells
        data = examples.download_gourds(zoom=True)
        assert data.n_cells

    def test_download_gourds_texture():
        data = examples.download_gourds_texture()
        assert isinstance(data, vtk.vtkTexture)
        data = examples.download_gourds_texture(zoom=True)
        assert isinstance(data, vtk.vtkTexture)

    def test_download_unstructured_grid():
        data = examples.download_unstructured_grid()
        assert data.n_cells

    def test_download_letter_k():
        data = examples.download_letter_k()
        assert data.n_cells

    def test_download_letter_a():
        data = examples.download_letter_a()
        assert data.n_cells

    def test_download_poly_line():
        data = examples.download_poly_line()
        assert data.n_cells

    def test_download_cad_model():
        data = examples.download_cad_model()
        assert data.n_cells

    def test_download_frog():
        data = examples.download_frog()
        assert data.n_cells

    def test_download_chest():
        data = examples.download_chest()
        assert data.n_cells

    def test_download_brain_atlas_with_sides():
        data = examples.download_brain_atlas_with_sides()
        assert data.n_cells

    def test_download_prostate():
        data = examples.download_prostate()
        assert data.n_cells

    def test_download_filled_contours():
        data = examples.download_filled_contours()
        assert data.n_cells

    def test_download_doorman():
        data = examples.download_doorman()
        assert data.n_cells

    def test_download_mug():
        data = examples.download_mug()
        assert data.n_blocks

    def test_download_oblique_cone():
        data = examples.download_oblique_cone()
        assert data.n_cells

    def test_download_emoji():
        data = examples.download_emoji()
        assert data.n_cells

    def test_download_emoji_texture():
        data = examples.download_emoji_texture()
        assert isinstance(data, vtk.vtkTexture)

    def test_download_teapot():
        data = examples.download_teapot()
        assert data.n_cells

    def test_download_brain():
        data = examples.download_brain()
        assert data.n_cells

    def test_download_structured_grid():
        data = examples.download_structured_grid()
        assert data.n_cells

    def test_download_structured_grid_two():
        data = examples.download_structured_grid_two()
        assert data.n_cells

    def test_download_trumpet():
        data = examples.download_trumpet()
        assert data.n_cells

    def test_download_face():
        data = examples.download_face()
        assert data.n_cells

    def test_download_sky_box_nz():
        data = examples.download_sky_box_nz()
        assert data.n_cells

    def test_download_sky_box_nz_texture():
        data = examples.download_sky_box_nz_texture()
        assert isinstance(data, vtk.vtkTexture)

    def test_download_disc_quads():
        data = examples.download_disc_quads()
        assert data.n_cells

    def test_download_honolulu():
        data = examples.download_honolulu()
        assert data.n_cells

    def test_download_motor():
        data = examples.download_motor()
        assert data.n_cells

    def test_download_tri_quadratic_hexahedron():
        data = examples.download_tri_quadratic_hexahedron()
        assert data.n_cells

    def test_download_human():
        data = examples.download_human()
        assert data.n_cells

    def test_download_vtk():
        data = examples.download_vtk()
        assert data.n_cells

    def test_download_spider():
        data = examples.download_spider()
        assert data.n_cells

    def test_download_carotid():
        data = examples.download_carotid()
        assert data.n_cells

    def test_download_carburetor():
        data = examples.download_carburetor()
        assert isinstance(data, pyvista.PolyData)
        assert data.n_faces

    def test_download_blow():
        data = examples.download_blow()
        assert data.n_cells

    def test_download_shark():
        data = examples.download_shark()
        assert data.n_cells

    def test_download_dragon():
        data = examples.download_dragon()
        assert data.n_cells

    def test_download_armadillo():
        data = examples.download_armadillo()
        assert data.n_cells

    def test_download_gears():
        data = examples.download_gears()
        assert data.n_cells

    def test_download_torso():
        data = examples.download_torso()
        assert data.n_cells

    def test_download_kitchen():
        data = examples.download_kitchen()
        assert data.n_cells

    def test_download_kitchen_split():
        data = examples.download_kitchen(split=True)
        assert data.n_blocks

    def test_download_backward_facing_step():
        data = examples.download_backward_facing_step()
        assert data.n_blocks

    # def test_download_topo_global():
    #     data = examples.download_topo_global()
    #     assert data.n_cells
    #
    # def test_download_topo_land():
    #     data = examples.download_topo_land()
    #     assert data.n_cells

    def test_download_coastlines():
        data = examples.download_coastlines()
        assert data.n_cells

    def test_download_knee():
        data = examples.download_knee()
        assert data.n_cells

    def test_download_lidar():
        data = examples.download_lidar()
        assert data.n_cells

    def test_pine_roots():
        data = examples.download_pine_roots()
        assert data.n_points

    def test_download_dicom_stack():
        data = examples.download_dicom_stack()
        assert isinstance(data, pyvista.UniformGrid)
        assert all([data.n_points, data.n_cells])

    def test_vrml_download_teapot():
        filename = examples.vrml.download_teapot()
        assert os.path.isfile(filename)

    def test_vrml_download_sextant():
        filename = examples.vrml.download_sextant()
        assert os.path.isfile(filename)

    def test_download_cavity():
        filename = examples.download_cavity(load=False)
        assert os.path.isfile(filename)

        dataset = examples.download_cavity(load=True)
        assert isinstance(dataset, pyvista.MultiBlock)

    def test_download_lucy():
        filename = examples.download_lucy(load=False)
        assert os.path.isfile(filename)

        dataset = examples.download_lucy(load=True)
        assert isinstance(dataset, pyvista.PolyData)

    def test_angular_sector():
        filename = examples.download_angular_sector(load=False)
        assert os.path.isfile(filename)

        dataset = examples.download_angular_sector(load=True)
        assert isinstance(dataset, pyvista.UnstructuredGrid)

    def test_mount_damavand():
        filename = examples.download_mount_damavand(load=False)
        assert os.path.isfile(filename)

        dataset = examples.download_mount_damavand(load=True)
        assert isinstance(dataset, pyvista.PolyData)

    def test_download_cubemap_space_4k():
        dataset = examples.download_cubemap_space_4k()
        assert isinstance(dataset, pyvista.Texture)

    def test_download_cubemap_space_16k():
        dataset = examples.download_cubemap_space_16k()
        assert isinstance(dataset, pyvista.Texture)

    def test_particles_lethe():
        filename = examples.download_particles_lethe(load=False)
        assert os.path.isfile(filename)

        dataset = examples.download_particles_lethe(load=True)
        assert isinstance(dataset, pyvista.UnstructuredGrid)

    def test_cubemap_park():
        dataset = examples.download_cubemap_park()
        assert isinstance(dataset, pyvista.Texture)

    def test_gif_simple():
        filename = examples.download_gif_simple(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('gif')

        dataset = examples.download_gif_simple(load=True)
        assert isinstance(dataset, pyvista.UniformGrid)
        assert 'frame0' in dataset.point_data

    def test_black_vase():
        filename = examples.download_black_vase(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('vtp')

        dataset = examples.download_black_vase(load=True)
        assert isinstance(dataset, pyvista.PolyData)
        assert dataset.n_points > 1_000_000

    def test_ivan_angel():
        filename = examples.download_ivan_angel(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('vtp')

        dataset = examples.download_ivan_angel(load=True)
        assert isinstance(dataset, pyvista.PolyData)
        assert dataset.n_points > 1_000_000

    def test_bird_bath():
        filename = examples.download_bird_bath(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('vtp')

        dataset = examples.download_bird_bath(load=True)
        assert isinstance(dataset, pyvista.PolyData)
        assert dataset.n_points > 1_000_000

    def test_owl():
        filename = examples.download_owl(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('vtp')

        dataset = examples.download_owl(load=True)
        assert isinstance(dataset, pyvista.PolyData)
        assert dataset.n_points > 1_000_000

    def test_plastic_vase():
        filename = examples.download_plastic_vase(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('vtp')

        dataset = examples.download_plastic_vase(load=True)
        assert isinstance(dataset, pyvista.PolyData)
        assert dataset.n_points > 1_000_000

    def test_sea_vase():
        filename = examples.download_sea_vase(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('vtp')

        dataset = examples.download_sea_vase(load=True)
        assert isinstance(dataset, pyvista.PolyData)
        assert dataset.n_points > 1_000_000

    def test_cad_model_case():
        filename = examples.download_cad_model_case(load=False)
        assert os.path.isfile(filename)
        assert filename.endswith('vtp')

        dataset = examples.download_cad_model_case(load=True)
        assert isinstance(dataset, pyvista.PolyData)
        assert dataset.n_points == 7677

    def test_load_sun():
        mesh = examples.planets.load_sun()
        assert mesh.n_cells
        assert mesh.textures["atmosphere"]

    def test_load_moon():
        mesh = examples.planets.load_moon()
        assert mesh.n_cells
        assert mesh.textures["surface"]

    def test_load_mercury():
        mesh = examples.planets.load_mercury()
        assert mesh.n_cells
        assert mesh.textures["surface"]

    def test_load_venus():
        mesh = examples.planets.load_venus()
        assert mesh.n_cells
        assert mesh.textures["surface"]
        assert mesh.textures["atmosphere"]

    def test_load_mars():
        mesh = examples.planets.load_mars()
        assert mesh.n_cells
        assert mesh.textures["surface"]

    def test_load_jupiter():
        mesh = examples.planets.load_jupiter()
        assert mesh.n_cells
        assert mesh.textures["atmosphere"]

    def test_load_saturn():
        mesh = examples.planets.load_saturn()
        assert mesh.n_cells
        assert mesh.textures["atmosphere"]

    def test_load_saturn_rings():
        mesh = examples.planets.load_saturn_rings()
        assert mesh.n_cells
        assert mesh.textures["atmosphere"]

    def test_load_uranus():
        mesh = examples.planets.load_uranus()
        assert mesh.n_cells
        assert mesh.textures["atmosphere"]

    def test_load_neptune():
        mesh = examples.planets.load_neptune()
        assert mesh.n_cells
        assert mesh.textures["atmosphere"]

    def test_load_pluto():
        mesh = examples.planets.load_pluto()
        assert mesh.n_cells
        assert mesh.textures["surface"]


# tests/filters/test_rectilinear_grid.py

import numpy as np
import pytest

import pyvista as pv


@pytest.fixture
def tiny_rectilinear():
    xrng = np.linspace(0, 3, 4)
    yrng = np.linspace(0, 3, 5)
    zrng = np.linspace(0, 3, 6)
    return pv.RectilinearGrid(xrng, yrng, zrng)


@pytest.mark.parametrize('tetra_per_cell', [5, 6, 12])
def test_to_tetrahedral(tiny_rectilinear, tetra_per_cell):
    tet_grid = tiny_rectilinear.to_tetrahedra(tetra_per_cell=tetra_per_cell)
    assert tet_grid.n_cells == tiny_rectilinear.n_cells * tetra_per_cell


def test_to_tetrahedral_raise(tiny_rectilinear):
    with pytest.raises(ValueError, match='either 5, 6, or 12'):
        tiny_rectilinear.to_tetrahedra(tetra_per_cell=9)


def test_to_tetrahedral_mixed(tiny_rectilinear):
    data = np.empty(tiny_rectilinear.n_cells, dtype=int)
    half = tiny_rectilinear.n_cells // 2
    data[:half] = 5
    data[half:] = 12
    tiny_rectilinear['data'] = data
    tet_grid = tiny_rectilinear.to_tetrahedra(mixed=True)
    assert tet_grid.n_cells == (half * 5 + half * 12)

    tet_grid = tiny_rectilinear.to_tetrahedra(mixed='data')
    assert tet_grid.n_cells == (half * 5 + half * 12)

    tet_grid = tiny_rectilinear.to_tetrahedra(mixed=data)
    assert tet_grid.n_cells == (half * 5 + half * 12)

    with pytest.raises(TypeError, match='mixed'):
        tet_grid = tiny_rectilinear.to_tetrahedra(mixed=123)


def test_to_tetrahedral_edge_case():
    with pytest.raises(RuntimeError, match='is 1'):
        pv.UniformGrid(dims=(1, 2, 2)).to_tetrahedra(tetra_per_cell=12)


# tests/jupyter/test_ipygany.py

import numpy as np
import pytest

import pyvista as pv
from pyvista import examples

has_ipygany = True
try:
    from ipygany.ipygany import Scene

    from pyvista.jupyter.pv_ipygany import check_colormap
except:  # noqa: E722
    has_ipygany = False

skip_no_ipygany = pytest.mark.skipif(not has_ipygany, reason="Requires ipygany package")


@skip_no_ipygany
def test_set_jupyter_backend_ipygany():
    pv.global_theme.jupyter_backend = 'ipygany'
    assert pv.global_theme.jupyter_backend == 'ipygany'
    pv.global_theme.jupyter_backend = None


@skip_no_ipygany
@pytest.mark.parametrize(
    'dataset',
    [
        examples.load_uniform(),  # UniformGrid
        examples.load_rectilinear(),  # RectilinearGrid
        examples.load_airplane(),  # PolyData
        examples.load_hexbeam(),  # UnstructuredGrid
        np.random.random((10, 3)),
    ],
)
def test_ipygany_from_plotter(dataset):
    pl = pv.Plotter(notebook=True)
    pl.add_mesh(dataset)
    viewer = pl.show(jupyter_backend='ipygany', return_viewer=True)
    assert isinstance(viewer, Scene)


@skip_no_ipygany
def test_ipygany_from_show(sphere):
    jupyter_kwargs = {'height': 100, 'width': 100}
    viewer = sphere.plot(
        notebook=True, jupyter_backend='ipygany', return_viewer=True, jupyter_kwargs=jupyter_kwargs
    )
    assert isinstance(viewer, Scene)
    assert viewer.children


@skip_no_ipygany
def test_check_colormap_fail():
    with pytest.raises(ValueError, match='is not supported'):
        check_colormap('notacolormap')


@skip_no_ipygany
@pytest.mark.filterwarnings("ignore")
def test_wireframe(sphere):
    # this is expected to warn and plot nothing as it's unsupported
    with pytest.warns(UserWarning, match='not supported'):
        viewer = sphere.plot(
            style='wireframe', notebook=True, jupyter_backend='ipygany', return_viewer=True
        )
    assert isinstance(viewer, Scene)
    assert not viewer.children


@skip_no_ipygany
def test_ipygany_scalar_bar(sphere):
    sphere['my_values'] = sphere.points[:, 2]
    viewer = sphere.plot(
        notebook=True, jupyter_backend='ipygany', return_viewer=False, show_scalar_bar=True
    )
    assert viewer is None


# tests/jupyter/test_ipyvtk.py

import os

import pytest

import pyvista as pv
from pyvista.plotting import system_supports_plotting

has_ipyvtklink = True
try:
    from ipyvtklink.viewer import ViewInteractiveWidget
except:  # noqa: E722
    has_ipyvtklink = False


skip_no_plotting = pytest.mark.skipif(
    not system_supports_plotting(), reason="Requires system to support plotting"
)

skip_no_ipyvtk = pytest.mark.skipif(not has_ipyvtklink, reason="Requires IPython package")


@skip_no_ipyvtk
def test_set_jupyter_backend_ipyvtklink():
    pv.global_theme.jupyter_backend = 'ipyvtklink'
    assert pv.global_theme.jupyter_backend == 'ipyvtklink'
    pv.global_theme.jupyter_backend = None


@skip_no_ipyvtk
@skip_no_plotting
def test_ipyvtk(sphere):
    pl = pv.Plotter(notebook=True)
    pl.add_mesh(sphere)
    viewer = pl.show(jupyter_backend='ipyvtklink', return_viewer=True)
    assert isinstance(viewer, ViewInteractiveWidget)


@skip_no_ipyvtk
@skip_no_plotting
def test_ipyvtk_warn(sphere):
    os.environ['SPYDER'] = 'exists'
    with pytest.warns(UserWarning, match='incompatible with Spyder'):
        sphere.plot(notebook=True, jupyter_backend='ipyvtklink')
    del os.environ['SPYDER']


# tests/jupyter/test_itk_plotting.py

import numpy as np
import pytest

import pyvista
from pyvista.plotting import system_supports_plotting

NO_PLOTTING = not system_supports_plotting()

HAS_ITK = False
try:
    import itkwidgets

    HAS_ITK = True
except ImportError:
    pass

SPHERE = pyvista.Sphere()
SPHERE['z'] = SPHERE.points[:, 2]

no_itk = pytest.mark.skipif(
    NO_PLOTTING or not HAS_ITK, reason="Requires system to support plotting and have itkwidgets."
)


@no_itk
def test_itk_plotting():
    viewer = pyvista.plot_itk(SPHERE)
    assert isinstance(viewer, itkwidgets.Viewer)


@no_itk
def test_itk_plotting_points():
    with pytest.raises(TypeError):
        viewer = pyvista.plot_itk([1, 2, 3], point_size='foo')

    viewer = pyvista.plot_itk(np.random.random((100, 3)))
    assert isinstance(viewer, itkwidgets.Viewer)


@no_itk
def test_itk_plotting_points_polydata():
    points = pyvista.PolyData(np.random.random((100, 3)))
    pl = pyvista.PlotterITK()
    pl.add_points(points)

    with pytest.raises(TypeError):
        viewer = pl.add_points([1, 2, 3], point_size='foo')

    viewer = pl.show()
    assert isinstance(viewer, itkwidgets.Viewer)


@no_itk
def test_itk_plotting_class():
    pl = pyvista.PlotterITK()
    pl.add_mesh(SPHERE, scalars='z')
    viewer = pl.show()
    assert isinstance(viewer, itkwidgets.Viewer)


@no_itk
def test_itk_plotting_class_no_scalars():
    pl = pyvista.PlotterITK()
    pl.add_mesh(SPHERE, color='w')
    with pytest.raises(ValueError):
        pl.camera_position = 'xy'
    with pytest.raises(ValueError):
        pl.camera_position = [[1, 0, 0], [1, 0, 0]]
    with pytest.raises(ValueError):
        pl.camera_position = [[1, 0, 0], [1, 0, 0], [1]]
    pl.camera_position = [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
    assert isinstance(pl.camera_position, list)
    pl.background_color = 'k'
    assert pl.background_color == (0.0, 0.0, 0.0)
    viewer = pl.show()
    assert isinstance(viewer, itkwidgets.Viewer)


@no_itk
def test_itk_plotting_class_npndarray_scalars():
    pl = pyvista.PlotterITK()
    pl.add_mesh(SPHERE, scalars=SPHERE.points[:, 0], smooth_shading=True)
    viewer = pl.show()
    assert isinstance(viewer, itkwidgets.Viewer)


@no_itk
def test_itk_plotting_class_unstructured(hexbeam):
    hexbeam.clear_data()
    pl = pyvista.PlotterITK()
    pl.add_mesh(hexbeam, smooth_shading=True)
    viewer = pl.show()
    assert isinstance(viewer, itkwidgets.Viewer)


# tests/jupyter/test_panel.py

import os

import pytest

import pyvista as pv

has_panel = True
try:
    from panel.pane.vtk.vtk import VTKRenderWindowSynchronized
except:  # noqa: E722
    has_panel = False

skip_no_panel = pytest.mark.skipif(not has_panel, reason='Requires panel')


@skip_no_panel
def test_set_jupyter_backend_panel():
    pv.set_jupyter_backend('panel')
    assert pv.global_theme.jupyter_backend == 'panel'
    pv.set_jupyter_backend(None)


@skip_no_panel
@pytest.mark.parametrize('return_viewer', [True, False])
def test_panel(sphere, return_viewer):
    viewer = sphere.plot(
        notebook=True,
        jupyter_backend='panel',
        return_viewer=return_viewer,
        window_size=(100, 100),
        show_bounds=True,
    )
    if return_viewer:
        assert isinstance(viewer, VTKRenderWindowSynchronized)
    else:
        return viewer is None


@skip_no_panel
def test_save(sphere, tmpdir):
    filename = str(tmpdir.join('tmp.html'))
    plotter = pv.Plotter(shape=(1, 2))

    plotter.add_text("Airplane 1\n", font_size=30, color='grey')
    plotter.add_mesh(sphere, show_edges=False, color='grey')

    plotter.subplot(0, 1)
    plotter.add_text("Airplane 2\n", font_size=30, color='grey')
    plotter.add_mesh(sphere, show_edges=False, color='grey')

    plotter.export_html(filename, backend='panel')
    assert os.path.isfile(filename)


# tests/jupyter/test_pythreejs.py

"""Test optional pythreejs functionality"""

import numpy as np
import pytest

try:
    import pythreejs  # noqa
except:  # noqa: E722
    pytestmark = pytest.mark.skip

import pyvista
from pyvista.jupyter import pv_pythreejs


def test_set_jupyter_backend_ipygany():
    try:
        pyvista.global_theme.jupyter_backend = 'pythreejs'
        assert pyvista.global_theme.jupyter_backend == 'pythreejs'
    finally:
        pyvista.global_theme.jupyter_backend = None


def test_export_to_html(sphere, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.html'))

    pl = pyvista.Plotter(shape=(1, 2))
    pl.add_text("Sphere 1\n", font_size=30, color='grey')
    pl.add_mesh(sphere, show_edges=False, color='grey')

    pl.subplot(0, 1)
    pl.add_text("Sphere 2\n", font_size=30, color='grey')
    pl.add_mesh(sphere, show_edges=False, color='grey')
    pl.link_views()

    with pytest.raises(ValueError, match="Invalid backend"):
        pl.export_html(filename, backend='not-a-valid-backend')

    pl.export_html(filename)

    raw = open(filename).read()
    assert 'jupyter-threejs' in raw

    # expect phong material
    assert '"model_name": "MeshPhongMaterialModel"' in raw

    # at least a single instance of lighting
    assert 'DirectionalLightModel' in raw


def test_export_to_html_composite(tmpdir):
    filename = str(tmpdir.join('tmp.html'))

    blocks = pyvista.MultiBlock()
    blocks.append(pyvista.Sphere())
    blocks.append(pyvista.Cube(center=(0, 0, -1)))

    pl = pyvista.Plotter()
    actor, mapper = pl.add_composite(blocks, show_edges=False, color='red')

    # override the color of the sphere
    mapper.block_attr[1].color = 'b'
    mapper.block_attr[1].opacity = 0.5

    pl.export_html(filename)

    # ensure modified block attributes have been outputted
    raw = open(filename).read()
    assert f'"opacity": {mapper.block_attr[1].opacity}' in raw
    assert f'"color": "{mapper.block_attr[1].color.hex_rgb}"' in raw


def test_segment_poly_cells(spline):
    cells = pv_pythreejs.segment_poly_cells(spline)

    assert cells.shape[0] == spline.lines[0] - 1
    assert (np.diff(cells, axis=0) == 1).all()
    assert (np.diff(cells, axis=1) == 1).all()


def test_buffer_normals(sphere):
    buf_attr = pv_pythreejs.buffer_normals(sphere)
    assert np.allclose(buf_attr.array, sphere.point_normals)

    sphere_w_normals = sphere.compute_normals()
    buf_attr = pv_pythreejs.buffer_normals(sphere_w_normals)
    assert np.allclose(buf_attr.array, sphere.point_normals)


def test_get_coloring(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=range(sphere.n_points))
    assert pv_pythreejs.get_coloring(pl.mapper, pl.mesh) == 'VertexColors'

    sphere.clear_data()

    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=range(sphere.n_cells))
    assert pv_pythreejs.get_coloring(pl.mapper, pl.mesh) == 'FaceColors'


def test_output_point_cloud(sphere):
    pl = pyvista.Plotter()
    pl.add_points(sphere.points, scalars=range(sphere.n_points))
    pv_pythreejs.convert_plotter(pl)

    # points but no active point scalars
    pl = pyvista.Plotter()
    pl.add_points(sphere, scalars=range(sphere.n_cells))
    pv_pythreejs.convert_plotter(pl)


def test_output_face_scalars(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=range(sphere.n_cells))
    pv_pythreejs.convert_plotter(pl)


@pytest.mark.parametrize(
    'max_index',
    [np.iinfo(np.uint16).max - 1, np.iinfo(np.uint32).max - 1, np.iinfo(np.uint32).max + 1],
)
def test_cast_to_min_size(max_index):

    if max_index < np.iinfo(np.uint16).max:
        buf_attr = pv_pythreejs.cast_to_min_size(np.arange(1000), max_index)
        assert buf_attr.array.dtype == np.uint16
    elif max_index < np.iinfo(np.uint32).max:
        buf_attr = pv_pythreejs.cast_to_min_size(np.arange(1000), max_index)
        assert buf_attr.array.dtype == np.uint32
    else:
        with pytest.raises(ValueError):
            buf_attr = pv_pythreejs.cast_to_min_size(np.arange(1000), max_index)


@pytest.mark.needs_vtk9
def test_pbr(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=range(sphere.n_cells), pbr=True)
    pv_pythreejs.convert_plotter(pl)


def test_output_face_scalars_rgba(sphere):
    colors = np.linspace(0, 255, sphere.n_cells)
    rgba = np.vstack((colors, colors, colors)).T

    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=rgba, rgba=True)
    pv_pythreejs.convert_plotter(pl)


def test_output_point_scalars(sphere):
    scalars = np.linspace(0, 255, sphere.n_points)
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=scalars)
    pv_pythreejs.convert_plotter(pl)


def test_output_point_scalars_rgba(sphere):
    colors = np.linspace(0, 255, sphere.n_points)
    rgba = np.vstack((colors, colors, colors)).T

    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=rgba, rgba=True)
    pv_pythreejs.convert_plotter(pl)


def test_output_texture(globe):
    pl = pyvista.Plotter()
    pl.add_mesh(globe)
    pv_pythreejs.convert_plotter(pl)


def test_no_lighting(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, lighting=False)
    pv_pythreejs.convert_plotter(pl)


def test_show_edges(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, show_edges=True)
    pv_pythreejs.convert_plotter(pl)


def test_not_polydata(hexbeam):
    pl = pyvista.Plotter()
    pl.add_mesh(hexbeam)
    pv_pythreejs.convert_plotter(pl)


@pytest.mark.parametrize('with_scalars', [False, True])
def test_output_wireframe(sphere, with_scalars):
    pl = pyvista.Plotter()
    if with_scalars:
        pl.add_mesh(sphere, scalars=range(sphere.n_points), style='wireframe')
    else:
        pl.add_mesh(sphere, style='wireframe')
    pv_pythreejs.convert_plotter(pl)


@pytest.mark.parametrize('with_scalars', [False, True])
def test_output_lines(spline, with_scalars):
    pl = pyvista.Plotter()
    if with_scalars:
        pl.add_mesh(spline, scalars=range(spline.n_points), style='wireframe')
    else:
        pl.add_mesh(spline, style='wireframe')
    pv_pythreejs.convert_plotter(pl)


def test_add_axes():
    pl = pyvista.Plotter()
    pl.add_axes()
    pv_pythreejs.convert_plotter(pl)


def test_grid_layout():
    pl = pyvista.Plotter(shape=(2, 2))
    pv_pythreejs.convert_plotter(pl)


def test_export_after_show():
    pl = pyvista.Plotter(shape=(2, 2))

    # deleting rather than showing to save time
    del pl.ren_win

    with pytest.raises(AttributeError):
        pv_pythreejs.convert_plotter(pl)


def test_non_standard_shape():
    pl = pyvista.Plotter(shape='2|3')
    with pytest.raises(RuntimeError, match='Unsupported plotter shape'):
        pv_pythreejs.convert_plotter(pl)


def test_labels():
    poly = pyvista.PolyData(np.random.rand(10, 3))
    poly["My Labels"] = [f"Label {i}" for i in range(poly.n_points)]

    pl = pyvista.Plotter()
    pl.add_point_labels(poly, "My Labels", point_size=20, font_size=36)

    # ensure that we ignore labels
    with pytest.warns(UserWarning):
        pv_pythreejs.convert_plotter(pl)


def test_linked_views(sphere):
    n_row, n_col = (2, 3)
    pl = pyvista.Plotter(shape=(n_row, n_col))

    for ii in range(n_row):
        for jj in range(n_col):
            pl.subplot(ii, jj)
            pl.add_mesh(sphere)

    pl.link_views((0, 1, 2))  # link first row together
    pl.link_views((3, 4, 5))  # link second row together

    # validate all cameras are linked
    widget = pv_pythreejs.convert_plotter(pl)

    # check first row is linked
    cameras = [widget[0, col].camera for col in range(n_col)]
    assert all([camera is cameras[0] for camera in cameras])

    # check second row is linked
    cameras = [widget[0, col].camera for col in range(n_col)]
    assert all([camera is cameras[0] for camera in cameras])

    # check first row camera is different than the second row
    cameras = [widget[row, 0].camera for row in range(2)]
    assert widget[0, 0].camera is not widget[1, 0].camera


# tests/jupyter/test_static.py

import pytest

import pyvista as pv
from pyvista.plotting import system_supports_plotting

has_ipython = True
try:
    import IPython  # noqa
    from PIL.Image import Image
except:  # noqa: E722
    has_ipython = False

skip_no_ipython = pytest.mark.skipif(not has_ipython, reason="Requires IPython package")

skip_no_plotting = pytest.mark.skipif(
    not system_supports_plotting(), reason="Requires system to support plotting"
)


def test_set_jupyter_backend_fail():
    with pytest.raises(ValueError, match='Invalid Jupyter notebook plotting backend'):
        pv.set_jupyter_backend('not a backend')


@pytest.mark.parametrize('backend', [None, 'none'])
def test_set_jupyter_backend_none(backend):
    pv.set_jupyter_backend(backend)
    assert pv.global_theme.jupyter_backend is None


@skip_no_ipython
def test_set_jupyter_backend_static():
    pv.set_jupyter_backend('static')
    assert pv.global_theme.jupyter_backend == 'static'
    pv.set_jupyter_backend(None)


@skip_no_ipython
@skip_no_plotting
@pytest.mark.parametrize('return_viewer', [True, False])
def test_static_from_show(sphere, return_viewer):
    window_size = (100, 100)
    image = sphere.plot(
        window_size=window_size,
        notebook=True,
        jupyter_backend='static',
        return_viewer=return_viewer,
    )
    if return_viewer:
        assert isinstance(image, Image)
        assert window_size == image.size


# tests/plotting/conftest.py

"""This conftest is here to allow for checking garbage collection and
memory leaks for all plotting tests
"""
import gc

import pytest

import pyvista

# these are set here because we only need them for plotting tests
pyvista.global_theme.load_theme(pyvista.themes._TestingTheme())
pyvista.OFF_SCREEN = True


def _is_vtk(obj):
    try:
        return obj.__class__.__name__.startswith('vtk')
    except Exception:  # old Python sometimes no __class__.__name__
        return False


@pytest.fixture(autouse=True)
def check_gc(request):
    """Ensure that all VTK objects are garbage-collected by Python."""
    before = set(id(o) for o in gc.get_objects() if _is_vtk(o))
    yield

    # Do not check for collection if the test session failed. Tests that fail
    # also fail to cleanup their resources and this makes reading the unit test
    # output more difficult.
    #
    # This applies to the entire session, so it's going to be the most useful
    # when debugging tests with `pytest -x`
    if request.session.testsfailed:
        return

    pyvista.close_all()

    gc.collect()
    after = [o for o in gc.get_objects() if _is_vtk(o) and id(o) not in before]
    assert len(after) == 0, 'Not all objects GCed:\n' + '\n'.join(
        sorted(o.__class__.__name__ for o in after)
    )


# tests/plotting/test_collection.py

"""This module contains any tests which cause memory leaks."""
import gc
import weakref

import numpy as np

import pyvista as pv


def test_pyvistandarray_assign(sphere):
    sphere.point_data['data'] = range(sphere.n_points)

    # this might leave a reference behind if we don't properly use the pointer
    # to the vtk array.
    sphere.point_data['data'] = sphere.point_data['data']


def test_pyvistandarray_strides(sphere):
    sphere['test_scalars'] = sphere.points[:, 2]
    assert np.allclose(sphere['test_scalars'], sphere.points[:, 2])


def test_complex_collection(plane):
    name = 'my_data'
    data = np.random.random((plane.n_points, 2)).view(np.complex128).ravel()
    plane.point_data[name] = data

    # ensure shallow copy
    assert np.shares_memory(plane.point_data[name], data)

    # ensure data remains but original numpy object does not
    ref = weakref.ref(data)
    data_copy = data.copy()
    del data
    assert np.allclose(plane.point_data[name], data_copy)

    assert ref() is None


def test_add_array(sphere):
    """Ensure data added dynamically to a plotter is collected."""
    pl = pv.Plotter()
    pl.add_mesh(sphere, scalars=range(sphere.n_points))


def test_plotting_collection():
    """Ensure that we don't leak Plotter, Renderer and Charts instances."""
    pl = pv.Plotter()
    ref_plotter = weakref.ref(pl)
    ref_renderers = weakref.ref(pl.renderers)
    ref_renderer = weakref.ref(pl.renderer)
    ref_charts = weakref.ref(pl.renderer._charts)  # instantiated on the fly

    # delete known references to Plotter
    del pv.plotting._ALL_PLOTTERS[pl._id_name]
    del pl

    # check that everything is eventually destroyed
    gc.collect()  # small reference cycles allowed
    assert ref_plotter() is None, gc.get_referrers(ref_plotter())
    assert ref_renderers() is None
    assert ref_renderer() is None
    assert ref_charts() is None


# tests/plotting/test_plotting.py

"""
This test module tests any functionality that requires plotting.

See the image regression notes in doc/extras/developer_notes.rst

"""
import inspect
import io
import os
import pathlib
import platform
import re
import time
import warnings

from PIL import Image
import imageio
import numpy as np
import pytest
import vtk

import pyvista
from pyvista import examples
from pyvista._vtk import VTK9
from pyvista.core.errors import DeprecationError
from pyvista.plotting import system_supports_plotting
from pyvista.plotting.plotting import SUPPORTED_FORMATS
from pyvista.utilities.misc import can_create_mpl_figure

# skip all tests if unable to render
if not system_supports_plotting():
    pytestmark = pytest.mark.skip(reason='Requires system to support plotting')


ffmpeg_failed = False
try:
    try:
        import imageio_ffmpeg

        imageio_ffmpeg.get_ffmpeg_exe()
    except ImportError:
        imageio.plugins.ffmpeg.download()
except:  # noqa: E722
    ffmpeg_failed = True


# Reset image cache with new images
glb_reset_image_cache = False
THIS_PATH = pathlib.Path(__file__).parent.absolute()
IMAGE_CACHE_DIR = os.path.join(THIS_PATH, 'image_cache')
if not os.path.isdir(IMAGE_CACHE_DIR):
    os.mkdir(IMAGE_CACHE_DIR)


def using_mesa():
    """Determine if using mesa."""
    pl = pyvista.Plotter(notebook=False, off_screen=True)
    pl.show(auto_close=False)
    gpu_info = pl.ren_win.ReportCapabilities()
    pl.close()

    regex = re.compile("OpenGL version string:(.+)\n")
    return "Mesa" in regex.findall(gpu_info)[0]


# always set on Windows CI
# These tests fail with mesa opengl on windows
skip_windows = pytest.mark.skipif(os.name == 'nt', reason='Test fails on Windows')
skip_windows_mesa = pytest.mark.skipif(
    using_mesa() and os.name == 'nt', reason='Does not display correctly within OSMesa on Windows'
)
skip_9_1_0 = pytest.mark.needs_vtk_version(9, 1, 0)
skip_9_0_X = pytest.mark.skipif((8, 2) < pyvista.vtk_version_info < (9, 1), reason="Flaky on 9.0.X")
skip_no_mpl_figure = pytest.mark.skipif(
    not can_create_mpl_figure(), reason="Cannot create a figure using matplotlib"
)

CI_WINDOWS = os.environ.get('CI_WINDOWS', 'false').lower() == 'true'

skip_mac = pytest.mark.skipif(
    platform.system() == 'Darwin', reason='MacOS CI fails when downloading examples'
)
skip_mac_flaky = pytest.mark.skipif(
    platform.system() == 'Darwin', reason='This is a flaky test on MacOS'
)
skip_mesa = pytest.mark.skipif(using_mesa(), reason='Does not display correctly within OSMesa')

# Normal image warning/error thresholds (assumes using use_vtk)
IMAGE_REGRESSION_ERROR = 500  # major differences
IMAGE_REGRESSION_WARNING = 200  # minor differences

# Image regression warning/error thresholds for releases after 9.0.1
# TODO: once we have a stable release for VTK, remove these.
HIGH_VARIANCE_TESTS = {
    'test_add_title',
    'test_export_gltf',  # image cache created with 9.0.20210612.dev0
    'test_import_gltf',  # image cache created with 9.0.20210612.dev0
    'test_opacity_by_array_direct',  # VTK regression 9.0.1 --> 9.1.0
    'test_opacity_by_array_user_transform',
    'test_pbr',
    'test_set_environment_texture_cubemap',
    'test_set_viewup',
}
VER_IMAGE_REGRESSION_ERROR = 1000
VER_IMAGE_REGRESSION_WARNING = 1000

# these images vary between Windows when using OSMesa and Linux/MacOS
# and will not be verified
WINDOWS_SKIP_IMAGE_CACHE = {
    'test_array_volume_rendering',
    'test_cmap_list',
    'test_collision_plot',
    'test_enable_stereo_render',
    'test_multi_plot_scalars',  # flaky
    'test_plot_add_scalar_bar',
    'test_plot_cell_data',
    'test_plot_complex_value',
    'test_plot_composite_poly_component_nested_multiblock',
    'test_plot_composite_poly_scalars_cell',
    'test_plot_composite_preference_cell',
    'test_plot_helper_two_volumes',
    'test_plot_helper_volume',
    'test_plot_string_array',
    'test_rectlinear_edge_case',
    'test_scalars_by_name',
    'test_user_annotations_scalar_bar_volume',
    'test_volume_rendering_from_helper',
}

# these images vary between Linux/Windows and MacOS
# and will not be verified for MacOS
MACOS_SKIP_IMAGE_CACHE = {
    'test_plot_show_grid_with_mesh',
    'test_property',
}


@pytest.fixture()
def multicomp_poly():
    """Create a dataset with vector values on points and cells."""
    data = pyvista.Plane()

    vector_values_points = np.empty((data.n_points, 3))
    vector_values_points[:, 0] = np.arange(data.n_points)
    vector_values_points[:, 1] = np.arange(data.n_points)[::-1]
    vector_values_points[:, 2] = 0

    vector_values_cells = np.empty((data.n_cells, 3))
    vector_values_cells[:, 0] = np.arange(data.n_cells)
    vector_values_cells[:, 1] = np.arange(data.n_cells)[::-1]
    vector_values_cells[:, 2] = 0

    data['vector_values_points'] = vector_values_points
    data['vector_values_cells'] = vector_values_cells
    return data


# this must be a session fixture to ensure this runs before any other test
@pytest.fixture(scope="session", autouse=True)
def get_cmd_opt(pytestconfig):
    global glb_reset_image_cache, glb_ignore_image_cache, glb_fail_extra_image_cache
    glb_reset_image_cache = pytestconfig.getoption('reset_image_cache')
    glb_ignore_image_cache = pytestconfig.getoption('ignore_image_cache')
    glb_fail_extra_image_cache = pytestconfig.getoption('fail_extra_image_cache')


def verify_cache_image(plotter):
    """Either store or validate an image.

    This is function should only be called within a pytest
    environment.  Pass it to either the ``Plotter.show()`` or the
    ``pyvista.plot()`` functions as the before_close_callback keyword
    arg.

    Assign this only once for each test you'd like to validate the
    previous image of.  This will not work with parameterized tests.

    Example Usage:
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.show(before_close_callback=verify_cache_image)

    """
    global glb_reset_image_cache, glb_ignore_image_cache, glb_fail_extra_image_cache

    # Image cache is only valid for VTK9+
    if not VTK9:
        return

    # since each test must contain a unique name, we can simply
    # use the function test to name the image
    stack = inspect.stack()
    for item in stack:
        if item.function == 'check_gc':
            return
        if item.function[:5] == 'test_':
            test_name = item.function
            break
    else:
        raise RuntimeError(
            'Unable to identify calling test function.  This function '
            'should only be used within a pytest environment.'
        )

    if test_name in HIGH_VARIANCE_TESTS:
        allowed_error = VER_IMAGE_REGRESSION_ERROR
        allowed_warning = VER_IMAGE_REGRESSION_WARNING
    else:
        allowed_error = IMAGE_REGRESSION_ERROR
        allowed_warning = IMAGE_REGRESSION_WARNING

    # some tests fail when on Windows with OSMesa
    if os.name == 'nt' and test_name in WINDOWS_SKIP_IMAGE_CACHE:
        return
    # high variation for MacOS
    if platform.system() == 'Darwin' and test_name in MACOS_SKIP_IMAGE_CACHE:
        return

    # cached image name
    image_filename = os.path.join(IMAGE_CACHE_DIR, test_name[5:] + '.png')

    if glb_ignore_image_cache:
        return

    if not os.path.isfile(image_filename) and glb_fail_extra_image_cache:
        raise RuntimeError(f"{image_filename} does not exist in image cache")
    # simply save the last screenshot if it doesn't exist or the cache
    # is being reset.
    if glb_reset_image_cache or not os.path.isfile(image_filename):
        return plotter.screenshot(image_filename)

    # otherwise, compare with the existing cached image
    error = pyvista.compare_images(image_filename, plotter)
    if error > allowed_error:
        raise RuntimeError(
            'Exceeded image regression error of '
            f'{IMAGE_REGRESSION_ERROR} with an image error of '
            f'{error}'
        )
    if error > allowed_warning:
        warnings.warn(
            'Exceeded image regression warning of '
            f'{IMAGE_REGRESSION_WARNING} with an image error of '
            f'{error}'
        )


@pytest.mark.needs_vtk9
def test_import_gltf():
    filename = os.path.join(THIS_PATH, '..', 'example_files', 'Box.glb')
    pl = pyvista.Plotter()

    with pytest.raises(FileNotFoundError):
        pl.import_gltf('not a file')

    pl.import_gltf(filename)
    pl.show(before_close_callback=verify_cache_image)


@pytest.mark.needs_vtk9
def test_export_gltf(tmpdir, sphere, airplane, hexbeam):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.gltf'))

    pl = pyvista.Plotter()
    pl.add_mesh(sphere, smooth_shading=True)
    pl.add_mesh(airplane)
    pl.add_mesh(hexbeam)  # to check warning
    with pytest.warns(UserWarning, match='Plotter contains non-PolyData datasets'):
        pl.export_gltf(filename)

    pl_import = pyvista.Plotter()
    pl_import.import_gltf(filename)
    pl_import.show(before_close_callback=verify_cache_image)

    with pytest.raises(RuntimeError, match='This plotter has been closed'):
        pl_import.export_gltf(filename)


def test_import_vrml():
    filename = os.path.join(THIS_PATH, '..', 'example_files', 'Box.wrl')
    pl = pyvista.Plotter()

    with pytest.raises(FileNotFoundError):
        pl.import_vrml('not a file')

    pl.import_vrml(filename)
    pl.show(before_close_callback=verify_cache_image)


def test_export_vrml(tmpdir, sphere, airplane, hexbeam):
    filename = str(tmpdir.mkdir("tmpdir").join("tmp.wrl"))

    pl = pyvista.Plotter()
    pl.add_mesh(sphere, smooth_shading=True)
    pl.export_vrml(filename)

    pl_import = pyvista.Plotter()
    pl_import.import_vrml(filename)
    pl_import.show(before_close_callback=verify_cache_image)

    with pytest.raises(RuntimeError, match="This plotter has been closed"):
        pl_import.export_vrml(filename)


@pytest.mark.needs_vtk9
@skip_windows
@pytest.mark.skipif(CI_WINDOWS, reason="Windows CI testing segfaults on pbr")
def test_pbr(sphere):
    """Test PBR rendering"""
    texture = examples.load_globe_texture()

    pl = pyvista.Plotter(lighting=None)
    pl.set_environment_texture(texture)
    pl.add_light(pyvista.Light())
    pl.add_mesh(
        sphere, color='w', pbr=True, metallic=0.8, roughness=0.2, smooth_shading=True, diffuse=1
    )
    pl.add_mesh(
        pyvista.Sphere(center=(0, 0, 1)),
        color='w',
        pbr=True,
        metallic=0.0,
        roughness=1.0,
        smooth_shading=True,
        diffuse=1,
    )
    pl.show(before_close_callback=verify_cache_image)


@pytest.mark.needs_vtk9
@skip_windows
@skip_mac
def test_set_environment_texture_cubemap(sphere):
    """Test set_environment_texture with a cubemap."""
    texture = examples.download_sky_box_cube_map()

    pl = pyvista.Plotter(lighting=None)
    pl.set_environment_texture(texture)
    pl.add_mesh(sphere, color='w', pbr=True, metallic=0.8, roughness=0.2)

    # VTK flipped the Z axis for the cubemap between 9.1 and 9.2
    if pyvista.vtk_version_info <= (9, 1):
        pl.show(before_close_callback=verify_cache_image)
    else:
        pl.show()


@pytest.mark.needs_vtk9
@skip_windows
@skip_mac
def test_remove_environment_texture_cubemap(sphere):
    """Test remove_environment_texture with a cubemap."""
    texture = examples.download_sky_box_cube_map()

    pl = pyvista.Plotter()
    pl.set_environment_texture(texture)
    pl.add_mesh(sphere, color='w', pbr=True, metallic=0.8, roughness=0.2)
    pl.remove_environment_texture()
    pl.show(before_close_callback=verify_cache_image)


def test_plot_pyvista_ndarray(sphere):
    # verify we can plot pyvista_ndarray
    pyvista.plot(sphere.points)

    plotter = pyvista.Plotter()
    plotter.add_points(sphere.points)
    plotter.add_points(sphere.points + 1)
    plotter.show()


def test_plot_increment_point_size():
    points = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0], [1, 1, 0]], dtype=np.float32)
    pl = pyvista.Plotter()
    pl.add_points(points + 1)
    pl.add_lines(points)
    pl.increment_point_size_and_line_width(5)
    pl.show(before_close_callback=verify_cache_image)


@pytest.mark.needs_vtk9
def test_plot_update(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.show(auto_close=False)
    pl.update()
    time.sleep(0.1)
    pl.update()
    pl.update(force_redraw=True)
    pl.close()


def test_plot(sphere, tmpdir):
    tmp_dir = tmpdir.mkdir("tmpdir2")
    filename = str(tmp_dir.join('tmp.png'))
    scalars = np.arange(sphere.n_points)
    cpos, img = pyvista.plot(
        sphere,
        full_screen=True,
        text='this is a sphere',
        show_bounds=True,
        color='r',
        style='wireframe',
        line_width=2,
        scalars=scalars,
        flip_scalars=True,
        cmap='bwr',
        interpolate_before_map=True,
        screenshot=filename,
        return_img=True,
        return_cpos=True,
    )
    assert isinstance(cpos, pyvista.CameraPosition)
    assert isinstance(img, np.ndarray)
    assert os.path.isfile(filename)

    filename = pathlib.Path(str(tmp_dir.join('tmp2.png')))
    pyvista.plot(sphere, screenshot=filename)

    # Ensure it added a PNG extension by default
    assert filename.with_suffix(".png").is_file()

    # test invalid extension
    with pytest.raises(ValueError):
        filename = pathlib.Path(str(tmp_dir.join('tmp3.foo')))
        pyvista.plot(sphere, screenshot=filename)


def test_plot_helper_volume(uniform):
    uniform.plot(
        volume=True,
        parallel_projection=True,
        show_scalar_bar=False,
        show_grid=True,
        before_close_callback=verify_cache_image,
    )


def test_plot_helper_two_datasets(sphere, airplane):
    pyvista.plot([sphere, airplane], before_close_callback=verify_cache_image)


def test_plot_helper_two_volumes(uniform):
    grid = uniform.copy()
    grid.origin = (0, 0, 10)
    pyvista.plot(
        [uniform, grid],
        volume=True,
        show_scalar_bar=False,
        before_close_callback=verify_cache_image,
    )


def test_plot_return_cpos(sphere):
    cpos = sphere.plot(return_cpos=True)
    assert isinstance(cpos, pyvista.CameraPosition)
    assert sphere.plot(return_cpos=False) is None


def test_add_title():
    plotter = pyvista.Plotter()
    plotter.add_title('Plot Title')
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_invalid_style(sphere):
    with pytest.raises(ValueError):
        pyvista.plot(sphere, style='not a style')


@pytest.mark.parametrize(
    'interaction, kwargs',
    [
        ('trackball', {}),
        ('trackball_actor', {}),
        ('image', {}),
        ('joystick', {}),
        ('joystick_actor', {}),
        ('zoom', {}),
        ('terrain', {}),
        ('terrain', {'mouse_wheel_zooms': True, 'shift_pans': True}),
        ('rubber_band', {}),
        ('rubber_band_2d', {}),
    ],
)
def test_interactor_style(sphere, interaction, kwargs):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    getattr(plotter, f'enable_{interaction}_style')(**kwargs)
    assert plotter.iren._style_class is not None
    plotter.close()


def test_lighting_disable_3_lights():
    with pytest.raises(DeprecationError):
        pyvista.Plotter().disable_3_lights()


def test_lighting_enable_three_lights(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)

    plotter.enable_3_lights()
    lights = plotter.renderer.lights
    assert len(lights) == 3
    for light in lights:
        assert light.on

    assert lights[0].intensity == 1.0
    assert lights[1].intensity == 0.6
    assert lights[2].intensity == 0.5

    plotter.show(before_close_callback=verify_cache_image)


def test_lighting_add_manual_light(sphere):
    plotter = pyvista.Plotter(lighting=None)
    plotter.add_mesh(sphere)

    # test manual light addition
    light = pyvista.Light()
    plotter.add_light(light)
    assert plotter.renderer.lights == [light]

    # failing case
    with pytest.raises(TypeError):
        plotter.add_light('invalid')

    plotter.show(before_close_callback=verify_cache_image)


def test_lighting_remove_manual_light(sphere):
    plotter = pyvista.Plotter(lighting=None)
    plotter.add_mesh(sphere)
    plotter.add_light(pyvista.Light())

    # test light removal
    plotter.remove_all_lights()
    assert not plotter.renderer.lights

    plotter.show(before_close_callback=verify_cache_image)


def test_lighting_subplots(sphere):
    plotter = pyvista.Plotter(shape='1|1')
    plotter.add_mesh(sphere)
    renderers = plotter.renderers

    light = pyvista.Light()
    plotter.remove_all_lights()
    for renderer in renderers:
        assert not renderer.lights

    plotter.subplot(0)
    plotter.add_light(light, only_active=True)
    assert renderers[0].lights and not renderers[1].lights
    plotter.add_light(light, only_active=False)
    assert renderers[0].lights and renderers[1].lights
    plotter.subplot(1)
    plotter.add_mesh(pyvista.Sphere())
    plotter.remove_all_lights(only_active=True)
    assert renderers[0].lights and not renderers[1].lights

    plotter.show(before_close_callback=verify_cache_image)


def test_lighting_init_light_kit(sphere):
    plotter = pyvista.Plotter(lighting='light kit')
    plotter.add_mesh(sphere)
    lights = plotter.renderer.lights
    assert len(lights) == 5
    assert lights[0].light_type == pyvista.Light.HEADLIGHT
    for light in lights[1:]:
        assert light.light_type == light.CAMERA_LIGHT
    plotter.show(before_close_callback=verify_cache_image)


def test_lighting_init_three_lights(sphere):
    plotter = pyvista.Plotter(lighting='three lights')
    plotter.add_mesh(sphere)
    lights = plotter.renderer.lights
    assert len(lights) == 3
    for light in lights:
        assert light.light_type == light.CAMERA_LIGHT
    plotter.show(before_close_callback=verify_cache_image)


def test_lighting_init_none(sphere):
    # ``None`` already tested above
    plotter = pyvista.Plotter(lighting='none')
    plotter.add_mesh(sphere)
    lights = plotter.renderer.lights
    assert not lights
    plotter.show(before_close_callback=verify_cache_image)


def test_lighting_init_invalid():
    with pytest.raises(ValueError):
        pyvista.Plotter(lighting='invalid')


def test_plotter_shape_invalid():
    # wrong size
    with pytest.raises(ValueError):
        pyvista.Plotter(shape=(1,))
    # not positive
    with pytest.raises(ValueError):
        pyvista.Plotter(shape=(1, 0))
    with pytest.raises(ValueError):
        pyvista.Plotter(shape=(0, 2))
    # not a sequence
    with pytest.raises(TypeError):
        pyvista.Plotter(shape={1, 2})


def test_plot_bounds_axes_with_no_data():
    plotter = pyvista.Plotter()
    plotter.show_bounds()
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_show_grid(sphere):
    plotter = pyvista.Plotter()

    with pytest.raises(ValueError, match='Value of location'):
        plotter.show_grid(location='foo')
    with pytest.raises(TypeError, match='location must be a string'):
        plotter.show_grid(location=10)
    with pytest.raises(ValueError, match='Value of ticks'):
        plotter.show_grid(ticks='foo')
    with pytest.raises(TypeError, match='ticks must be a string'):
        plotter.show_grid(ticks=10)

    plotter.show_grid()
    plotter.add_mesh(sphere)
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_show_grid_with_mesh(hexbeam, plane):
    """Show the grid bounds for a specific mesh."""
    hexbeam.clear_data()
    plotter = pyvista.Plotter()
    plotter.add_mesh(hexbeam, style='wireframe')
    plotter.add_mesh(plane)
    plotter.show_grid(mesh=plane, show_zlabels=False, show_zaxis=False)
    plotter.show(before_close_callback=verify_cache_image)


cpos_param = [
    [(2.0, 5.0, 13.0), (0.0, 0.0, 0.0), (-0.7, -0.5, 0.3)],
    [-1, 2, -5],  # trigger view vector
    [1.0, 2.0, 3.0],
]
cpos_param.extend(pyvista.plotting.Renderer.CAMERA_STR_ATTR_MAP)


@pytest.mark.parametrize('cpos', cpos_param)
def test_set_camera_position(cpos, sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.camera_position = cpos
    plotter.show()


@pytest.mark.parametrize(
    'cpos', [[(2.0, 5.0), (0.0, 0.0, 0.0), (-0.7, -0.5, 0.3)], [-1, 2], [(1, 2, 3)], 'notvalid']
)
def test_set_camera_position_invalid(cpos, sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    with pytest.raises(pyvista.core.errors.InvalidCameraError):
        plotter.camera_position = cpos


def test_parallel_projection():
    plotter = pyvista.Plotter()
    assert isinstance(plotter.parallel_projection, bool)


@pytest.mark.parametrize("state", [True, False])
def test_set_parallel_projection(state):
    plotter = pyvista.Plotter()
    plotter.parallel_projection = state
    assert plotter.parallel_projection == state


def test_parallel_scale():
    plotter = pyvista.Plotter()
    assert isinstance(plotter.parallel_scale, float)


@pytest.mark.parametrize("value", [1, 1.5, 0.3, 10])
def test_set_parallel_scale(value):
    plotter = pyvista.Plotter()
    plotter.parallel_scale = value
    assert plotter.parallel_scale == value


def test_set_parallel_scale_invalid():
    plotter = pyvista.Plotter()
    with pytest.raises(TypeError):
        plotter.parallel_scale = "invalid"


def test_plot_no_active_scalars(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    with pytest.raises(ValueError):
        plotter.update_scalars(np.arange(5))
    with pytest.raises(ValueError):
        plotter.update_scalars(np.arange(sphere.n_faces))


def test_plot_show_bounds(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.show_bounds(
        show_xaxis=False,
        show_yaxis=False,
        show_zaxis=False,
        show_xlabels=False,
        show_ylabels=False,
        show_zlabels=False,
        use_2d=True,
    )
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_label_fmt(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.show_bounds(xlabel='My X', fmt=r'%.3f')
    plotter.show(before_close_callback=verify_cache_image)


@pytest.mark.parametrize('grid', [True, 'both', 'front', 'back'])
@pytest.mark.parametrize('location', ['all', 'origin', 'outer', 'front', 'back'])
def test_plot_show_bounds_params(grid, location):
    plotter = pyvista.Plotter()
    plotter.add_mesh(pyvista.Cube())
    plotter.show_bounds(grid=grid, ticks='inside', location=location)
    plotter.show_bounds(grid=grid, ticks='outside', location=location)
    plotter.show_bounds(grid=grid, ticks='both', location=location)
    plotter.show()


def test_plot_silhouette_fail(hexbeam):
    plotter = pyvista.Plotter()
    with pytest.raises(TypeError, match="Expected type is `PolyData`"):
        plotter.add_mesh(hexbeam, silhouette=True)


def test_plot_no_silhouette(tri_cylinder):
    # silhouette=False
    plotter = pyvista.Plotter()
    plotter.add_mesh(tri_cylinder)
    assert len(list(plotter.renderer.GetActors())) == 1  # only cylinder
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_silhouette(tri_cylinder):
    # silhouette=True and default properties
    plotter = pyvista.Plotter()
    plotter.add_mesh(tri_cylinder, silhouette=True)
    actors = list(plotter.renderer.GetActors())
    assert len(actors) == 2  # cylinder + silhouette
    actor = actors[0]  # get silhouette actor
    props = actor.GetProperty()
    assert props.GetColor() == pyvista.global_theme.silhouette.color
    assert props.GetOpacity() == pyvista.global_theme.silhouette.opacity
    assert props.GetLineWidth() == pyvista.global_theme.silhouette.line_width
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_silhouette_method(tri_cylinder):
    plotter = pyvista.Plotter()

    plotter.add_mesh(tri_cylinder)
    actors = list(plotter.renderer.GetActors())
    assert len(actors) == 1  # cylinder

    plotter.add_silhouette(tri_cylinder)
    actors = list(plotter.renderer.GetActors())
    assert len(actors) == 2  # cylinder + silhouette

    actor = actors[1]  # get silhouette actor
    props = actor.GetProperty()
    assert props.GetColor() == pyvista.global_theme.silhouette.color
    assert props.GetOpacity() == pyvista.global_theme.silhouette.opacity
    assert props.GetLineWidth() == pyvista.global_theme.silhouette.line_width
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_silhouette_options(tri_cylinder):
    # cover other properties
    plotter = pyvista.Plotter()
    plotter.add_mesh(tri_cylinder, silhouette=dict(decimate=None, feature_angle=20))
    plotter.show(before_close_callback=verify_cache_image)


def test_plotter_scale(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.set_scale(10, 10, 10)
    assert plotter.scale == [10, 10, 10]
    plotter.set_scale(5.0)
    plotter.set_scale(yscale=6.0)
    plotter.set_scale(zscale=9.0)
    assert plotter.scale == [5.0, 6.0, 9.0]
    plotter.scale = [1.0, 4.0, 2.0]
    assert plotter.scale == [1.0, 4.0, 2.0]
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_add_scalar_bar(sphere):
    sphere['test_scalars'] = sphere.points[:, 2]
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.add_scalar_bar(
        label_font_size=10, title_font_size=20, title='woa', interactive=True, vertical=True
    )
    plotter.add_scalar_bar(background_color='white', n_colors=256)
    assert isinstance(plotter.scalar_bar, vtk.vtkScalarBarActor)
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_invalid_add_scalar_bar():
    with pytest.raises(AttributeError):
        plotter = pyvista.Plotter()
        plotter.add_scalar_bar()


def test_plot_list():
    sphere_a = pyvista.Sphere(0.5)
    sphere_b = pyvista.Sphere(1.0)
    sphere_c = pyvista.Sphere(2.0)
    pyvista.plot(
        [sphere_a, sphere_b, sphere_c], style='wireframe', before_close_callback=verify_cache_image
    )


def test_add_lines_invalid():
    plotter = pyvista.Plotter()
    with pytest.raises(TypeError):
        plotter.add_lines(range(10))


def test_open_gif_invalid():
    plotter = pyvista.Plotter()
    with pytest.raises(ValueError):
        plotter.open_gif('file.abs')


@pytest.mark.skipif(ffmpeg_failed, reason="Requires imageio-ffmpeg")
def test_make_movie(sphere):
    # Make temporary file
    filename = os.path.join(pyvista.USER_DATA_PATH, 'tmp.mp4')

    movie_sphere = sphere.copy()
    plotter = pyvista.Plotter()
    plotter.open_movie(filename)
    actor = plotter.add_axes_at_origin()
    plotter.remove_actor(actor, reset_camera=False, render=True)
    plotter.add_mesh(movie_sphere, scalars=np.random.random(movie_sphere.n_faces))
    plotter.show(auto_close=False, window_size=[304, 304])
    plotter.set_focus([0, 0, 0])
    for _ in range(3):  # limiting number of frames to write for speed
        plotter.write_frame()
        random_points = np.random.random(movie_sphere.points.shape)
        movie_sphere.points[:] = random_points * 0.01 + movie_sphere.points * 0.99
        movie_sphere.points[:] -= movie_sphere.points.mean(0)
        scalars = np.random.random(movie_sphere.n_faces)
        plotter.update_scalars(scalars)

    # remove file
    plotter.close()
    os.remove(filename)  # verifies that the plotter has closed


def test_add_legend(sphere):
    plotter = pyvista.Plotter()
    with pytest.raises(TypeError):
        plotter.add_mesh(sphere, label=2)
    plotter.add_mesh(sphere)
    with pytest.raises(ValueError):
        plotter.add_legend()
    legend_labels = [['sphere', 'r']]
    plotter.add_legend(labels=legend_labels, border=True, bcolor=None, size=[0.1, 0.1])
    plotter.show(before_close_callback=verify_cache_image)


def test_legend_circle_face(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    legend_labels = [['sphere', 'r']]
    face = "circle"
    _ = plotter.add_legend(
        labels=legend_labels, border=True, bcolor=None, size=[0.1, 0.1], face=face
    )
    plotter.show(before_close_callback=verify_cache_image)


def test_legend_rectangle_face(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    legend_labels = [['sphere', 'r']]
    face = "rectangle"
    _ = plotter.add_legend(
        labels=legend_labels, border=True, bcolor=None, size=[0.1, 0.1], face=face
    )
    plotter.show(before_close_callback=verify_cache_image)


def test_legend_invalid_face(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    legend_labels = [['sphere', 'r']]
    face = "invalid_face"
    with pytest.raises(ValueError):
        plotter.add_legend(
            labels=legend_labels, border=True, bcolor=None, size=[0.1, 0.1], face=face
        )


def test_legend_subplots(sphere, cube):
    plotter = pyvista.Plotter(shape=(1, 2))
    plotter.add_mesh(sphere, 'blue', smooth_shading=True, label='Sphere')
    assert plotter.legend is None
    plotter.add_legend(bcolor='w')
    assert isinstance(plotter.legend, vtk.vtkActor2D)

    plotter.subplot(0, 1)
    plotter.add_mesh(cube, 'r', label='Cube')
    assert plotter.legend is None
    plotter.add_legend(bcolor='w')
    assert isinstance(plotter.legend, vtk.vtkActor2D)

    plotter.show(before_close_callback=verify_cache_image)


def test_add_axes_twice():
    plotter = pyvista.Plotter()
    plotter.add_axes()
    plotter.add_axes(interactive=True)
    plotter.show(before_close_callback=verify_cache_image)


def test_hide_axes():
    plotter = pyvista.Plotter()
    plotter.add_axes()
    plotter.hide_axes()
    plotter.show(before_close_callback=verify_cache_image)


def test_add_axes_parameters():
    plotter = pyvista.Plotter()
    plotter.add_axes()
    marker_args = dict(
        cone_radius=0.6,
        shaft_length=0.7,
        tip_length=0.3,
        ambient=0.5,
        label_size=(0.4, 0.16),
    )
    plotter.add_axes(
        line_width=5,
        marker_args=marker_args,
        viewport=(0, 0, 0.4, 0.4),
    )
    plotter.show(before_close_callback=verify_cache_image)


def test_show_axes_all():
    plotter = pyvista.Plotter()
    plotter.show_axes_all()
    plotter.show(before_close_callback=verify_cache_image)


def test_hide_axes_all():
    plotter = pyvista.Plotter()
    plotter.hide_axes_all()
    plotter.show(before_close_callback=verify_cache_image)


def test_isometric_view_interactive(sphere):
    plotter_iso = pyvista.Plotter()
    plotter_iso.add_mesh(sphere)
    plotter_iso.camera_position = 'xy'
    cpos_old = plotter_iso.camera_position
    plotter_iso.isometric_view_interactive()
    assert not plotter_iso.camera_position == cpos_old


def test_add_point_labels():
    plotter = pyvista.Plotter()

    # cannot use random points with image regression
    points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0.5, 0.5, 0.5], [1, 1, 1]])
    n = points.shape[0]

    with pytest.raises(ValueError):
        plotter.add_point_labels(points, range(n - 1))

    plotter.add_point_labels(points, range(n), show_points=True, point_color='r', point_size=10)
    plotter.add_point_labels(
        points - 1, range(n), show_points=False, point_color='r', point_size=10
    )
    plotter.show(before_close_callback=verify_cache_image)


@pytest.mark.parametrize('always_visible', [False, True])
def test_add_point_labels_always_visible(always_visible):
    # just make sure it runs without exception
    plotter = pyvista.Plotter()
    plotter.add_point_labels(
        np.array([[0.0, 0.0, 0.0]]), ['hello world'], always_visible=always_visible
    )
    plotter.show()


def test_set_background():
    plotter = pyvista.Plotter()
    plotter.set_background('k')
    plotter.background_color = "yellow"
    plotter.set_background([0, 0, 0], top=[1, 1, 1])  # Gradient
    plotter.background_color
    plotter.show()

    plotter = pyvista.Plotter(shape=(1, 2))
    plotter.set_background('orange')
    for renderer in plotter.renderers:
        assert renderer.GetBackground() == pyvista.Color('orange')
    plotter.show()

    plotter = pyvista.Plotter(shape=(1, 2))
    plotter.subplot(0, 1)
    plotter.set_background('orange', all_renderers=False)
    assert plotter.renderers[0].GetBackground() != pyvista.Color('orange')
    assert plotter.renderers[1].GetBackground() == pyvista.Color('orange')
    plotter.show(before_close_callback=verify_cache_image)


def test_add_points():
    plotter = pyvista.Plotter()

    points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0.5, 0.5, 0.5], [1, 1, 1]])
    n = points.shape[0]

    plotter.add_points(
        points, scalars=np.arange(n), cmap=None, flip_scalars=True, show_scalar_bar=False
    )
    plotter.show(before_close_callback=verify_cache_image)


def test_key_press_event():
    plotter = pyvista.Plotter()
    plotter.key_press_event(None, None)
    plotter.close()


def test_enable_picking_gc():
    plotter = pyvista.Plotter()
    sphere = pyvista.Sphere()
    plotter.add_mesh(sphere)
    plotter.enable_cell_picking()
    plotter.close()


def test_left_button_down():
    plotter = pyvista.Plotter()
    if VTK9:
        with pytest.raises(ValueError):
            plotter.left_button_down(None, None)
    else:
        plotter.left_button_down(None, None)
    plotter.close()


def test_show_axes():
    plotter = pyvista.Plotter()
    plotter.show_axes()
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_cell_data(sphere):
    plotter = pyvista.Plotter()
    scalars = np.arange(sphere.n_faces)
    plotter.add_mesh(
        sphere,
        interpolate_before_map=True,
        scalars=scalars,
        n_colors=10,
        rng=sphere.n_faces,
        show_scalar_bar=False,
    )
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_clim(sphere):
    plotter = pyvista.Plotter()
    scalars = np.arange(sphere.n_faces)
    plotter.add_mesh(
        sphere,
        interpolate_before_map=True,
        scalars=scalars,
        n_colors=5,
        clim=10,
        show_scalar_bar=False,
    )
    assert plotter.mapper.GetScalarRange() == (-10, 10)
    plotter.show(before_close_callback=verify_cache_image)


def test_invalid_n_arrays(sphere):
    with pytest.raises(ValueError):
        plotter = pyvista.Plotter()
        plotter.add_mesh(sphere, scalars=np.arange(10))
        plotter.show()


def test_plot_arrow():
    cent = np.array([0, 0, 0])
    direction = np.array([1, 0, 0])
    pyvista.plot_arrows(cent, direction, before_close_callback=verify_cache_image)


def test_plot_arrows():
    cent = np.array([[0, 0, 0], [1, 0, 0]])
    direction = np.array([[1, 1, 1], [-1, -1, -1]])
    pyvista.plot_arrows(cent, direction, before_close_callback=verify_cache_image)


def test_add_arrows():
    vector = np.array([1, 0, 0])
    center = np.array([0, 0, 0])
    plotter = pyvista.Plotter()
    plotter.add_arrows(cent=center, direction=vector, mag=2.2, color="#009900")
    plotter.show(before_close_callback=verify_cache_image)


def test_axes():
    plotter = pyvista.Plotter()
    plotter.add_orientation_widget(pyvista.Cube(), color='b')
    plotter.add_mesh(pyvista.Cube())
    plotter.show(before_close_callback=verify_cache_image)


def test_box_axes():
    plotter = pyvista.Plotter()
    plotter.add_axes(box=True)
    plotter.add_mesh(pyvista.Sphere())
    plotter.show(before_close_callback=verify_cache_image)


def test_box_axes_color_box():
    plotter = pyvista.Plotter()
    plotter.add_axes(box=True, box_args={'color_box': True})
    plotter.add_mesh(pyvista.Sphere())
    plotter.show(before_close_callback=verify_cache_image)


def test_screenshot(tmpdir):
    plotter = pyvista.Plotter()
    plotter.add_mesh(pyvista.Sphere())
    img = plotter.screenshot(transparent_background=False)
    assert np.any(img)
    img_again = plotter.screenshot()
    assert np.any(img_again)
    filename = str(tmpdir.mkdir("tmpdir").join('export-graphic.svg'))
    plotter.save_graphic(filename)

    # test window and array size
    w, h = 20, 10
    img = plotter.screenshot(transparent_background=False, window_size=(w, h))
    assert img.shape == (h, w, 3)
    img = plotter.screenshot(transparent_background=True, window_size=(w, h))
    assert img.shape == (h, w, 4)

    # check error before first render
    plotter = pyvista.Plotter(off_screen=False)
    plotter.add_mesh(pyvista.Sphere())
    with pytest.raises(RuntimeError):
        plotter.screenshot()


def test_screenshot_bytes():
    # Test screenshot to bytes object
    buffer = io.BytesIO()
    plotter = pyvista.Plotter(off_screen=True)
    plotter.add_mesh(pyvista.Sphere())
    plotter.show(screenshot=buffer)
    buffer.seek(0)
    im = Image.open(buffer)
    assert im.format == 'PNG'


@pytest.mark.parametrize('ext', SUPPORTED_FORMATS)
def test_save_screenshot(tmpdir, sphere, ext):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp' + ext))
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.screenshot(filename)
    assert os.path.isfile(filename)
    assert pathlib.Path(filename).stat().st_size


def test_scalars_by_name():
    plotter = pyvista.Plotter()
    data = examples.load_uniform()
    plotter.add_mesh(data, scalars='Spatial Cell Data')
    plotter.show(before_close_callback=verify_cache_image)


def test_multi_block_plot():
    multi = pyvista.MultiBlock()
    multi.append(examples.load_rectilinear())
    uni = examples.load_uniform()
    arr = np.random.rand(uni.n_cells)
    uni.cell_data.set_array(arr, 'Random Data')
    multi.append(uni)
    # And now add a data set without the desired array and a NULL component
    multi.append(examples.load_airplane())

    # missing data should still plot
    multi.plot(scalars='Random Data')

    multi.plot(multi_colors=True, before_close_callback=verify_cache_image)


def test_clear(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.clear()
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_texture():
    """Test adding a texture to a plot"""
    globe = examples.load_globe()
    texture = examples.load_globe_texture()
    plotter = pyvista.Plotter()
    plotter.add_mesh(globe, texture=texture)
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_texture_alone(tmpdir):
    """Test adding a texture to a plot"""
    path = str(tmpdir.mkdir("tmpdir"))
    image = Image.new('RGB', (10, 10), color='blue')
    filename = os.path.join(path, 'tmp.jpg')
    image.save(filename)

    texture = pyvista.read_texture(filename)
    texture.plot(rgba=True, before_close_callback=verify_cache_image)


def test_plot_texture_associated():
    """Test adding a texture to a plot"""
    globe = examples.load_globe()
    plotter = pyvista.Plotter()
    plotter.add_mesh(globe, texture=True)
    plotter.show(before_close_callback=verify_cache_image)


def test_read_texture_from_numpy():
    """Test adding a texture to a plot"""
    globe = examples.load_globe()
    texture = pyvista.numpy_to_texture(imageio.imread(examples.mapfile))
    plotter = pyvista.Plotter()
    plotter.add_mesh(globe, texture=texture)
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_rgb():
    """Test adding a texture to a plot"""
    cube = pyvista.Cube()
    cube.clear_data()
    x_face_color = (255, 0, 0)
    y_face_color = (0, 255, 0)
    z_face_color = (0, 0, 255)
    face_colors = np.array(
        [
            x_face_color,
            x_face_color,
            y_face_color,
            y_face_color,
            z_face_color,
            z_face_color,
        ],
        dtype=np.uint8,
    )
    cube.cell_data['face_colors'] = face_colors
    plotter = pyvista.Plotter()
    plotter.add_mesh(cube, scalars='face_colors', rgb=True)
    plotter.show(before_close_callback=verify_cache_image)


def test_vector_array_with_points(multicomp_poly):
    """Test using vector valued data with and without component arg."""
    # test no component argument
    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_points')
    pl.show()

    # test component argument
    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_points', component=0)
    pl.show(before_close_callback=verify_cache_image)


def test_vector_array_with_cells(multicomp_poly):
    """Test using vector valued data with and without component arg."""
    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_cells')
    pl.show()

    # test component argument
    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_cells', component=0)
    pl.show(before_close_callback=verify_cache_image)


def test_vector_array(multicomp_poly):
    """Test using vector valued data for image regression."""
    pl = pyvista.Plotter(shape=(2, 2))
    pl.subplot(0, 0)
    pl.add_mesh(multicomp_poly, scalars="vector_values_points", show_scalar_bar=False)
    pl.subplot(0, 1)
    pl.add_mesh(multicomp_poly.copy(), scalars="vector_values_points", component=0)
    pl.subplot(1, 0)
    pl.add_mesh(multicomp_poly.copy(), scalars="vector_values_points", component=1)
    pl.subplot(1, 1)
    pl.add_mesh(multicomp_poly.copy(), scalars="vector_values_points", component=2)
    pl.link_views()
    pl.reset_camera()
    pl.show(before_close_callback=verify_cache_image)


def test_vector_plotting_doesnt_modify_data(multicomp_poly):
    """Test that the operations in plotting do not modify the data in the mesh."""

    copy_vector_values_points = multicomp_poly["vector_values_points"].copy()
    copy_vector_values_cells = multicomp_poly["vector_values_cells"].copy()

    # test that adding a vector with no component parameter to a Plotter instance
    # does not modify it.
    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_points')
    pl.show()
    assert np.array_equal(multicomp_poly['vector_values_points'], copy_vector_values_points)

    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_cells')
    pl.show()
    assert np.array_equal(multicomp_poly['vector_values_cells'], copy_vector_values_cells)

    # test that adding a vector with a component parameter to a Plotter instance
    # does not modify it.
    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_points', component=0)
    pl.show()
    assert np.array_equal(multicomp_poly['vector_values_points'], copy_vector_values_points)

    pl = pyvista.Plotter()
    pl.add_mesh(multicomp_poly, scalars='vector_values_cells', component=0)
    pl.show()
    assert np.array_equal(multicomp_poly['vector_values_cells'], copy_vector_values_cells)


def test_vector_array_fail_with_incorrect_component(multicomp_poly):
    """Test failure modes of component argument."""
    p = pyvista.Plotter()

    # Non-Integer
    with pytest.raises(TypeError):
        p.add_mesh(multicomp_poly, scalars='vector_values_points', component=1.5)
        p.show()

    # Component doesn't exist
    p = pyvista.Plotter()
    with pytest.raises(ValueError):
        p.add_mesh(multicomp_poly, scalars='vector_values_points', component=3)
        p.show()

    # Component doesn't exist
    p = pyvista.Plotter()
    with pytest.raises(ValueError):
        p.add_mesh(multicomp_poly, scalars='vector_values_points', component=-1)
        p.show()


def test_camera(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.view_isometric()
    plotter.reset_camera()
    plotter.view_xy()
    plotter.view_xz()
    plotter.view_yz()
    plotter.add_mesh(examples.load_uniform(), reset_camera=True, culling=True)
    plotter.view_xy(True)
    plotter.view_xz(True)
    plotter.view_yz(True)
    plotter.show(before_close_callback=verify_cache_image)

    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.camera.zoom(5)
    plotter.camera.up = 0, 0, 10
    plotter.show()


def test_multi_renderers():
    plotter = pyvista.Plotter(shape=(2, 2))

    plotter.subplot(0, 0)
    plotter.add_text('Render Window 0', font_size=30)
    sphere = pyvista.Sphere()
    plotter.add_mesh(sphere, scalars=sphere.points[:, 2], show_scalar_bar=False)
    plotter.add_scalar_bar('Z', vertical=True)

    plotter.subplot(0, 1)
    plotter.add_text('Render Window 1', font_size=30)
    plotter.add_mesh(pyvista.Cube(), show_edges=True)

    plotter.subplot(1, 0)
    plotter.add_text('Render Window 2', font_size=30)
    plotter.add_mesh(pyvista.Arrow(), color='y', show_edges=True)

    plotter.subplot(1, 1)
    plotter.add_text('Render Window 3', position=(0.0, 0.0), font_size=30, viewport=True)
    plotter.add_mesh(pyvista.Cone(), color='g', show_edges=True, culling=True)
    plotter.add_bounding_box(render_lines_as_tubes=True, line_width=5)
    plotter.show_bounds(all_edges=True)

    plotter.update_bounds_axes()
    plotter.show(before_close_callback=verify_cache_image)


def test_multi_renderers_subplot_ind_2x1():

    # Test subplot indices (2 rows by 1 column)
    plotter = pyvista.Plotter(shape=(2, 1))
    # First row
    plotter.subplot(0, 0)
    plotter.add_mesh(pyvista.Sphere())
    # Second row
    plotter.subplot(1, 0)
    plotter.add_mesh(pyvista.Cube())
    plotter.show(before_close_callback=verify_cache_image)


def test_multi_renderers_subplot_ind_1x2():
    # Test subplot indices (1 row by 2 columns)
    plotter = pyvista.Plotter(shape=(1, 2))
    # First column
    plotter.subplot(0, 0)
    plotter.add_mesh(pyvista.Sphere())
    # Second column
    plotter.subplot(0, 1)
    plotter.add_mesh(pyvista.Cube())
    plotter.show(before_close_callback=verify_cache_image)


def test_multi_renderers_bad_indices():
    with pytest.raises(IndexError):
        # Test bad indices
        plotter = pyvista.Plotter(shape=(1, 2))
        plotter.subplot(0, 0)
        plotter.add_mesh(pyvista.Sphere())
        plotter.subplot(1, 0)
        plotter.add_mesh(pyvista.Cube())
        plotter.show()


def test_multi_renderers_subplot_ind_3x1():
    # Test subplot 3 on left, 1 on right
    plotter = pyvista.Plotter(shape='3|1')
    # First column
    plotter.subplot(0)
    plotter.add_mesh(pyvista.Sphere())
    plotter.subplot(1)
    plotter.add_mesh(pyvista.Cube())
    plotter.subplot(2)
    plotter.add_mesh(pyvista.Cylinder())
    plotter.subplot(3)
    plotter.add_mesh(pyvista.Cone())
    plotter.show(before_close_callback=verify_cache_image)


def test_multi_renderers_subplot_ind_3x1_splitting_pos():
    # Test subplot 3 on top, 1 on bottom
    plotter = pyvista.Plotter(shape='3/1', splitting_position=0.5)
    # First column
    plotter.subplot(0)
    plotter.add_mesh(pyvista.Sphere())
    plotter.subplot(1)
    plotter.add_mesh(pyvista.Cube())
    plotter.subplot(2)
    plotter.add_mesh(pyvista.Cylinder())
    plotter.subplot(3)
    plotter.add_mesh(pyvista.Cone())
    plotter.show(before_close_callback=verify_cache_image)


def test_multi_renderers_subplot_ind_1x3():
    # Test subplot 3 on bottom, 1 on top
    plotter = pyvista.Plotter(shape='1|3')
    # First column
    plotter.subplot(0)
    plotter.add_mesh(pyvista.Sphere())
    plotter.subplot(1)
    plotter.add_mesh(pyvista.Cube())
    plotter.subplot(2)
    plotter.add_mesh(pyvista.Cylinder())
    plotter.subplot(3)
    plotter.add_mesh(pyvista.Cone())
    plotter.show(before_close_callback=verify_cache_image)


def test_subplot_groups():
    plotter = pyvista.Plotter(shape=(3, 3), groups=[(1, [1, 2]), (np.s_[:], 0)])
    plotter.subplot(0, 0)
    plotter.add_mesh(pyvista.Sphere())
    plotter.subplot(0, 1)
    plotter.add_mesh(pyvista.Cube())
    plotter.subplot(0, 2)
    plotter.add_mesh(pyvista.Arrow())
    plotter.subplot(1, 1)
    plotter.add_mesh(pyvista.Cylinder())
    plotter.subplot(2, 1)
    plotter.add_mesh(pyvista.Cone())
    plotter.subplot(2, 2)
    plotter.add_mesh(pyvista.Box())
    plotter.show(before_close_callback=verify_cache_image)


def test_subplot_groups_fail():
    # Test group overlap
    with pytest.raises(ValueError):
        # Partial overlap
        pyvista.Plotter(shape=(3, 3), groups=[([1, 2], [0, 1]), ([0, 1], [1, 2])])
    with pytest.raises(ValueError):
        # Full overlap (inner)
        pyvista.Plotter(shape=(4, 4), groups=[(np.s_[:], np.s_[:]), ([1, 2], [1, 2])])
    with pytest.raises(ValueError):
        # Full overlap (outer)
        pyvista.Plotter(shape=(4, 4), groups=[(1, [1, 2]), ([0, 3], np.s_[:])])


@skip_windows
def test_link_views(sphere):
    plotter = pyvista.Plotter(shape=(1, 4))
    plotter.subplot(0, 0)
    plotter.add_mesh(sphere, smooth_shading=False, show_edges=False)
    plotter.subplot(0, 1)
    plotter.add_mesh(sphere, smooth_shading=True, show_edges=False)
    plotter.subplot(0, 2)
    plotter.add_mesh(sphere, smooth_shading=False, show_edges=True)
    plotter.subplot(0, 3)
    plotter.add_mesh(sphere, smooth_shading=True, show_edges=True)
    with pytest.raises(TypeError):
        plotter.link_views(views='foo')
    plotter.link_views([0, 1])
    plotter.link_views()
    with pytest.raises(TypeError):
        plotter.unlink_views(views='foo')
    plotter.unlink_views([0, 1])
    plotter.unlink_views(2)
    plotter.unlink_views()
    plotter.show(before_close_callback=verify_cache_image)


def test_orthographic_slicer(uniform):
    uniform.set_active_scalars('Spatial Cell Data')
    slices = uniform.slice_orthogonal()

    # Orthographic Slicer
    p = pyvista.Plotter(shape=(2, 2))

    p.subplot(1, 1)
    p.add_mesh(slices, clim=uniform.get_data_range())
    p.add_axes()
    p.enable()

    p.subplot(0, 0)
    p.add_mesh(slices['XY'])
    p.view_xy()
    p.disable()

    p.subplot(0, 1)
    p.add_mesh(slices['XZ'])
    p.view_xz(negative=True)
    p.disable()

    p.subplot(1, 0)
    p.add_mesh(slices['YZ'])
    p.view_yz()
    p.disable()

    p.show()


def test_remove_actor(uniform):
    plotter = pyvista.Plotter()
    plotter.add_mesh(uniform.copy(), name='data')
    plotter.add_mesh(uniform.copy(), name='data')
    plotter.add_mesh(uniform.copy(), name='data')
    plotter.show(before_close_callback=verify_cache_image)


def test_image_properties():
    mesh = examples.load_uniform()
    p = pyvista.Plotter()
    p.add_mesh(mesh)
    p.show(auto_close=False)  # DO NOT close plotter
    # Get RGB image
    _ = p.image
    # Get the depth image
    _ = p.get_image_depth()
    p.close()
    p = pyvista.Plotter()
    p.add_mesh(mesh)
    p.store_image = True
    assert p.store_image is True
    p.show()  # close plotter
    # Get RGB image
    _ = p.image
    # verify property matches method while testing both available
    assert np.allclose(p.image_depth, p.get_image_depth(), equal_nan=True)
    p.close()

    # gh-920
    rr = np.array([[-0.5, -0.5, 0], [-0.5, 0.5, 1], [0.5, 0.5, 0], [0.5, -0.5, 1]])
    tris = np.array([[3, 0, 2, 1], [3, 2, 0, 3]])
    mesh = pyvista.PolyData(rr, tris)
    p = pyvista.Plotter()
    p.add_mesh(mesh, color=True)
    p.renderer.camera_position = (0.0, 0.0, 1.0)
    p.renderer.ResetCamera()
    p.enable_parallel_projection()
    assert p.renderer.camera_set
    p.show(interactive=False, auto_close=False)
    img = p.get_image_depth(fill_value=0.0)
    rng = np.ptp(img)
    assert 0.3 < rng < 0.4, rng  # 0.3313504 in testing
    p.close()


def test_volume_rendering_from_helper(uniform):
    uniform.plot(volume=True, opacity='linear', before_close_callback=verify_cache_image)


def test_volume_rendering_from_plotter(uniform):
    plotter = pyvista.Plotter()
    plotter.add_volume(uniform, opacity='sigmoid', cmap='jet', n_colors=15)
    plotter.show(before_close_callback=verify_cache_image)


@skip_windows
def test_multiblock_volume_rendering(uniform):
    ds_a = uniform.copy()
    ds_b = uniform.copy()
    ds_b.origin = (9.0, 0.0, 0.0)
    ds_c = uniform.copy()
    ds_c.origin = (0.0, 9.0, 0.0)
    ds_d = uniform.copy()
    ds_d.origin = (9.0, 9.0, 0.0)

    data = pyvista.MultiBlock(
        dict(
            a=ds_a,
            b=ds_b,
            c=ds_c,
            d=ds_d,
        )
    )
    data['a'].rename_array('Spatial Point Data', 'a')
    data['b'].rename_array('Spatial Point Data', 'b')
    data['c'].rename_array('Spatial Point Data', 'c')
    data['d'].rename_array('Spatial Point Data', 'd')
    data.plot(volume=True, multi_colors=True, before_close_callback=verify_cache_image)


def test_array_volume_rendering(uniform):
    arr = uniform["Spatial Point Data"].reshape(uniform.dimensions)
    pyvista.plot(arr, volume=True, opacity='linear', before_close_callback=verify_cache_image)


def test_plot_compare_four():
    # Really just making sure no errors are thrown
    mesh = examples.load_uniform()
    data_a = mesh.contour()
    data_b = mesh.threshold_percent(0.5)
    data_c = mesh.decimate_boundary(0.5)
    data_d = mesh.glyph(scale=False, orient=False)
    pyvista.plot_compare_four(
        data_a,
        data_b,
        data_c,
        data_d,
        disply_kwargs={'color': 'w'},
        show_kwargs={'before_close_callback': verify_cache_image},
    )


def test_plot_depth_peeling():
    mesh = examples.load_airplane()
    p = pyvista.Plotter()
    p.add_mesh(mesh)
    p.enable_depth_peeling()
    p.disable_depth_peeling()
    p.show(before_close_callback=verify_cache_image)


@pytest.mark.skipif(os.name == 'nt', reason="No testing on windows for EDL")
def test_plot_eye_dome_lighting_plot(airplane):
    airplane.plot(eye_dome_lighting=True, before_close_callback=verify_cache_image)


@pytest.mark.skipif(os.name == 'nt', reason="No testing on windows for EDL")
def test_plot_eye_dome_lighting_plotter(airplane):
    p = pyvista.Plotter()
    p.add_mesh(airplane)
    p.enable_eye_dome_lighting()
    p.show(before_close_callback=verify_cache_image)


@pytest.mark.skipif(os.name == 'nt', reason="No testing on windows for EDL")
def test_plot_eye_dome_lighting_enable_disable(airplane):
    p = pyvista.Plotter()
    p.add_mesh(airplane)
    p.enable_eye_dome_lighting()
    p.disable_eye_dome_lighting()
    p.show(before_close_callback=verify_cache_image)


@skip_windows
def test_opacity_by_array_direct(plane):
    # test with opacity parm as an array, both cell and point sized
    plane_shift = plane.translate((0, 0, 1), inplace=False)
    pl = pyvista.Plotter()
    pl.add_mesh(plane, color='b', opacity=np.linspace(0, 1, plane.n_points), show_edges=True)
    pl.add_mesh(plane_shift, color='r', opacity=np.linspace(0, 1, plane.n_cells), show_edges=True)
    pl.show(before_close_callback=verify_cache_image)


def test_opacity_by_array(uniform):
    # Test with opacity array
    opac = uniform['Spatial Point Data'] / uniform['Spatial Point Data'].max()
    uniform['opac'] = opac
    p = pyvista.Plotter()
    p.add_mesh(uniform, scalars='Spatial Point Data', opacity='opac')
    p.show(before_close_callback=verify_cache_image)


def test_opacity_by_array_uncertainty(uniform):
    # Test with uncertainty array (transparency)
    opac = uniform['Spatial Point Data'] / uniform['Spatial Point Data'].max()
    uniform['unc'] = opac
    p = pyvista.Plotter()
    p.add_mesh(uniform, scalars='Spatial Point Data', opacity='unc', use_transparency=True)
    p.show(before_close_callback=verify_cache_image)


def test_opacity_by_array_user_transform(uniform):
    uniform['Spatial Point Data'] /= uniform['Spatial Point Data'].max()

    # Test with user defined transfer function
    opacities = [0, 0.2, 0.9, 0.2, 0.1]
    p = pyvista.Plotter()
    p.add_mesh(uniform, scalars='Spatial Point Data', opacity=opacities)
    p.show(before_close_callback=verify_cache_image)


def test_opacity_mismatched_fail(uniform):
    opac = uniform['Spatial Point Data'] / uniform['Spatial Point Data'].max()
    uniform['unc'] = opac

    # Test using mismatched arrays
    p = pyvista.Plotter()
    with pytest.raises(ValueError):
        # cell scalars vs point opacity
        p.add_mesh(uniform, scalars='Spatial Cell Data', opacity='unc')


def test_opacity_by_array_preference():
    tetra = pyvista.Tetrahedron()  # 4 points, 4 cells
    opacities = np.linspace(0.2, 0.8, tetra.n_points)
    tetra.clear_data()
    tetra.point_data['scalars'] = tetra.cell_data['scalars'] = np.arange(tetra.n_points)
    tetra.point_data['opac'] = tetra.cell_data['opac'] = opacities

    # test opacity by key
    p = pyvista.Plotter()
    p.add_mesh(tetra.copy(), opacity='opac', preference='cell')
    p.add_mesh(tetra.translate((2, 0, 0), inplace=False), opacity='opac', preference='point')
    p.close()

    # test opacity by array
    p = pyvista.Plotter()
    p.add_mesh(tetra.copy(), opacity=opacities, preference='cell')
    p.add_mesh(tetra.translate((2, 0, 0), inplace=False), opacity=opacities, preference='point')
    p.show(before_close_callback=verify_cache_image)


def test_opacity_transfer_functions():
    n = 256
    mapping = pyvista.opacity_transfer_function('linear', n)
    assert len(mapping) == n
    mapping = pyvista.opacity_transfer_function('sigmoid_10', n)
    assert len(mapping) == n
    with pytest.raises(KeyError):
        mapping = pyvista.opacity_transfer_function('foo', n)
    with pytest.raises(RuntimeError):
        mapping = pyvista.opacity_transfer_function(np.linspace(0, 1, 2 * n), n)
    foo = np.linspace(0, n, n)
    mapping = pyvista.opacity_transfer_function(foo, n)
    assert np.allclose(foo, mapping)
    foo = [0, 0.2, 0.9, 0.2, 0.1]
    mapping = pyvista.opacity_transfer_function(foo, n, interpolate=False)
    assert len(mapping) == n
    foo = [3, 5, 6, 10]
    mapping = pyvista.opacity_transfer_function(foo, n)
    assert len(mapping) == n


def test_closing_and_mem_cleanup():
    n = 5
    for _ in range(n):
        for _ in range(n):
            p = pyvista.Plotter()
            for k in range(n):
                p.add_mesh(pyvista.Sphere(radius=k))
            p.show()
        pyvista.close_all()


def test_above_below_scalar_range_annotations():
    p = pyvista.Plotter()
    p.add_mesh(
        examples.load_uniform(),
        clim=[100, 500],
        cmap='viridis',
        below_color='blue',
        above_color='red',
    )
    p.show(before_close_callback=verify_cache_image)


def test_user_annotations_scalar_bar_mesh(uniform):
    p = pyvista.Plotter()
    p.add_mesh(uniform, annotations={100.0: 'yum'})
    p.show(before_close_callback=verify_cache_image)


def test_fixed_font_size_annotation_text_scaling_off():
    p = pyvista.Plotter()
    sargs = {'title_font_size': 12, 'label_font_size': 10}
    p.add_mesh(
        examples.load_uniform(),
        clim=[100, 500],
        cmap='viridis',
        below_color='blue',
        above_color='red',
        annotations={300.0: 'yum'},
        scalar_bar_args=sargs,
    )
    p.show(before_close_callback=verify_cache_image)


def test_user_annotations_scalar_bar_volume(uniform):
    p = pyvista.Plotter()
    p.add_volume(uniform, scalars='Spatial Point Data', annotations={100.0: 'yum'})
    p.show(before_close_callback=verify_cache_image)


def test_scalar_bar_args_unmodified_add_mesh(sphere):
    sargs = {"vertical": True}
    sargs_copy = sargs.copy()

    p = pyvista.Plotter()
    p.add_mesh(sphere, scalar_bar_args=sargs)

    assert sargs == sargs_copy


def test_scalar_bar_args_unmodified_add_volume(uniform):
    sargs = {"vertical": True}
    sargs_copy = sargs.copy()

    p = pyvista.Plotter()
    p.add_volume(uniform, scalar_bar_args=sargs)

    assert sargs == sargs_copy


def test_plot_string_array():
    mesh = examples.load_uniform()
    labels = np.empty(mesh.n_cells, dtype='<U10')
    labels[:] = 'High'
    labels[mesh['Spatial Cell Data'] < 300] = 'Medium'
    labels[mesh['Spatial Cell Data'] < 100] = 'Low'
    mesh['labels'] = labels
    p = pyvista.Plotter()
    p.add_mesh(mesh, scalars='labels')
    p.show(before_close_callback=verify_cache_image)


def test_fail_plot_table():
    """Make sure tables cannot be plotted"""
    table = pyvista.Table(np.random.rand(50, 3))
    with pytest.raises(TypeError):
        pyvista.plot(table)
    with pytest.raises(TypeError):
        plotter = pyvista.Plotter()
        plotter.add_mesh(table)


def test_bad_keyword_arguments():
    """Make sure bad keyword arguments raise an error"""
    mesh = examples.load_uniform()
    with pytest.raises(TypeError):
        pyvista.plot(mesh, foo=5)
    with pytest.raises(TypeError):
        pyvista.plot(mesh, scalar=mesh.active_scalars_name)
    with pytest.raises(TypeError):
        plotter = pyvista.Plotter()
        plotter.add_mesh(mesh, scalar=mesh.active_scalars_name)
        plotter.show()
    with pytest.raises(TypeError):
        plotter = pyvista.Plotter()
        plotter.add_mesh(mesh, foo="bad")
        plotter.show()


def test_cmap_list(sphere):
    n = sphere.n_points
    scalars = np.empty(n)
    scalars[: n // 3] = 0
    scalars[n // 3 : 2 * n // 3] = 1
    scalars[2 * n // 3 :] = 2

    with pytest.raises(TypeError):
        sphere.plot(scalars=scalars, cmap=['red', None, 'blue'])

    sphere.plot(
        scalars=scalars, cmap=['red', 'green', 'blue'], before_close_callback=verify_cache_image
    )


def test_default_name_tracking():
    N = 10
    color = "tan"

    p = pyvista.Plotter()
    for i in range(N):
        for j in range(N):
            center = (i, j, 0)
            mesh = pyvista.Sphere(center=center)
            p.add_mesh(mesh, color=color)
    n_made_it = len(p.renderer._actors)
    p.show()
    assert n_made_it == N**2

    # release attached scalars
    mesh.ReleaseData()
    del mesh


def test_add_background_image_global(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.add_background_image(examples.mapfile, as_global=True)
    plotter.show(before_close_callback=verify_cache_image)


def test_add_background_image_not_global(sphere):
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.add_background_image(examples.mapfile, as_global=False)
    plotter.show(before_close_callback=verify_cache_image)


def test_add_background_image_subplots(airplane):
    pl = pyvista.Plotter(shape=(2, 2))
    pl.add_background_image(examples.mapfile, scale=1, as_global=False)
    pl.add_mesh(airplane)
    pl.subplot(1, 1)
    pl.add_background_image(examples.mapfile, scale=1, as_global=False)
    pl.add_mesh(airplane)
    pl.remove_background_image()

    # should error out as there's no background
    with pytest.raises(RuntimeError):
        pl.remove_background_image()

    pl.add_background_image(examples.mapfile, scale=1, as_global=False)
    pl.show(before_close_callback=verify_cache_image)


def test_add_remove_floor(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.add_floor(color='b', line_width=2, lighting=True)
    pl.add_bounding_box()  # needed for update_bounds_axes
    assert len(pl.renderer._floors) == 1
    pl.add_mesh(pyvista.Sphere(1.0))
    pl.update_bounds_axes()
    assert len(pl.renderer._floors) == 1
    pl.show()

    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.add_floor(color='b', line_width=2, lighting=True)
    pl.remove_floors()
    assert not pl.renderer._floors
    pl.show(before_close_callback=verify_cache_image)


def test_reset_camera_clipping_range(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere)

    # get default clipping range
    default_clipping_range = pl.camera.clipping_range

    # make sure we assign something different than default
    assert default_clipping_range != (10, 100)

    # set clipping range to some random numbers and make sure
    # assignment is successful
    pl.camera.clipping_range = (10, 100)
    assert pl.camera.clipping_range == (10, 100)

    pl.reset_camera_clipping_range()
    assert pl.camera.clipping_range == default_clipping_range
    assert pl.camera.clipping_range != (10, 100)


def test_index_vs_loc():
    # first: 2d grid
    pl = pyvista.Plotter(shape=(2, 3))
    # index_to_loc valid cases
    vals = [0, 2, 4]
    expecteds = [(0, 0), (0, 2), (1, 1)]
    for val, expected in zip(vals, expecteds):
        assert tuple(pl.renderers.index_to_loc(val)) == expected
    # loc_to_index valid cases
    vals = [(0, 0), (0, 2), (1, 1)]
    expecteds = [0, 2, 4]
    for val, expected in zip(vals, expecteds):
        assert pl.renderers.loc_to_index(val) == expected
        assert pl.renderers.loc_to_index(expected) == expected

    # indexing failing cases
    with pytest.raises(TypeError):
        pl.renderers.index_to_loc(1.5)
    with pytest.raises(IndexError):
        pl.renderers.index_to_loc((-1))
    with pytest.raises(TypeError):
        pl.renderers.index_to_loc((1, 2))
    with pytest.raises(IndexError):
        pl.renderers.loc_to_index((-1, 0))
    with pytest.raises(IndexError):
        pl.renderers.loc_to_index((0, -1))
    with pytest.raises(TypeError):
        pl.renderers.loc_to_index({1, 2})
    with pytest.raises(ValueError):
        pl.renderers.loc_to_index((1, 2, 3))

    # set active_renderer fails
    with pytest.raises(IndexError):
        pl.renderers.set_active_renderer(0, -1)

    # then: "1d" grid
    pl = pyvista.Plotter(shape='2|3')
    # valid cases
    for val in range(5):
        assert pl.renderers.index_to_loc(val) == val
        assert pl.renderers.index_to_loc(np.int_(val)) == val
        assert pl.renderers.loc_to_index(val) == val
        assert pl.renderers.loc_to_index(np.int_(val)) == val


def test_interactive_update():
    # Regression test for #1053
    p = pyvista.Plotter()
    p.show(interactive_update=True)
    assert isinstance(p.iren.interactor, vtk.vtkRenderWindowInteractor)
    p.close()

    p = pyvista.Plotter()
    with pytest.warns(UserWarning):
        p.show(auto_close=True, interactive_update=True)


def test_where_is():
    plotter = pyvista.Plotter(shape=(2, 2))
    plotter.subplot(0, 0)
    plotter.add_mesh(pyvista.Box(), name='box')
    plotter.subplot(0, 1)
    plotter.add_mesh(pyvista.Sphere(), name='sphere')
    plotter.subplot(1, 0)
    plotter.add_mesh(pyvista.Box(), name='box')
    plotter.subplot(1, 1)
    plotter.add_mesh(pyvista.Cone(), name='cone')
    places = plotter.where_is('box')
    assert isinstance(places, list)
    for loc in places:
        assert isinstance(loc, tuple)


def test_log_scale():
    mesh = examples.load_uniform()
    plotter = pyvista.Plotter()
    plotter.add_mesh(mesh, log_scale=True)
    plotter.show()


def test_set_focus():
    plane = pyvista.Plane()
    p = pyvista.Plotter()
    p.add_mesh(plane, color="tan", show_edges=True)
    p.set_focus((-0.5, -0.5, 0))  # focus on corner of the plane
    p.show(before_close_callback=verify_cache_image)


def test_set_viewup():
    plane = pyvista.Plane()
    plane_higher = pyvista.Plane(center=(0, 0, 1), i_size=0.5, j_size=0.5)
    p = pyvista.Plotter()
    p.add_mesh(plane, color="tan", show_edges=False)
    p.add_mesh(plane_higher, color="red", show_edges=False)
    p.set_viewup((1.0, 1.0, 1.0))
    p.show(before_close_callback=verify_cache_image)


def test_plot_remove_scalar_bar(sphere):
    sphere['z'] = sphere.points[:, 2]
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere, show_scalar_bar=False)
    plotter.add_scalar_bar(interactive=True)
    assert len(plotter.scalar_bars) == 1
    plotter.remove_scalar_bar()
    assert len(plotter.scalar_bars) == 0
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_shadows():
    plotter = pyvista.Plotter(lighting=None)

    # add several planes
    for plane_y in [2, 5, 10]:
        screen = pyvista.Plane(center=(0, plane_y, 0), direction=(0, 1, 0), i_size=5, j_size=5)
        plotter.add_mesh(screen, color='white')

    light = pyvista.Light(
        position=(0, 0, 0),
        focal_point=(0, 1, 0),
        color='cyan',
        intensity=15,
        cone_angle=15,
        positional=True,
        show_actor=True,
        attenuation_values=(2, 0, 0),
    )

    plotter.add_light(light)
    plotter.view_vector((1, -2, 2))

    # verify disabling shadows when not enabled does nothing
    plotter.disable_shadows()

    plotter.enable_shadows()

    # verify shadows can safely be enabled twice
    plotter.enable_shadows()

    plotter.show(before_close_callback=verify_cache_image)


def test_plot_shadows_enable_disable():
    """Test shadows are added and removed properly"""
    plotter = pyvista.Plotter(lighting=None)

    # add several planes
    for plane_y in [2, 5, 10]:
        screen = pyvista.Plane(center=(0, plane_y, 0), direction=(0, 1, 0), i_size=5, j_size=5)
        plotter.add_mesh(screen, color='white')

    light = pyvista.Light(
        position=(0, 0, 0), focal_point=(0, 1, 0), color='cyan', intensity=15, cone_angle=15
    )
    light.positional = True
    light.attenuation_values = (2, 0, 0)
    light.show_actor()

    plotter.add_light(light)
    plotter.view_vector((1, -2, 2))

    # add and remove and verify that the light passes through all via
    # image cache
    plotter.enable_shadows()
    plotter.disable_shadows()

    plotter.show(before_close_callback=verify_cache_image)


def test_plot_lighting_change_positional_true_false(sphere):
    light = pyvista.Light(positional=True, show_actor=True)

    plotter = pyvista.Plotter(lighting=None)
    plotter.add_light(light)
    light.positional = False
    plotter.add_mesh(sphere)
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_lighting_change_positional_false_true(sphere):
    light = pyvista.Light(positional=False, show_actor=True)

    plotter = pyvista.Plotter(lighting=None)

    plotter.add_light(light)
    light.positional = True
    plotter.add_mesh(sphere)
    plotter.show(before_close_callback=verify_cache_image)


def test_plotter_image():
    plotter = pyvista.Plotter()
    plotter.show()
    with pytest.raises(AttributeError, match='To retrieve an image after'):
        plotter.image

    plotter = pyvista.Plotter()
    wsz = tuple(plotter.window_size)
    plotter.store_image = True
    plotter.show()
    assert plotter.image.shape[:2] == wsz


def test_scalar_cell_priorities():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1.5, 1, 0], [0, 0, 1]])
    faces = np.hstack([[3, 0, 1, 2], [3, 0, 3, 2], [3, 0, 1, 3], [3, 1, 2, 3]])
    mesh = pyvista.PolyData(vertices, faces)
    colors = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 255]]

    mesh.cell_data['colors'] = colors
    plotter = pyvista.Plotter()
    plotter.add_mesh(mesh, scalars='colors', rgb=True, preference='cell')
    plotter.show(before_close_callback=verify_cache_image)


@pytest.mark.needs_vtk9
def test_collision_plot():
    """Verify rgba arrays automatically plot"""
    sphere0 = pyvista.Sphere()
    sphere1 = pyvista.Sphere(radius=0.6, center=(-1, 0, 0))
    col, n_contacts = sphere0.collision(sphere1, generate_scalars=True)

    plotter = pyvista.Plotter()
    plotter.add_mesh(col)
    plotter.camera_position = 'zy'
    plotter.show(before_close_callback=verify_cache_image)


@skip_mac
@pytest.mark.needs_vtk_version(9, 2, 0)
def test_chart_plot():
    """Basic test to verify chart plots correctly"""
    # Chart 1 (bottom left)
    chart_bl = pyvista.Chart2D(size=(0.4, 0.4), loc=(0.05, 0.05))
    chart_bl.background_color = "tab:purple"
    chart_bl.x_range = [np.pi / 2, 3 * np.pi / 2]
    chart_bl.y_axis.margin = 20
    chart_bl.y_axis.tick_locations = [-1, 0, 1]
    chart_bl.y_axis.tick_labels = ["Small", "Medium", "Large"]
    chart_bl.y_axis.tick_size += 10
    chart_bl.y_axis.tick_labels_offset += 12
    chart_bl.y_axis.pen.width = 10
    chart_bl.grid = True
    x = np.linspace(0, 2 * np.pi, 50)
    y = np.cos(x) * (-1) ** np.arange(len(x))
    hidden_plot = chart_bl.line(x, y, color="k", width=40)
    hidden_plot.visible = False  # Make sure plot visibility works
    chart_bl.bar(x, y, color="#33ff33")

    # Chart 2 (bottom right)
    chart_br = pyvista.Chart2D(size=(0.4, 0.4), loc=(0.55, 0.05))
    chart_br.background_texture = examples.load_globe_texture()
    chart_br.border_color = "r"
    chart_br.border_width = 5
    chart_br.border_style = "-."
    chart_br.hide_axes()
    x = np.linspace(0, 1, 50)
    y = np.sin(6.5 * x - 1)
    chart_br.scatter(x, y, color="y", size=15, style="o", label="Invisible label")
    chart_br.legend_visible = False  # Check legend visibility

    # Chart 3 (top left)
    chart_tl = pyvista.Chart2D(size=(0.4, 0.4), loc=(0.05, 0.55))
    chart_tl.background_color = (0.8, 0.8, 0.2)
    chart_tl.title = "Exponential growth"
    chart_tl.x_label = "X axis"
    chart_tl.y_label = "Y axis"
    chart_tl.y_axis.log_scale = True
    x = np.arange(6)
    y = 10**x
    chart_tl.line(x, y, color="tab:green", width=5, style="--")
    removed_plot = chart_tl.area(x, y, color="k")
    chart_tl.remove_plot(removed_plot)  # Make sure plot removal works

    # Chart 4 (top right)
    chart_tr = pyvista.Chart2D(size=(0.4, 0.4), loc=(0.55, 0.55))
    x = [0, 1, 2, 3, 4]
    ys = [[0, 1, 2, 3, 4], [1, 0, 1, 0, 1], [6, 4, 5, 3, 2]]
    chart_tr.stack(x, ys, colors="citrus", labels=["Segment 1", "Segment 2", "Segment 3"])
    chart_tr.legend_visible = True

    # Hidden chart (make sure chart visibility works)
    hidden_chart = pyvista.ChartPie([3, 4, 5])
    hidden_chart.visible = False

    # Removed chart (make sure chart removal works)
    removed_chart = pyvista.ChartBox([[1, 2, 3]])

    pl = pyvista.Plotter(window_size=(1000, 1000))
    pl.background_color = 'w'
    pl.add_chart(chart_bl, chart_br, chart_tl, chart_tr, hidden_chart, removed_chart)
    pl.remove_chart(removed_chart)
    pl.show(before_close_callback=verify_cache_image)


@skip_9_1_0
@skip_no_mpl_figure
def test_chart_matplotlib_plot():
    """Test integration with matplotlib"""
    import matplotlib.pyplot as plt

    rng = np.random.default_rng(1)
    # First, create the matplotlib figure
    # use tight layout to keep axis labels visible on smaller figures
    fig, ax = plt.subplots(tight_layout=True)
    alphas = [0.5 + i for i in range(5)]
    betas = [*reversed(alphas)]
    N = int(1e4)
    data = [rng.beta(alpha, beta, N) for alpha, beta in zip(alphas, betas)]
    labels = [
        f"$\\alpha={alpha:.1f}\\,;\\,\\beta={beta:.1f}$" for alpha, beta in zip(alphas, betas)
    ]
    ax.violinplot(data)
    ax.set_xticks(np.arange(1, 1 + len(labels)))
    ax.set_xticklabels(labels)
    ax.set_title("$B(\\alpha, \\beta)$")

    # Next, embed the figure into a pyvista plotting window
    pl = pyvista.Plotter()
    pl.background_color = "w"
    chart = pyvista.ChartMPL(fig)
    pl.add_chart(chart)
    pl.show()


def test_add_remove_background(sphere):
    plotter = pyvista.Plotter(shape=(1, 2))
    plotter.add_mesh(sphere, color='w')
    plotter.add_background_image(examples.mapfile, as_global=False)
    plotter.subplot(0, 1)
    plotter.add_mesh(sphere, color='w')
    plotter.add_background_image(examples.mapfile, as_global=False)
    plotter.remove_background_image()
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_zoom(sphere):
    # it's difficult to verify that zoom actually worked since we
    # can't get the output with cpos or verify the image cache matches
    sphere.plot(zoom=2, before_close_callback=verify_cache_image)


def test_splitting():
    nut = examples.load_nut()
    nut['sample_data'] = nut.points[:, 2]

    # feature angle of 50 will smooth the outer edges of the nut but not the inner.
    nut.plot(
        smooth_shading=True,
        split_sharp_edges=True,
        feature_angle=50,
        before_close_callback=verify_cache_image,
        show_scalar_bar=False,
    )


@skip_mac_flaky
def test_splitting_active_cells(cube):
    cube.cell_data['cell_id'] = range(cube.n_cells)
    cube = cube.triangulate().subdivide(1)
    cube.plot(
        smooth_shading=True,
        split_sharp_edges=True,
        show_scalar_bar=False,
        before_close_callback=verify_cache_image,
    )


def test_add_cursor():
    sphere = pyvista.Sphere()
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.add_cursor()
    plotter.show(before_close_callback=verify_cache_image)


def test_enable_stereo_render():
    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.Cube())
    pl.camera.distance = 0.1
    pl.enable_stereo_render()
    pl.show(before_close_callback=verify_cache_image)


def test_disable_stereo_render():
    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.Cube())
    pl.camera.distance = 0.1
    pl.enable_stereo_render()
    pl.disable_stereo_render()
    pl.show(before_close_callback=verify_cache_image)


def test_orbit_on_path(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, show_edges=True)
    pl.orbit_on_path(step=0.01, progress_bar=True)
    pl.close()


def test_rectlinear_edge_case():
    # ensure that edges look like square edges regardless of the dtype of X
    xrng = np.arange(-10, 10, 5)
    yrng = np.arange(-10, 10, 5)
    zrng = [1]
    rec_grid = pyvista.RectilinearGrid(xrng, yrng, zrng)
    rec_grid.plot(show_edges=True, cpos='xy', before_close_callback=verify_cache_image)


@skip_9_1_0
def test_pointset_plot(pointset):
    pointset.plot()

    pl = pyvista.Plotter()
    pl.add_mesh(pointset, scalars=range(pointset.n_points), show_scalar_bar=False)
    pl.show(before_close_callback=verify_cache_image)


@skip_9_1_0
def test_pointset_plot_as_points(pointset):
    pl = pyvista.Plotter()
    pl.add_points(pointset, scalars=range(pointset.n_points), show_scalar_bar=False)
    pl.show(before_close_callback=verify_cache_image)


@skip_9_1_0
def test_pointset_plot_vtk():
    pointset = vtk.vtkPointSet()
    points = pyvista.vtk_points(np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]))
    pointset.SetPoints(points)

    pl = pyvista.Plotter()
    pl.add_mesh(pointset, color='red', point_size=25)
    pl.show(before_close_callback=verify_cache_image)


@skip_9_1_0
def test_pointset_plot_as_points_vtk():
    pointset = vtk.vtkPointSet()
    points = pyvista.vtk_points(np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]))
    pointset.SetPoints(points)

    pl = pyvista.Plotter()
    pl.add_points(pointset, color='red', point_size=25)
    pl.show(before_close_callback=verify_cache_image)


def test_write_gif(sphere, tmpdir):
    basename = 'write_gif.gif'
    path = str(tmpdir.join(basename))
    pl = pyvista.Plotter()
    pl.open_gif(path)
    pl.add_mesh(sphere)
    pl.write_frame()
    pl.close()

    # assert file exists and is not empty
    assert os.path.isfile(path)
    assert os.path.getsize(path)


def test_ruler(sphere):
    sphere = pyvista.Sphere()
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.add_ruler([-0.6, -0.6, 0], [0.6, -0.6, 0], font_size_factor=1.2)
    plotter.view_xy()
    plotter.show(before_close_callback=verify_cache_image)


def test_plot_complex_value(plane):
    """Test plotting complex data."""
    data = np.arange(plane.n_points, dtype=np.complex128)
    data += np.linspace(0, 1, plane.n_points) * -1j
    with pytest.warns(np.ComplexWarning):
        plane.plot(scalars=data)

    pl = pyvista.Plotter()
    with pytest.warns(np.ComplexWarning):
        pl.add_mesh(plane, scalars=data, show_scalar_bar=True)
    pl.show(before_close_callback=verify_cache_image)


def test_warn_screenshot_notebook():
    pl = pyvista.Plotter(notebook=True)
    pl.theme.jupyter_backend = 'static'
    with pytest.warns(UserWarning, match='Set `jupyter_backend` backend to `"none"`'):
        pl.show(screenshot='tmp.png')


def test_culling_frontface(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, culling='frontface')
    pl.show(before_close_callback=verify_cache_image)


def test_add_text():
    plotter = pyvista.Plotter()
    plotter.add_text("Upper Left", position='upper_left', font_size=25, color='blue')
    plotter.add_text("Center", position=(0.5, 0.5), viewport=True, orientation=-90)
    plotter.show(before_close_callback=verify_cache_image)


@skip_windows
@skip_9_0_X
def test_depth_of_field():
    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.Sphere(), show_edges=True)
    pl.enable_depth_of_field()
    pl.show(before_close_callback=verify_cache_image)


@skip_9_0_X
def test_blurring():
    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.Sphere(), show_edges=True)
    pl.add_blurring()
    pl.show(before_close_callback=verify_cache_image)


@skip_mesa
def test_ssaa_pass():
    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.Sphere(), show_edges=True)
    pl.enable_anti_aliasing('ssaa')
    pl.show(before_close_callback=verify_cache_image)


@skip_windows_mesa
def test_ssao_pass():
    ugrid = pyvista.UniformGrid(dims=(2, 2, 2)).to_tetrahedra(5).explode()
    pl = pyvista.Plotter()
    pl.add_mesh(ugrid)

    if pyvista.vtk_version_info < (9,):
        with pytest.raises(pyvista.core.errors.VTKVersionError):
            pl.enable_ssao()
        return

    pl.enable_ssao()
    pl.show(before_close_callback=verify_cache_image, auto_close=False)

    # ensure this fails when ssao disabled
    pl.disable_ssao()
    with pytest.raises(RuntimeError):
        pl.show(before_close_callback=verify_cache_image)


@skip_mesa
def test_ssao_pass_from_helper():
    ugrid = pyvista.UniformGrid(dims=(2, 2, 2)).to_tetrahedra(5).explode()

    if pyvista.vtk_version_info < (9,):
        with pytest.raises(pyvista.core.errors.VTKVersionError):
            ugrid.plot(ssao=True)
        return

    ugrid.plot(ssao=True, before_close_callback=verify_cache_image)


@skip_windows
def test_many_multi_pass():
    pl = pyvista.Plotter(lighting=None)
    pl.add_mesh(pyvista.Sphere(), show_edges=True)
    pl.add_light(pyvista.Light(position=(0, 0, 10)))
    pl.enable_anti_aliasing('ssaa')
    pl.enable_depth_of_field()
    pl.add_blurring()
    pl.enable_shadows()
    pl.enable_eye_dome_lighting()


def test_plot_composite_many_options(multiblock_poly):
    # add composite data
    for block in multiblock_poly:
        # use np.uint8 for coverage of non-standard datatypes
        block['data'] = np.arange(block.n_points, dtype=np.uint8)

    pl = pyvista.Plotter()
    pl.add_composite(
        multiblock_poly,
        scalars='data',
        annotations={94: 'foo', 162: 'bar'},
        above_color='k',
        below_color='w',
        clim=[64, 192],
        log_scale=True,
        flip_scalars=True,
        label='my composite',
    )
    pl.add_legend()
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_raise(sphere, multiblock_poly):
    pl = pyvista.Plotter()
    with pytest.raises(TypeError, match='Must be a composite dataset'):
        pl.add_composite(sphere)
    with pytest.raises(TypeError, match='must be a string for'):
        pl.add_composite(multiblock_poly, scalars=range(10))
    with pytest.raises(TypeError, match='must be an int'):
        pl.add_composite(multiblock_poly, categories='abc')


def test_plot_composite_categories(multiblock_poly):
    pl = pyvista.Plotter()
    pl.add_composite(multiblock_poly, scalars='data_b', categories=5)
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_preference_cell(multiblock_poly):
    """Show that we will plot cell data if both point and cell exist in all."""
    # use the first two datasets as the third is missing scalars
    multiblock_poly[:2].plot(preference='cell', before_close_callback=verify_cache_image)


@skip_windows  # because of opacity
def test_plot_composite_poly_scalars_opacity(multiblock_poly):
    pl = pyvista.Plotter()

    actor, mapper = pl.add_composite(
        multiblock_poly,
        scalars='data_a',
        nan_color='green',
        color_missing_with_nan=True,
        smooth_shading=True,
        show_edges=True,
        cmap='bwr',
    )
    mapper.block_attr[1].color = 'blue'
    mapper.block_attr[1].opacity = 0.5

    pl.camera_position = 'xy'

    # 9.0.3 has a bug where VTK changes the edge visibility on blocks that are
    # also opaque. Don't verify the image of that version.
    if pyvista.vtk_version_info == (9, 0, 3):
        pl.show()
    else:
        pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_poly_scalars_cell(multiblock_poly):
    pl = pyvista.Plotter()

    actor, mapper = pl.add_composite(
        multiblock_poly,
        scalars='cell_data',
    )
    mapper.block_attr[1].color = 'blue'

    pl.camera_position = 'xy'
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_poly_no_scalars(multiblock_poly):
    pl = pyvista.Plotter()

    actor, mapper = pl.add_composite(
        multiblock_poly,
        color='red',
        lighting=False,
    )

    # Note: set the camera position before making the blocks invisible to be
    # consistent between 9.0.3 and 9.1+
    #
    # 9.0.3 still considers invisible blocks when determining camera bounds, so
    # there will be some empty space where the invisible block is for 9.0.3,
    # while 9.1.0 ignores invisible blocks when computing camera bounds.
    pl.camera_position = 'xy'
    mapper.block_attr[2].color = 'blue'
    mapper.block_attr[3].visible = False

    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_poly_component_norm(multiblock_poly):
    for ii, block in enumerate(multiblock_poly):
        data = block.compute_normals().point_data['Normals']
        data[:, ii] *= 2
        block['data'] = data

    pl = pyvista.Plotter()
    pl.add_composite(multiblock_poly, scalars='data', cmap='bwr')
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_poly_component_single(multiblock_poly):
    for block in multiblock_poly:
        data = block.compute_normals().point_data['Normals']
        block['data'] = data

    pl = pyvista.Plotter()
    with pytest.raises(ValueError, match='must be nonnegative'):
        pl.add_composite(multiblock_poly, scalars='data', component=-1)
    with pytest.raises(TypeError, match='None or an integer'):
        pl.add_composite(multiblock_poly, scalars='data', component='apple')

    pl.add_composite(multiblock_poly, scalars='data', component=1)
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_poly_component_nested_multiblock(multiblock_poly):
    for block in multiblock_poly:
        data = block.compute_normals().point_data['Normals']
        block['data'] = data

    multiblock_poly2 = multiblock_poly.copy()
    for block in multiblock_poly2:
        block.points += np.array([0, 0, 1])

    multimulti = pyvista.MultiBlock([multiblock_poly, multiblock_poly2])

    pl = pyvista.Plotter()
    pl.add_composite(multimulti, scalars='data', style='points', clim=[0.99, 1.01], copy_mesh=True)
    pl.add_composite(multimulti, scalars='data', component=1, copy_mesh=True)
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_poly_complex(multiblock_poly):
    # add composite data
    for block in multiblock_poly:
        data = np.arange(block.n_points) + np.arange(block.n_points) * 1j
        block['data'] = data

    # make a multi_multi for better coverage
    multi_multi = pyvista.MultiBlock([multiblock_poly, multiblock_poly])

    pl = pyvista.Plotter()
    with pytest.warns(np.ComplexWarning, match='Casting complex'):
        pl.add_composite(multi_multi, scalars='data')
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_rgba(multiblock_poly):
    # add composite data
    for i, block in enumerate(multiblock_poly):
        rgba_value = np.zeros((block.n_points, 3), dtype=np.uint8)
        rgba_value[:, i] = np.linspace(0, 255, block.n_points)
        block['data'] = rgba_value

    pl = pyvista.Plotter()
    with pytest.raises(ValueError, match='3/4 in shape'):
        pl.add_composite(multiblock_poly, scalars='all_data', rgba=True)
    pl.add_composite(multiblock_poly, scalars='data', rgba=True)
    pl.show(before_close_callback=verify_cache_image)


def test_plot_composite_bool(multiblock_poly):
    # add in bool data
    for i, block in enumerate(multiblock_poly):
        block['scalars'] = np.zeros(block.n_points, dtype=bool)
        block['scalars'][::2] = 1

    pl = pyvista.Plotter()
    pl.add_composite(multiblock_poly, scalars='scalars')
    pl.show(before_close_callback=verify_cache_image)


def test_export_obj(tmpdir, sphere):
    filename = str(tmpdir.mkdir("tmpdir").join("tmp.obj"))

    pl = pyvista.Plotter()
    pl.add_mesh(sphere, smooth_shading=True)

    if pyvista.vtk_version_info <= (8, 1, 2):
        with pytest.raises(pyvista.core.errors.VTKVersionError):
            pl.export_obj(filename)
        return

    with pytest.raises(ValueError, match='end with ".obj"'):
        pl.export_obj('badfilename')

    pl.export_obj(filename)

    # Check that the object file has been written
    assert os.path.exists(filename)

    # Check that when we close the plotter, the adequate error is raised
    pl.close()
    with pytest.raises(RuntimeError, match='This plotter must still have a render window open.'):
        pl.export_obj(filename)


def test_multi_plot_scalars():
    res = 5
    plane = pyvista.Plane(j_resolution=res, i_resolution=res)
    plane.clear_data()
    kek = np.arange(res + 1)
    kek = np.tile(kek, (res + 1, 1))
    u = kek.flatten().copy()
    v = kek.T.flatten().copy()

    plane.point_data['u'] = u
    plane.point_data['v'] = v

    pl = pyvista.Plotter(shape=(1, 2))
    pl.subplot(0, 0)
    pl.add_text('"u" point scalars')
    pl.add_mesh(plane, scalars='u', copy_mesh=True)
    pl.subplot(0, 1)
    pl.add_text('"v" point scalars')
    pl.add_mesh(plane, scalars='v', copy_mesh=True)
    pl.show(before_close_callback=verify_cache_image)


def test_bool_scalars(sphere):
    sphere['scalars'] = np.zeros(sphere.n_points, dtype=bool)
    sphere['scalars'][::2] = 1
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    plotter.show(before_close_callback=verify_cache_image)


@skip_windows  # because of pbr
@skip_9_1_0  # pbr required
def test_property():
    prop = pyvista.Property(interpolation='pbr', metallic=1.0)

    # VTK flipped the Z axis for the cubemap between 9.1 and 9.2
    if pyvista.vtk_version_info <= (9, 1):
        prop.plot(before_close_callback=verify_cache_image)
    else:
        prop.plot()


def test_tight_square(noise_2d):
    noise_2d.plot(
        window_size=[800, 200],
        show_scalar_bar=False,
        cpos='xy',
        zoom='tight',
        before_close_callback=verify_cache_image,
    )


@skip_windows_mesa  # due to opacity
def test_plot_cell():
    grid = examples.cells.Tetrahedron()
    examples.plot_cell(grid, before_close_callback=verify_cache_image)


def test_tight_square_padding():
    grid = pyvista.UniformGrid(dims=(200, 100, 1))
    grid['data'] = np.arange(grid.n_points)
    pl = pyvista.Plotter(window_size=(150, 150))
    pl.add_mesh(grid, show_scalar_bar=False)
    pl.camera_position = 'xy'
    pl.camera.tight(padding=0.05)
    # limit to widest dimension
    assert np.allclose(pl.window_size, [150, 75])
    pl.show(before_close_callback=verify_cache_image)


def test_tight_tall():
    grid = pyvista.UniformGrid(dims=(100, 200, 1))
    grid['data'] = np.arange(grid.n_points)
    pl = pyvista.Plotter(window_size=(150, 150))
    pl.add_mesh(grid, show_scalar_bar=False)
    pl.camera_position = 'xy'
    with pytest.raises(ValueError, match='can only be "tight"'):
        pl.camera.zoom('invalid')
    pl.camera.tight()
    # limit to widest dimension
    assert np.allclose(pl.window_size, [75, 150], rtol=1)
    pl.show(before_close_callback=verify_cache_image)


def test_tight_wide():
    grid = pyvista.UniformGrid(dims=(200, 100, 1))
    grid['data'] = np.arange(grid.n_points)
    pl = pyvista.Plotter(window_size=(150, 150))
    pl.add_mesh(grid, show_scalar_bar=False)
    pl.camera_position = 'xy'
    pl.camera.tight()
    # limit to widest dimension
    assert np.allclose(pl.window_size, [150, 75])
    pl.show(before_close_callback=verify_cache_image)


def test_remove_bounds_axes(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    actor = pl.show_bounds(grid='front', location='outer')
    assert isinstance(actor, vtk.vtkActor)
    pl.remove_bounds_axes()
    pl.show(before_close_callback=verify_cache_image)


@skip_9_1_0
def test_charts_sin():
    x = np.linspace(0, 2 * np.pi, 20)
    y = np.sin(x)
    chart = pyvista.Chart2D()
    chart.scatter(x, y)
    chart.line(x, y, 'r')
    chart.show(dev_kwargs={'before_close_callback': verify_cache_image})


def test_plot_actor(sphere):
    pl = pyvista.Plotter()
    actor = pl.add_mesh(sphere, lighting=False, color='b', show_edges=True)
    actor.plot(before_close_callback=verify_cache_image)


def test_wireframe_color(sphere):
    sphere.plot(
        lighting=False, color='b', style='wireframe', before_close_callback=verify_cache_image
    )


# tests/plotting/test_widgets.py

import numpy as np
import pytest

import pyvista
from pyvista.plotting import system_supports_plotting

# skip all tests if unable to render
if not system_supports_plotting():
    pytestmark = pytest.mark.skip


def test_widget_box(uniform):
    p = pyvista.Plotter()
    func = lambda box: box  # Does nothing
    p.add_mesh(uniform)
    p.add_box_widget(callback=func)
    p.close()

    p = pyvista.Plotter()
    func = lambda box, widget: box  # Does nothing
    p.add_mesh(uniform)
    p.add_box_widget(callback=func, pass_widget=True)
    p.close()

    # clip box with and without crinkle
    p = pyvista.Plotter()
    p.add_mesh_clip_box(uniform)
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_clip_box(uniform, crinkle=True)
    p.close()

    p = pyvista.Plotter()
    # merge_points=True is the default and is tested above
    p.add_mesh_clip_box(uniform, merge_points=False)
    p.close()


def test_widget_plane(uniform):
    p = pyvista.Plotter()
    func = lambda normal, origin: normal  # Does nothing
    p.add_mesh(uniform)
    p.add_plane_widget(callback=func, implicit=True)
    p.close()

    p = pyvista.Plotter()
    func = lambda normal, origin, widget: normal  # Does nothing
    p.add_mesh(uniform)
    p.add_plane_widget(callback=func, pass_widget=True, implicit=True)
    p.close()

    p = pyvista.Plotter()
    func = lambda normal, origin: normal  # Does nothing
    p.add_mesh(uniform)
    p.add_plane_widget(callback=func, implicit=False)
    p.close()

    p = pyvista.Plotter()
    func = lambda normal, origin, widget: normal  # Does nothing
    p.add_mesh(uniform)
    p.add_plane_widget(callback=func, pass_widget=True, implicit=False)
    p.close()

    p = pyvista.Plotter()
    func = lambda normal, origin: normal  # Does nothing
    p.add_mesh(uniform)
    p.add_plane_widget(callback=func, assign_to_axis='z', implicit=True)
    p.close()

    p = pyvista.Plotter()
    func = lambda normal, origin: normal  # Does nothing
    p.add_mesh(uniform)
    p.add_plane_widget(callback=func, normal_rotation=False, implicit=False)
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_clip_plane(uniform)
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_clip_plane(uniform, crinkle=True)
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_slice(uniform)
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_slice_orthogonal(uniform)
    p.close()


def test_widget_line(uniform):
    p = pyvista.Plotter()
    func = lambda line: line  # Does nothing
    p.add_mesh(uniform)
    p.add_line_widget(callback=func)
    p.close()

    p = pyvista.Plotter()
    func = lambda line, widget: line  # Does nothing
    p.add_mesh(uniform)
    p.add_line_widget(callback=func, pass_widget=True)
    p.close()

    p = pyvista.Plotter()
    func = lambda a, b: (a, b)  # Does nothing
    p.add_mesh(uniform)
    p.add_line_widget(callback=func, use_vertices=True)
    p.close()


def test_widget_text_slider(uniform):
    p = pyvista.Plotter()
    func = lambda value: value  # Does nothing
    p.add_mesh(uniform)
    with pytest.raises(TypeError, match='must be a list'):
        p.add_text_slider_widget(callback=func, data='foo')
    with pytest.raises(ValueError, match='list of values is empty'):
        p.add_text_slider_widget(callback=func, data=[])
    for style in pyvista.global_theme.slider_styles:
        p.add_text_slider_widget(callback=func, data=['foo', 'bar'], style=style)
    p.close()


def test_widget_slider(uniform):
    p = pyvista.Plotter()
    func = lambda value: value  # Does nothing
    p.add_mesh(uniform)
    p.add_slider_widget(callback=func, rng=[0, 10], style="classic")
    p.close()

    p = pyvista.Plotter()
    for event_type in ['start', 'end', 'always']:
        p.add_slider_widget(callback=func, rng=[0, 10], event_type=event_type)
    with pytest.raises(TypeError, match='type for ``style``'):
        p.add_slider_widget(callback=func, rng=[0, 10], style=0)
    with pytest.raises(AttributeError):
        p.add_slider_widget(callback=func, rng=[0, 10], style="foo")
    with pytest.raises(TypeError, match='type for `event_type`'):
        p.add_slider_widget(callback=func, rng=[0, 10], event_type=0)
    with pytest.raises(ValueError, match='value for `event_type`'):
        p.add_slider_widget(callback=func, rng=[0, 10], event_type='foo')
    p.close()

    p = pyvista.Plotter()
    func = lambda value, widget: value  # Does nothing
    p.add_mesh(uniform)
    p.add_slider_widget(callback=func, rng=[0, 10], style="modern", pass_widget=True)
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_threshold(uniform, invert=True)
    p.add_mesh(uniform.outline())
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_threshold(uniform, invert=False)
    p.add_mesh(uniform.outline())
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_isovalue(uniform)
    p.close()

    p = pyvista.Plotter()
    title_height = np.random.random()
    s = p.add_slider_widget(callback=func, rng=[0, 10], style="classic", title_height=title_height)
    assert s.GetRepresentation().GetTitleHeight() == title_height
    p.close()

    p = pyvista.Plotter()
    title_opacity = np.random.random()
    s = p.add_slider_widget(
        callback=func, rng=[0, 10], style="classic", title_opacity=title_opacity
    )
    assert s.GetRepresentation().GetTitleProperty().GetOpacity() == title_opacity
    p.close()

    p = pyvista.Plotter()
    title_color = "red"
    s = p.add_slider_widget(callback=func, rng=[0, 10], style="classic", title_color=title_color)
    assert s.GetRepresentation().GetTitleProperty().GetColor() == pyvista.Color(title_color)
    p.close()

    p = pyvista.Plotter()
    fmt = "%0.9f"
    s = p.add_slider_widget(callback=func, rng=[0, 10], style="classic", fmt=fmt)
    assert s.GetRepresentation().GetLabelFormat() == fmt
    p.close()


def test_widget_spline(uniform):
    p = pyvista.Plotter()
    func = lambda spline: spline  # Does nothing
    p.add_mesh(uniform)
    p.add_spline_widget(callback=func)
    p.close()

    p = pyvista.Plotter()
    func = lambda spline, widget: spline  # Does nothing
    p.add_mesh(uniform)
    p.add_spline_widget(callback=func, pass_widget=True, color=None, show_ribbon=True)
    p.close()

    p = pyvista.Plotter()
    p.add_mesh_slice_spline(uniform)
    p.close()


def test_widget_uniform(uniform):
    p = pyvista.Plotter()
    func = lambda center: center  # Does nothing
    p.add_sphere_widget(callback=func, center=(0, 0, 0))
    p.close()

    nodes = np.array([[-1, -1, -1], [1, 1, 1]])
    p = pyvista.Plotter()
    func = lambda center: center  # Does nothing
    p.add_sphere_widget(callback=func, center=nodes)
    p.close()


def test_widget_checkbox_button(uniform):
    p = pyvista.Plotter()
    func = lambda value: value  # Does nothing
    p.add_mesh(uniform)
    p.add_checkbox_button_widget(callback=func)
    p.close()


@pytest.mark.needs_vtk_version(9, 1)
def test_add_camera_orientation_widget(uniform):
    p = pyvista.Plotter()
    p.add_camera_orientation_widget()
    assert p.camera_widgets
    p.close()
    assert not p.camera_widgets


# tests/tinypages/conf.py

import datetime
import os
import re
import sys

from packaging.version import parse as parse_version
import sphinx

import pyvista

sys.path.append(os.path.join(os.path.dirname(__file__)))

# -- General configuration ------------------------------------------------

extensions = ['pyvista.ext.plot_directive']
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
project = 'tinypages'
year = datetime.date.today().year
copyright = f"2021-{year}, PyVista developers"
version = '0.1'
release = '0.1'
exclude_patterns = ['_build']
pygments_style = 'sphinx'

extensions = [
    "numpydoc",
    "pyvista.ext.plot_directive",
    "sphinx.ext.autosummary",
]

# -- Plot directive specific configuration --------------------------------
plot_setup = plot_cleanup = 'import pyvista'

# -- Options for HTML output ----------------------------------------------

if parse_version(sphinx.__version__) >= parse_version('1.3'):
    html_theme = 'classic'
else:
    html_theme = 'default'

html_static_path = ['_static']

# -- pyvista configuration ------------------------------------------------
pyvista.BUILDING_GALLERY = True

# -- .. pyvista-plot:: directive ----------------------------------------------
from numpydoc.docscrape_sphinx import SphinxDocString

IMPORT_PYVISTA_RE = r'\b(import +pyvista|from +pyvista +import)\b'
IMPORT_MATPLOTLIB_RE = r'\b(import +matplotlib|from +matplotlib +import)\b'

plot_setup = """
from pyvista import set_plot_theme as __s_p_t
__s_p_t('document')
del __s_p_t
"""
plot_cleanup = plot_setup


def _str_examples(self):
    examples_str = "\n".join(self['Examples'])

    if (
        self.use_plots
        and re.search(IMPORT_MATPLOTLIB_RE, examples_str)
        and 'plot::' not in examples_str
    ):
        out = []
        out += self._str_header('Examples')
        out += ['.. plot::', '']
        out += self._str_indent(self['Examples'])
        out += ['']
        return out
    elif re.search(IMPORT_PYVISTA_RE, examples_str) and 'plot-pyvista::' not in examples_str:
        out = []
        out += self._str_header('Examples')
        out += ['.. pyvista-plot::', '']
        out += self._str_indent(self['Examples'])
        out += ['']
        return out
    else:
        return self._str_section('Examples')


SphinxDocString._str_examples = _str_examples


# tests/tinypages/plot_cone.py

import pyvista

pyvista.Cone().plot()


# tests/tinypages/plot_polygon.py

import pyvista


def not_called():
    """Never called if plot_directive works as expected."""
    raise NotImplementedError


def plot_poly():
    """The function that should be executed."""
    pyvista.Polygon().plot()


# tests/tinypages/samples.py

import pyvista as pv


def make_sphere():
    """Make a sphere.

    Test that the pyvista-plot directive works correctly when there is
    something to plot.

    Examples
    --------
    >>> import pyvista  # must import pyvista for the plotting directive to work
    >>> from samples import make_sphere
    >>> sphere = make_sphere()
    >>> sphere.plot()

    """
    return pv.Sphere()


def example_with_empty_plotter():
    """Do not do anything.

    Test that the pyvista-plot directive works correctly when there is
    nothing to plot and the plotter has been created but not shown.

    Examples
    --------
    >>> import pyvista
    >>> pl = pyvista.Plotter()

    """
    return


def example_with_closed_plotter():
    """Do not do anything.

    Test that the pyvista-plot directive works correctly when there is
    nothing to plot and the plotter has been created but not shown.

    Examples
    --------
    >>> import pyvista
    >>> pl = pyvista.Plotter()
    >>> pl.close()

    """
    return


# tests/utilities/test_common.py

""" Tests for pyvista.utilities.common."""

import numpy as np
import pytest

from pyvista.utilities.arrays import _coerce_pointslike_arg


def test_coerce_point_like_arg():
    # Test with Sequence
    point = [1.0, 2.0, 3.0]
    coerced_arg, singular = _coerce_pointslike_arg(point)
    assert isinstance(coerced_arg, np.ndarray)
    assert coerced_arg.shape == (1, 3)
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0]]))
    assert singular

    # Test with 1D np.ndarray
    point = np.array([1.0, 2.0, 3.0])
    coerced_arg, singular = _coerce_pointslike_arg(point)
    assert isinstance(coerced_arg, np.ndarray)
    assert coerced_arg.shape == (1, 3)
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0]]))
    assert singular

    # Test with (1, 3) np.ndarray
    point = np.array([[1.0, 2.0, 3.0]])
    coerced_arg, singular = _coerce_pointslike_arg(point)
    assert isinstance(coerced_arg, np.ndarray)
    assert coerced_arg.shape == (1, 3)
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0]]))
    assert not singular

    # Test with 2D ndarray
    point = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
    coerced_arg, singular = _coerce_pointslike_arg(point)
    assert isinstance(coerced_arg, np.ndarray)
    assert coerced_arg.shape == (2, 3)
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]))
    assert not singular


def test_coerce_point_like_arg_copy():
    # Sequence is always copied
    point = [1.0, 2.0, 3.0]
    coerced_arg, _ = _coerce_pointslike_arg(point, copy=True)
    point[0] = 10.0
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0]]))

    point = [1.0, 2.0, 3.0]
    coerced_arg, _ = _coerce_pointslike_arg(point, copy=False)
    point[0] = 10.0
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0]]))

    # 1D np.ndarray can be copied or not
    point = np.array([1.0, 2.0, 3.0])
    coerced_arg, _ = _coerce_pointslike_arg(point, copy=True)
    point[0] = 10.0
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0]]))

    point = np.array([1.0, 2.0, 3.0])
    coerced_arg, _ = _coerce_pointslike_arg(point, copy=False)
    point[0] = 10.0
    assert np.array_equal(coerced_arg, np.array([[10.0, 2.0, 3.0]]))

    # 2D np.ndarray can be copied or not
    point = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
    coerced_arg, _ = _coerce_pointslike_arg(point, copy=True)
    point[0, 0] = 10.0
    assert np.array_equal(coerced_arg, np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]))

    point = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
    coerced_arg, _ = _coerce_pointslike_arg(point, copy=False)
    point[0, 0] = 10.0
    assert np.array_equal(coerced_arg, np.array([[10.0, 2.0, 3.0], [4.0, 5.0, 6.0]]))


def test_coerce_point_like_arg_errors():
    # wrong length sequence
    with pytest.raises(ValueError):
        _coerce_pointslike_arg([1, 2])

    # wrong type
    with pytest.raises(TypeError):
        # allow Sequence but not Iterable
        _coerce_pointslike_arg({1, 2, 3})

    # wrong length ndarray
    with pytest.raises(ValueError):
        _coerce_pointslike_arg(np.empty(4))
    with pytest.raises(ValueError):
        _coerce_pointslike_arg(np.empty([2, 4]))

    # wrong ndim ndarray
    with pytest.raises(ValueError):
        _coerce_pointslike_arg(np.empty([1, 3, 3]))


def test_coerce_points_like_args_does_not_copy():
    source = np.random.rand(100, 3)
    output, _ = _coerce_pointslike_arg(source)  # test that copy=False is default
    output /= 2
    assert np.array_equal(output, source)
    assert np.may_share_memory(output, source)


# tests/utilities/test_misc.py

import os

import pytest

from pyvista.utilities.misc import _set_plot_theme_from_env, has_module


def test_set_plot_theme_from_env():
    os.environ['PYVISTA_PLOT_THEME'] = 'not a valid theme'
    try:
        with pytest.warns(UserWarning, match='Invalid'):
            _set_plot_theme_from_env()
    finally:
        os.environ.pop('PYVISTA_PLOT_THEME', None)


def test_has_module():
    assert has_module('pytest')
    assert not has_module('not_a_module')


# tests/utilities/test_reader.py

import os
import platform

from PIL import Image, ImageSequence
import numpy as np
import pytest

import pyvista
from pyvista import examples
from pyvista.examples.downloads import _download_file

pytestmark = pytest.mark.skipif(
    platform.system() == 'Darwin', reason='MacOS testing on Azure fails when downloading'
)


@pytest.fixture()
def gif_file(tmpdir):
    filename = str(tmpdir.join('sample.gif'))

    pl = pyvista.Plotter(window_size=(300, 200))
    pl.open_gif(filename, palettesize=16, fps=1)

    mesh = pyvista.Sphere()
    opacity = mesh.points[:, 0]
    opacity -= opacity.min()
    opacity /= opacity.max()
    for color in ['red', 'blue', 'green']:
        pl.clear()
        pl.background_color = 'w'
        pl.add_mesh(mesh, color=color, opacity=opacity)
        pl.camera_position = 'xy'
        pl.write_frame()

    pl.close()
    return filename


def test_get_reader_fail():
    with pytest.raises(ValueError):
        pyvista.get_reader("not_a_supported_file.no_data")


def test_reader_invalid_file():
    # cannot use the BaseReader
    with pytest.raises(FileNotFoundError, match='does not exist'):
        pyvista.DICOMReader('dummy/')


def test_xmlimagedatareader(tmpdir):
    tmpfile = tmpdir.join("temp.vti")
    mesh = pyvista.UniformGrid()
    mesh.save(tmpfile.strpath)

    reader = pyvista.get_reader(tmpfile.strpath)
    assert reader.path == tmpfile.strpath
    new_mesh = reader.read()
    assert isinstance(new_mesh, pyvista.UniformGrid)
    assert new_mesh.n_points == mesh.n_points
    assert new_mesh.n_cells == mesh.n_cells


def test_xmlrectilineargridreader(tmpdir):
    tmpfile = tmpdir.join("temp.vtr")
    mesh = pyvista.RectilinearGrid()
    mesh.save(tmpfile.strpath)

    reader = pyvista.get_reader(tmpfile.strpath)
    assert reader.path == tmpfile.strpath
    new_mesh = reader.read()
    assert isinstance(new_mesh, pyvista.RectilinearGrid)
    assert new_mesh.n_points == mesh.n_points
    assert new_mesh.n_cells == mesh.n_cells


def test_xmlunstructuredgridreader(tmpdir):
    tmpfile = tmpdir.join("temp.vtu")
    mesh = pyvista.UnstructuredGrid()
    mesh.save(tmpfile.strpath)

    reader = pyvista.get_reader(tmpfile.strpath)
    assert reader.path == tmpfile.strpath
    new_mesh = reader.read()
    assert isinstance(new_mesh, pyvista.UnstructuredGrid)
    assert new_mesh.n_points == mesh.n_points
    assert new_mesh.n_cells == mesh.n_cells


def test_xmlpolydatareader(tmpdir):
    tmpfile = tmpdir.join("temp.vtp")
    mesh = pyvista.Sphere()
    mesh.save(tmpfile.strpath)

    reader = pyvista.get_reader(tmpfile.strpath)
    assert reader.path == tmpfile.strpath
    new_mesh = reader.read()
    assert isinstance(new_mesh, pyvista.PolyData)
    assert new_mesh.n_points == mesh.n_points
    assert new_mesh.n_cells == mesh.n_cells


def test_xmlstructuredgridreader(tmpdir):
    tmpfile = tmpdir.join("temp.vts")
    mesh = pyvista.StructuredGrid()
    mesh.save(tmpfile.strpath)

    reader = pyvista.get_reader(tmpfile.strpath)
    assert reader.path == tmpfile.strpath
    new_mesh = reader.read()
    assert isinstance(new_mesh, pyvista.StructuredGrid)
    assert new_mesh.n_points == mesh.n_points
    assert new_mesh.n_cells == mesh.n_cells


def test_xmlmultiblockreader(tmpdir):
    tmpfile = tmpdir.join("temp.vtm")
    mesh = pyvista.MultiBlock([pyvista.Sphere() for i in range(5)])
    mesh.save(tmpfile.strpath)

    reader = pyvista.get_reader(tmpfile.strpath)
    assert reader.path == tmpfile.strpath
    new_mesh = reader.read()
    assert isinstance(new_mesh, pyvista.MultiBlock)
    assert new_mesh.n_blocks == mesh.n_blocks
    for i in range(new_mesh.n_blocks):
        assert new_mesh[i].n_points == mesh[i].n_points
        assert new_mesh[i].n_cells == mesh[i].n_cells


def test_reader_cell_point_data(tmpdir):
    tmpfile = tmpdir.join("temp.vtp")
    mesh = pyvista.Sphere()
    mesh['height'] = mesh.points[:, 1]
    mesh['id'] = np.arange(mesh.n_cells)
    mesh.save(tmpfile.strpath)
    # mesh has an additional 'Normals' point data array

    reader = pyvista.get_reader(tmpfile.strpath)

    assert reader.number_cell_arrays == 1
    assert reader.number_point_arrays == 2

    assert reader.cell_array_names == ['id']
    assert reader.point_array_names == ['Normals', 'height']

    assert reader.all_cell_arrays_status == {'id': True}
    assert reader.all_point_arrays_status == {'Normals': True, 'height': True}

    assert reader.cell_array_status('id') is True
    assert reader.point_array_status('Normals') is True

    reader.disable_all_cell_arrays()
    assert reader.cell_array_status('id') is False

    reader.disable_all_point_arrays()
    assert reader.all_point_arrays_status == {'Normals': False, 'height': False}

    reader.enable_all_cell_arrays()
    assert reader.cell_array_status('id') is True

    reader.enable_all_point_arrays()
    assert reader.all_point_arrays_status == {'Normals': True, 'height': True}

    reader.disable_cell_array('id')
    assert reader.cell_array_status('id') is False

    reader.disable_point_array('Normals')
    assert reader.point_array_status('Normals') is False

    reader.enable_cell_array('id')
    assert reader.cell_array_status('id') is True

    reader.enable_point_array('Normals')
    assert reader.point_array_status('Normals') is True


def test_ensightreader_arrays():
    filename = examples.download_backward_facing_step(load=False)

    reader = pyvista.get_reader(filename)
    assert reader.path == filename
    assert reader.number_cell_arrays == 9
    assert reader.number_point_arrays == 0

    assert reader.cell_array_names == [
        'v2',
        'nut',
        'k',
        'nuTilda',
        'p',
        'omega',
        'f',
        'epsilon',
        'U',
    ]
    assert reader.point_array_names == []

    reader.disable_all_cell_arrays()
    reader.enable_cell_array('k')

    assert reader.all_cell_arrays_status == {
        'v2': False,
        'nut': False,
        'k': True,
        'nuTilda': False,
        'p': False,
        'omega': False,
        'f': False,
        'epsilon': False,
        'U': False,
    }

    mesh = reader.read()
    assert isinstance(mesh, pyvista.MultiBlock)

    for i in range(mesh.n_blocks):
        assert all([mesh[i].n_points, mesh[i].n_cells])
        assert mesh[i].array_names == ['k']

    # re-enable all cell arrays and read again
    reader.enable_all_cell_arrays()
    all_mesh = reader.read()
    assert isinstance(all_mesh, pyvista.MultiBlock)

    for i in range(all_mesh.n_blocks):
        assert all([all_mesh[i].n_points, all_mesh[i].n_cells])
        assert all_mesh[i].array_names == [
            'v2',
            'nut',
            'k',
            'nuTilda',
            'p',
            'omega',
            'f',
            'epsilon',
            'U',
        ]


def test_ensightreader_timepoints():
    filename = examples.download_naca(load=False)

    reader = pyvista.get_reader(filename)
    assert reader.path == filename

    assert reader.number_time_points == 2
    assert reader.time_values == [1.0, 3.0]
    assert reader.time_point_value(0) == 1.0
    assert reader.time_point_value(1) == 3.0

    assert reader.active_time_value == 1.0
    mesh_1 = reader.read()

    reader.set_active_time_value(3.0)
    assert reader.active_time_value == 3.0
    mesh_3 = reader.read()

    # assert all the data is different
    for m_1, m_3 in zip(mesh_1, mesh_3):
        assert not all(m_1['DENS'] == m_3['DENS'])

    reader.set_active_time_point(0)
    assert reader.active_time_value == 1.0


def test_dcmreader(tmpdir):
    # Test reading directory (image stack)
    directory = examples.download_dicom_stack(load=False)
    reader = pyvista.DICOMReader(directory)  # ``get_reader`` doesn't support directories
    assert directory in str(reader)
    assert isinstance(reader, pyvista.DICOMReader)
    assert reader.path == directory

    mesh = reader.read()
    assert isinstance(mesh, pyvista.UniformGrid)
    assert all([mesh.n_points, mesh.n_cells])

    # Test reading single file (*.dcm)
    filename = os.path.join(directory, "1-1.dcm")
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.DICOMReader)
    assert reader.path == filename

    mesh = reader.read()
    assert isinstance(mesh, pyvista.UniformGrid)
    assert all([mesh.n_points, mesh.n_cells])


def test_plyreader():
    filename = examples.spherefile
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.PLYReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_objreader():
    filename = examples.download_doorman(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.OBJReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_stlreader():
    filename = examples.download_gears(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.STLReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_tecplotreader():
    filename = examples.download_tecplot_ascii(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.TecplotReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh[0].n_points, mesh[0].n_cells])


def test_vtkreader():
    filename = examples.hexbeamfile
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.VTKDataSetReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_byureader():
    filename = examples.download_teapot(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.BYUReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_facetreader():
    filename = examples.download_clown(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.FacetReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_plot3dmetareader():
    filename, _ = _download_file('multi.p3d')
    _download_file('multi-bin.xyz')
    _download_file('multi-bin.q')
    _download_file('multi-bin.f')
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.Plot3DMetaReader)
    assert reader.path == filename

    mesh = reader.read()
    for m in mesh:
        assert all([m.n_points, m.n_cells])


def test_multiblockplot3dreader():
    filename, _ = _download_file('multi-bin.xyz')
    q_filename, _ = _download_file('multi-bin.q')
    reader = pyvista.MultiBlockPlot3DReader(filename)
    assert reader.path == filename

    mesh = reader.read()
    for m in mesh:
        assert all([m.n_points, m.n_cells])
        assert len(m.array_names) == 0

    # Reader doesn't yet support reusability
    reader = pyvista.MultiBlockPlot3DReader(filename)
    reader.add_q_files(q_filename)

    reader.add_function(112)  # add by int
    reader.add_function(pyvista.reader.Plot3DFunctionEnum.PRESSURE_GRADIENT)  # add by enum
    reader.add_function(reader.KINETIC_ENERGY)  # add by class variable (alias to enum value)
    reader.add_function(reader.ENTROPY)  # add ENTROPY by class variable
    reader.remove_function(170)  # remove ENTROPY by int

    reader.add_function(reader.ENTROPY)
    reader.remove_function(reader.ENTROPY)  # remove by class variable

    mesh = reader.read()
    for m in mesh:
        assert len(m.array_names) > 0

    assert 'MachNumber' in mesh[0].point_data
    assert 'PressureGradient' in mesh[0].point_data
    assert 'KineticEnergy' in mesh[0].point_data
    assert 'Entropy' not in mesh[0].point_data

    reader = pyvista.MultiBlockPlot3DReader(filename)
    reader.add_q_files([q_filename])
    mesh = reader.read()
    for m in mesh:
        assert len(m.array_names) > 0

    reader = pyvista.MultiBlockPlot3DReader(filename)

    # get/set of `auto_detect_format`
    reader.auto_detect_format = False
    assert reader.auto_detect_format is False
    reader.auto_detect_format = True
    assert reader.auto_detect_format is True

    # get/set of `preserve_intermediate_functions`
    reader.preserve_intermediate_functions = False
    assert reader.preserve_intermediate_functions is False
    reader.preserve_intermediate_functions = True
    assert reader.preserve_intermediate_functions is True

    # get/set of `gamma`
    reader.gamma = 1.5
    assert reader.gamma == 1.5
    reader.gamma = 99
    assert reader.gamma == 99

    # get/set of `r_gas_constant`
    reader.r_gas_constant = 5
    assert reader.r_gas_constant == 5
    reader.r_gas_constant = 10
    assert reader.r_gas_constant == 10

    # check removing all functions
    reader = pyvista.MultiBlockPlot3DReader(filename)
    reader.add_q_files(q_filename)
    reader.add_function(reader.ENTROPY)
    reader.remove_all_functions()
    mesh_no_functions = reader.read()
    assert 'ENTROPY' not in mesh_no_functions[0].point_data


def test_binarymarchingcubesreader():
    filename = examples.download_pine_roots(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.BinaryMarchingCubesReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_pvdreader():
    filename = examples.download_wavy(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.PVDReader)
    assert reader.reader == reader  # PVDReader refers to itself
    assert reader.path == filename

    assert reader.number_time_points == 15
    assert reader.time_point_value(1) == 1.0
    assert np.array_equal(reader.time_values, np.arange(0, 15, dtype=np.float))

    assert reader.active_time_value == reader.time_values[0]

    active_datasets = reader.active_datasets
    assert len(active_datasets) == 1
    active_dataset0 = active_datasets[0]
    assert active_dataset0.time == 0.0
    assert active_dataset0.path == "wavy/wavy00.vts"
    assert active_dataset0.group == ""
    assert active_dataset0.part == 0

    assert len(reader.datasets) == len(reader.time_values)

    active_readers = reader.active_readers
    assert len(active_readers) == 1
    active_reader = active_readers[0]
    assert isinstance(active_reader, pyvista.XMLStructuredGridReader)

    reader.set_active_time_value(1.0)
    assert reader.active_time_value == 1.0

    reader.set_active_time_point(2)
    assert reader.active_time_value == 2.0

    mesh = reader.read()
    assert isinstance(mesh, pyvista.MultiBlock)
    assert len(mesh) == 1
    assert isinstance(mesh[0], pyvista.StructuredGrid)


def test_pvdreader_no_time_group():
    filename = examples.download_dual_sphere_animation(load=False)  # download all the files
    # Use a pvd file that has no timestep or group and two parts.
    filename = os.path.join(os.path.dirname(filename), 'dualSphereNoTime.pvd')

    reader = pyvista.PVDReader(filename)
    assert reader.time_values == [0.0]
    assert reader.active_time_value == 0.0

    assert len(reader.active_datasets) == 2
    for i, dataset in enumerate(reader.active_datasets):
        assert dataset.time == 0.0
        assert dataset.group is None
        assert dataset.part == i


def get_cavity_reader():
    filename = examples.download_cavity(load=False)
    return pyvista.get_reader(filename)


def test_openfoamreader_arrays_time():
    reader = get_cavity_reader()
    assert isinstance(reader, pyvista.OpenFOAMReader)

    assert reader.number_point_arrays == 0
    assert reader.number_cell_arrays == 2

    assert reader.number_time_points == 6
    assert reader.time_values == [0.0, 0.5, 1.0, 1.5, 2.0, 2.5]


def test_openfoamreader_active_time():
    # vtk < 9.1.0 does not support
    if pyvista.vtk_version_info < (9, 1, 0):
        pytest.xfail("OpenFOAMReader GetTimeValue missing on vtk<9.1.0")

    reader = get_cavity_reader()
    assert reader.active_time_value == 0.0
    reader.set_active_time_point(1)
    assert reader.active_time_value == 0.5
    reader.set_active_time_value(1.0)
    assert reader.active_time_value == 1.0

    with pytest.raises(
        ValueError, match=r'Not a valid .* time values: \[0.0, 0.5, 1.0, 1.5, 2.0, 2.5\]'
    ):
        reader.set_active_time_value(1000)


def test_openfoamreader_read_data_time_value():
    reader = get_cavity_reader()

    reader.set_active_time_value(0.0)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 0.0, 0.0, 1e-10)

    reader.set_active_time_value(0.5)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.524879113887437e-05, 0.0, 1e-10)

    reader.set_active_time_value(1.0)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.5253094867803156e-05, 0.0, 1e-10)

    reader.set_active_time_value(1.5)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.525657641352154e-05, 0.0, 1e-10)

    reader.set_active_time_value(2.0)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.5258551836013794e-05, 0.0, 1e-10)

    reader.set_active_time_value(2.5)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.525951953837648e-05, 0.0, 1e-10)


def test_openfoamreader_read_data_time_point():
    reader = get_cavity_reader()

    reader.set_active_time_point(0)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 0.0, 0.0, 1e-10)

    reader.set_active_time_point(1)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.524879113887437e-05, 0.0, 1e-10)

    reader.set_active_time_point(2)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.5253094867803156e-05, 0.0, 1e-10)

    reader.set_active_time_point(3)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.525657641352154e-05, 0.0, 1e-10)

    reader.set_active_time_point(4)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.5258551836013794e-05, 0.0, 1e-10)

    reader.set_active_time_point(5)
    data = reader.read()["internalMesh"]
    assert np.isclose(data.cell_data["U"][:, 1].mean(), 4.525951953837648e-05, 0.0, 1e-10)


def test_openfoam_decompose_polyhedra():
    reader = get_cavity_reader()
    reader.decompose_polyhedra = False
    assert reader.decompose_polyhedra is False
    reader.decompose_polyhedra = True
    assert reader.decompose_polyhedra is True


def test_openfoam_skip_zero_time():
    reader = get_cavity_reader()

    reader.skip_zero_time = True
    assert reader.skip_zero_time is True
    assert 0.0 not in reader.time_values

    # Test from 'True' to 'False'
    reader.skip_zero_time = False
    assert reader.skip_zero_time is False
    assert 0.0 in reader.time_values

    # Test from 'False' to 'True'
    reader.skip_zero_time = True
    assert reader.skip_zero_time is True
    assert 0.0 not in reader.time_values


def test_openfoam_cell_to_point_default():
    reader = get_cavity_reader()
    mesh = reader.read()
    assert reader.cell_to_point_creation is True
    assert mesh[0].n_arrays == 4

    reader = get_cavity_reader()
    reader.cell_to_point_creation = False
    assert reader.cell_to_point_creation is False
    mesh = reader.read()
    assert mesh[0].n_arrays == 2

    reader = get_cavity_reader()
    mesh = reader.read()
    reader.cell_to_point_creation = True
    assert reader.cell_to_point_creation is True
    assert mesh[0].n_arrays == 4


def test_openfoam_patch_arrays():
    # vtk version 9.1.0 changed the way patch names are handled.
    vtk_version = pyvista.vtk_version_info
    if vtk_version >= (9, 1, 0):
        patch_array_key = 'boundary'
        reader_patch_prefix = 'patch/'
    else:
        patch_array_key = 'Patches'
        reader_patch_prefix = ''

    reader = get_cavity_reader()
    assert reader.number_patch_arrays == 4
    assert reader.patch_array_names == [
        'internalMesh',
        f'{reader_patch_prefix}movingWall',
        f'{reader_patch_prefix}fixedWalls',
        f'{reader_patch_prefix}frontAndBack',
    ]
    assert reader.all_patch_arrays_status == {
        'internalMesh': True,
        f'{reader_patch_prefix}movingWall': True,
        f'{reader_patch_prefix}fixedWalls': True,
        f'{reader_patch_prefix}frontAndBack': True,
    }

    # first only read in 'internalMesh'
    for patch_array in reader.patch_array_names[1:]:
        reader.disable_patch_array(patch_array)
    assert reader.all_patch_arrays_status == {
        'internalMesh': True,
        f'{reader_patch_prefix}movingWall': False,
        f'{reader_patch_prefix}fixedWalls': False,
        f'{reader_patch_prefix}frontAndBack': False,
    }
    mesh = reader.read()
    assert mesh.n_blocks == 1
    assert patch_array_key not in mesh.keys()

    # now read in one more patch
    reader = get_cavity_reader()
    reader.disable_all_patch_arrays()
    reader.enable_patch_array('internalMesh')
    reader.enable_patch_array(f'{reader_patch_prefix}fixedWalls')
    mesh = reader.read()
    assert mesh.n_blocks == 2
    assert patch_array_key in mesh.keys()
    assert mesh[patch_array_key].keys() == ['fixedWalls']

    # check multiple patch arrays without 'internalMesh'
    reader = get_cavity_reader()
    reader.disable_patch_array('internalMesh')
    mesh = reader.read()
    assert mesh.n_blocks == 1
    assert patch_array_key in mesh.keys()
    assert mesh[patch_array_key].keys() == ['movingWall', 'fixedWalls', 'frontAndBack']


def test_openfoam_case_type():
    reader = get_cavity_reader()
    reader.case_type = 'decomposed'
    assert reader.case_type == 'decomposed'
    reader.case_type = 'reconstructed'
    assert reader.case_type == 'reconstructed'
    with pytest.raises(ValueError, match="Unknown case type 'wrong_value'."):
        reader.case_type = 'wrong_value'


@pytest.mark.needs_vtk_version(9, 1)
def test_read_cgns():
    filename = examples.download_cgns_structured(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.CGNSReader)
    assert "CGNS" in str(reader)
    reader.show_progress()
    assert reader._progress_bar is True

    assert reader.distribute_blocks in [True, False]  # don't insist on a VTK default
    reader.distribute_blocks = True
    assert reader.distribute_blocks
    reader.disable_all_bases()
    reader.disable_all_families()
    reader.disable_all_cell_arrays()
    reader.disable_all_point_arrays()
    reader.load_boundary_patch = False
    empty_block = reader.read()
    assert len(empty_block) == 0

    reader.enable_all_bases()
    reader.enable_all_families()
    reader.enable_all_cell_arrays()
    reader.enable_all_point_arrays()

    # check can modify unsteady_pattern
    orig = reader.unsteady_pattern
    reader.unsteady_pattern = not orig
    assert reader.unsteady_pattern is not orig

    # check can modify vector_3d
    orig = reader.vector_3d
    reader.vector_3d = not orig
    assert reader.vector_3d is not orig

    # check can boundary_patch accessible
    assert reader.load_boundary_patch in [True, False]

    reader.hide_progress()
    assert reader._progress_bar is False

    block = reader.read()
    assert len(block[0]) == 12

    # actual block
    assert len(block[0][0].cell_data) == 3

    assert reader.base_array_names == ['SQNZ']
    assert reader.base_array_status('SQNZ') is True

    assert reader.family_array_names == ['inflow', 'outflow', 'sym', 'wall']
    assert reader.family_array_status('inflow') is True


def test_bmpreader():
    filename = examples.download_masonry_texture(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.BMPReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_demreader():
    filename = examples.download_st_helens(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.DEMReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_jpegreader():
    filename = examples.planets.download_mars_surface(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.JPEGReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_meta_image_reader():
    filename = examples.download_chest(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.MetaImageReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_nifti_reader():
    filename = examples.download_brain_atlas_with_sides(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.NIFTIReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_nrrd_reader():
    filename = examples.download_beach(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.NRRDReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_png_reader():
    filename = examples.download_vtk_logo(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.PNGReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_pnm_reader():
    filename = examples.download_gourds_pnm(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.PNMReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_slc_reader():
    filename = examples.download_knee_full(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.SLCReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_tiff_reader():
    filename = examples.download_crater_imagery(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.TIFFReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


@pytest.mark.needs_vtk9
def test_hdr_reader():
    filename = examples.download_parched_canal_4k(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.HDRReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


def test_avsucd_reader():
    filename = examples.download_cells_nd(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.AVSucdReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])


@pytest.mark.needs_vtk_version(9, 1)
def test_hdf_reader():
    filename = examples.download_can_crushed_hdf(load=False)
    reader = pyvista.get_reader(filename)
    assert isinstance(reader, pyvista.HDFReader)
    assert reader.path == filename

    mesh = reader.read()
    assert all([mesh.n_points, mesh.n_cells])
    assert mesh.n_points == 6724
    assert 'VEL' in mesh.point_data
    assert mesh.n_cells == 4800


def test_gif_reader(gif_file):
    reader = pyvista.get_reader(gif_file)
    assert isinstance(reader, pyvista.GIFReader)
    assert reader.path == gif_file
    reader.show_progress()

    grid = reader.read()
    assert grid.n_arrays == 3

    img = Image.open(gif_file)
    new_grid = pyvista.UniformGrid(dims=(img.size[0], img.size[1], 1))

    # load each frame to the grid
    for i, frame in enumerate(ImageSequence.Iterator(img)):
        data = np.array(frame.convert('RGB').getdata(), dtype=np.uint8)
        data_name = f'frame{i}'
        new_grid.point_data.set_array(data, data_name)
        assert np.allclose(grid[data_name], new_grid[data_name])


# setup.py

"""Installation file for python pyvista module."""
from io import open as io_open
import os

from setuptools import setup

package_name = 'pyvista'

__version__ = None
filepath = os.path.dirname(__file__)
version_file = os.path.join(filepath, package_name, '_version.py')
with io_open(version_file, mode='r') as fd:
    exec(fd.read())

install_requires = [
    'numpy',
    'imageio',
    'pillow',
    'appdirs',
    'scooby>=0.5.1',
    'vtk',
    "typing-extensions; python_version < '3.8'",
]

readme_file = os.path.join(filepath, 'README.rst')

setup(
    name=package_name,
    packages=[
        'pyvista',
        'pyvista.examples',
        'pyvista.core',
        'pyvista.core.filters',
        'pyvista.demos',
        'pyvista.jupyter',
        'pyvista.plotting',
        'pyvista.utilities',
        'pyvista.ext',
    ],
    version=__version__,
    description='Easier Pythonic interface to VTK',
    long_description=io_open(readme_file, encoding="utf-8").read(),
    long_description_content_type='text/x-rst',
    author='PyVista Developers',
    author_email='info@pyvista.org',
    license='MIT',
    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Science/Research',
        'Topic :: Scientific/Engineering :: Information Analysis',
        'License :: OSI Approved :: MIT License',
        'Operating System :: Microsoft :: Windows',
        'Operating System :: POSIX',
        'Operating System :: MacOS',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
        'Programming Language :: Python :: 3.10',
    ],
    url='https://github.com/pyvista/pyvista',
    keywords='vtk numpy plotting mesh',
    package_data={
        'pyvista': [
            'py.typed',
        ],
        'pyvista.examples': [
            'airplane.ply',
            'ant.ply',
            'channels.vti',
            'hexbeam.vtk',
            'sphere.ply',
            'nut.ply',
            'uniform.vtk',
            'rectilinear.vtk',
            'globe.vtk',
            '2k_earth_daymap.jpg',
        ],
    },
    project_urls={
        "Documentation": "https://docs.pyvista.org/",
        "Bug Tracker": "https://github.com/pyvista/pyvista/issues",
        "Source Code": "https://github.com/pyvista/pyvista",
    },
    python_requires='>=3.7.*',
    install_requires=install_requires,
    extras_require={
        'all': [
            'matplotlib',
            'colorcet',
            'cmocean',
            'meshio>=5.2',
            'ipyvtklink',
            'panel',
            'pythreejs',
        ],
        'colormaps': ['matplotlib', 'colorcet', 'cmocean'],
        'io': ['meshio>=5.2'],
        'jupyter': ['ipyvtklink', 'panel', 'pythreejs'],
    },
    zip_safe=False,
)


# tests/check_doctest_names.py

"""A helper script to check names in doctests.

This module is intended to be called from pyvista's root directory with

    python tests/check_doctest_names.py

The problem is that pytest doctests (following the standard-library
doctest module) see the module-global namespace. So when a doctest looks
like this:

    Examples
    --------
    >>> import numpy
    >>> import vtk
    >>> import pyvista
    >>> offset = np.array([0, 9])
    >>> cells = np.array([8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15])
    >>> cell_type = np.array([vtk.VTK_HEXAHEDRON, vtk.VTK_HEXAHEDRON], np.int8)

there will be a ``NameError`` when the code block is copied into Python
because the ``np`` name is undefined. However, pytest and sphinx test
runs will not catch this, as the ``np`` name is typically also available
in the global namespace of the module where the doctest resides.

In order to fix this, we build a tree of pyvista's public and private
API, using the standard-library doctest module as a doctest parser. We
execute examples with a clean empty namespace to ensure that mistakes
such as the above can be caught.

Note that we don't try to verify that the actual results from each
example are correct; that's still pytest's responsibility. As long
as the examples run without error, this module will be happy.

The implementation is not very robust or smart, it just gets the job
done to find the rare name mistake in our examples.

If you need off-screen plotting, set the ``PYVISTA_OFF_SCREEN``
environmental variable to ``True`` before running the script.
"""

from argparse import ArgumentParser
from doctest import DocTestFinder
import re
import sys
from textwrap import indent
from types import ModuleType

import pyvista


def discover_modules(entry=pyvista, recurse=True):
    """Discover the submodules present under an entry point.

    If ``recurse=True``, search goes all the way into descendants of the
    entry point. Only modules are gathered, because within a module
    ``doctest``'s discovery can work recursively.

    Should work for ``pyvista`` as entry, but no promises for its more
    general applicability.

    Parameters
    ----------
    entry : module, optional
        The entry point of the submodule search. Defaults to the main
        ``pyvista`` module.

    recurse : bool, optional
        Whether to recurse into submodules.

    Returns
    -------
    modules : dict of modules
        A (module name -> module) mapping of submodules under ``entry``.

    """
    entry_name = entry.__name__
    found_modules = {}
    next_entries = {entry}
    while next_entries:
        next_modules = {}
        for entry in next_entries:
            for attr_short_name in dir(entry):
                attr = getattr(entry, attr_short_name)
                if not isinstance(attr, ModuleType):
                    continue

                module_name = attr.__name__

                if module_name.startswith(entry_name):
                    next_modules[module_name] = attr

        if not recurse:
            return next_modules

        # find as-of-yet-undiscovered submodules
        next_entries = {
            module
            for module_name, module in next_modules.items()
            if module_name not in found_modules
        }
        found_modules.update(next_modules)

    return found_modules


def check_doctests(modules=None, respect_skips=True, verbose=True):
    """Check whether doctests can be run as-is without errors.

    Parameters
    ----------
    modules : dict, optional
        (module name -> module) mapping of submodules defined in a
        package as returned by ``discover_modules()``. If omitted,
        ``discover_modules()`` will be called for ``pyvista``.

    respect_skips : bool, optional
        Whether to ignore doctest examples that contain a DOCTEST:+SKIP
        directive.

    verbose : bool, optional
        Whether to print passes/failures as the testing progresses.
        Failures are printed at the end in every case.

    Returns
    -------
    failures : dict of (Exception, str)  tuples
        An (object name -> (exception raised, failing code)) mapping
        of failed doctests under the specified modules.

    """
    skip_pattern = re.compile('doctest: *\+SKIP')  # noqa: W605

    if modules is None:
        modules = discover_modules()

    # find and parse all docstrings; this will also remove any duplicates
    doctests = {
        dt.name: dt
        for module_name, module in modules.items()
        for dt in DocTestFinder(recurse=True).find(module, globs={})
    }

    # loop over doctests in alphabetical order for sanity
    sorted_names = sorted(doctests)
    failures = {}
    for dt_name in sorted_names:
        dt = doctests[dt_name]
        if not dt.examples:
            continue

        # mock print to suppress output from a few talkative tests
        globs = {'print': (lambda *args, **kwargs: ...)}
        for iline, example in enumerate(dt.examples, start=1):
            if not example.source.strip() or (
                respect_skips and skip_pattern.search(example.source)
            ):
                continue
            try:
                exec(example.source, globs)
            except Exception as exc:
                if verbose:
                    print(f'FAILED: {dt.name} -- {repr(exc)}')
                erroring_code = ''.join([example.source for example in dt.examples[:iline]])
                failures[dt_name] = exc, erroring_code
                break
        else:
            if verbose:
                print(f'PASSED: {dt.name}')

    total = len(doctests)
    fails = len(failures)
    passes = total - fails
    print(f'\n{passes} passes and {fails} failures out of {total} total doctests.\n')
    if not fails:
        return failures

    print('List of failures:')
    for name, (exc, erroring_code) in failures.items():
        print('-' * 60)
        print(f'{name}:')
        print(indent(erroring_code, '    '))
        print(repr(exc))
    print('-' * 60)

    return failures


if __name__ == "__main__":
    parser = ArgumentParser(description='Look for name errors in doctests.')
    parser.add_argument(
        '-v', '--verbose', action='store_true', help='print passes and failures as tests progress'
    )
    parser.add_argument(
        '--no-respect-skips',
        action='store_false',
        dest='respect_skips',
        help='ignore doctest SKIP directives',
    )
    args = parser.parse_args()

    failures = check_doctests(verbose=args.verbose, respect_skips=args.respect_skips)

    if failures:
        # raise a red flag for CI
        sys.exit(1)


# tests/conftest.py

import numpy as np
from numpy.random import default_rng
from pytest import fixture, skip

import pyvista
from pyvista import examples

pyvista.OFF_SCREEN = True


@fixture(scope='session', autouse=True)
def set_mpl():
    """Avoid matplotlib windows popping up."""
    try:
        import matplotlib
    except ImportError:
        pass
    else:
        matplotlib.use('agg', force=True)


@fixture()
def cube():
    return pyvista.Cube()


@fixture()
def airplane():
    return examples.load_airplane()


@fixture()
def rectilinear():
    return examples.load_rectilinear()


@fixture()
def sphere():
    return examples.load_sphere()


@fixture()
def uniform():
    return examples.load_uniform()


@fixture()
def ant():
    return examples.load_ant()


@fixture()
def globe():
    return examples.load_globe()


@fixture()
def hexbeam():
    return examples.load_hexbeam()


@fixture()
def tetbeam():
    return examples.load_tetbeam()


@fixture()
def struct_grid():
    x, y, z = np.meshgrid(
        np.arange(-10, 10, 2, dtype=np.float32),
        np.arange(-10, 10, 2, dtype=np.float32),
        np.arange(-10, 10, 2, dtype=np.float32),
    )
    return pyvista.StructuredGrid(x, y, z)


@fixture()
def plane():
    return pyvista.Plane()


@fixture()
def spline():
    return examples.load_spline()


@fixture()
def tri_cylinder():
    """Triangulated cylinder"""
    return pyvista.Cylinder().triangulate()


@fixture()
def datasets():
    return [
        examples.load_uniform(),  # UniformGrid
        examples.load_rectilinear(),  # RectilinearGrid
        examples.load_hexbeam(),  # UnstructuredGrid
        examples.load_airplane(),  # PolyData
        examples.load_structured(),  # StructuredGrid
    ]


@fixture()
def multiblock_poly():
    # format and order of data (including missing) is intentional
    mesh_a = pyvista.Sphere(center=(0, 0, 0))
    mesh_a['data_a'] = mesh_a.points[:, 0] * 10
    mesh_a['data_b'] = mesh_a.points[:, 1] * 10
    mesh_a['cell_data'] = mesh_a.cell_centers().points[:, 0]
    mesh_a.point_data.set_array(mesh_a.points[:, 2] * 10, 'all_data')

    mesh_b = pyvista.Sphere(center=(1, 0, 0))
    mesh_b['data_a'] = mesh_b.points[:, 0] * 10
    mesh_b['data_b'] = mesh_b.points[:, 1] * 10
    mesh_b['cell_data'] = mesh_b.cell_centers().points[:, 0]
    mesh_b.point_data.set_array(mesh_b.points[:, 2] * 10, 'all_data')

    mesh_c = pyvista.Sphere(center=(2, 0, 0))
    mesh_c.point_data.set_array(mesh_c.points, 'multi-comp')
    mesh_c.point_data.set_array(mesh_c.points[:, 2] * 10, 'all_data')

    mblock = pyvista.MultiBlock()
    mblock.append(mesh_a)
    mblock.append(mesh_b)
    mblock.append(mesh_c)
    return mblock


@fixture()
def datasets_vtk9():
    return [
        examples.load_explicit_structured(),
    ]


@fixture()
def pointset():
    rng = default_rng(0)
    points = rng.random((10, 3))
    return pyvista.PointSet(points)


@fixture()
def multiblock_all(datasets):
    """Return datasets fixture combined in a pyvista multiblock."""
    return pyvista.MultiBlock(datasets)


@fixture()
def noise_2d():
    freq = [10, 5, 0]
    noise = pyvista.perlin_noise(1, freq, (0, 0, 0))
    return pyvista.sample_function(noise, bounds=(0, 10, 0, 10, 0, 10), dim=(2**4, 2**4, 1))


def pytest_addoption(parser):
    parser.addoption("--reset_image_cache", action='store_true', default=False)
    parser.addoption("--ignore_image_cache", action='store_true', default=False)
    parser.addoption("--fail_extra_image_cache", action='store_true', default=False)


def pytest_runtest_setup(item):
    """Custom setup to handle skips based on VTK version.

    See pytest.mark.needs_vtk9 and pytest.mark.needs_vtk_version
    in pytest.ini.

    """
    for mark in item.iter_markers('needs_vtk9'):
        # this test needs VTK 9 or newer
        if not pyvista._vtk.VTK9:
            skip('Test needs VTK 9 or newer.')
    for mark in item.iter_markers('needs_vtk_version'):
        # this test needs the given VTK version
        # allow both needs_vtk_version(9, 1) and needs_vtk_version((9, 1))
        args = mark.args
        if len(args) == 1 and isinstance(args[0], tuple):
            version_needed = args[0]
        else:
            version_needed = args
        if pyvista.vtk_version_info < version_needed:
            version_str = '.'.join(map(str, version_needed))
            skip(f'Test needs VTK {version_str} or newer.')


# tests/test_actor.py

import numpy as np
import pytest

import pyvista as pv
from pyvista import examples


@pytest.fixture()
def actor():
    return pv.Plotter().add_mesh(pv.Plane())


def test_actor_init_empty():
    actor = pv.Actor()
    assert 'Position' in repr(actor)
    assert 'Has mapper' in repr(actor)
    assert 'Mapper' not in repr(actor)
    assert isinstance(actor.prop, pv.Property)
    assert actor.mapper is None

    with pytest.raises(AttributeError):
        actor.not_an_attribute = None

    with pytest.raises(TypeError):
        actor.renderer = None

    assert actor.memory_address == actor.GetAddressAsString("")


def test_actor_from_plotter():
    mesh = pv.Sphere()
    actor = pv.Plotter().add_mesh(mesh, lighting=False)
    assert actor.mapper.dataset is mesh
    assert actor.prop.lighting is False
    assert 'Mapper' in repr(actor)


def test_actor_copy_deep(actor):
    actor_copy = actor.copy()
    assert actor_copy is not actor

    assert actor_copy.prop is not actor.prop
    actor_copy.prop.lighting = not actor_copy.prop.lighting
    assert actor_copy.prop.lighting is not actor.prop.lighting

    assert actor_copy.mapper is not actor.mapper
    assert actor_copy.mapper.dataset is actor.mapper.dataset
    actor_copy.mapper.dataset = None
    assert actor.mapper.dataset is not None


def test_actor_copy_shallow(actor):
    actor_copy = actor.copy(deep=False)
    assert actor_copy is not actor
    assert actor_copy.prop is actor.prop
    assert actor_copy.mapper is actor.mapper


def test_actor_texture(actor):
    texture = examples.download_masonry_texture()
    actor.texture = texture
    assert actor.texture is texture


def test_actor_pickable(actor):
    actor.pickable = True
    assert actor.pickable is True


def test_actor_visible(actor):
    actor.visibility = True
    assert actor.visibility is True


def test_actor_scale(actor):
    assert actor.scale == (1, 1, 1)
    scale = (2, 2, 2)
    actor.scale = scale
    assert actor.scale == scale


def test_actor_position(actor):
    assert actor.position == (0, 0, 0)
    position = (2, 2, 2)
    actor.position = position
    assert actor.position == position


def test_actor_rotate_x(actor):
    actor.rotate_x(90)
    assert np.allclose(actor.orientation, (90, 0, 0))


def test_actor_rotate_y(actor):
    actor.rotate_y(90)
    assert np.allclose(actor.orientation, (0, 90, 0))


def test_actor_rotate_z(actor):
    actor.rotate_z(90)
    assert np.allclose(actor.orientation, (0, 0, 90))


def test_actor_orientation(actor):
    actor.orientation == (0, 0, 0)
    orientation = (10, 20, 30)
    actor.orientation = orientation
    assert np.allclose(actor.orientation, orientation)


def test_actor_unit_matrix(actor):
    assert actor.user_matrix is None

    arr = np.array([[0.707, -0.707, 0, 0], [0.707, 0.707, 0, 0], [0, 0, 1, 1.500001], [0, 0, 0, 2]])

    actor.user_matrix = arr
    assert isinstance(actor.user_matrix, np.ndarray)
    assert np.allclose(actor.user_matrix, arr)


def test_actor_bounds(actor):
    assert isinstance(actor.bounds, tuple)
    assert np.allclose(actor.bounds, (-0.5, 0.5, -0.5, 0.5, 0, 0))


def test_actor_center(actor):
    assert actor.center == (0.0, 0.0, 0.0)


def test_actor_backface_prop(actor):
    actor.prop.opacity = 0.5
    assert isinstance(actor.backface_prop, pv.Property)
    assert actor.backface_prop.opacity == actor.prop.opacity
    actor.backface_prop.opacity = 1.0
    assert actor.backface_prop.opacity == 1.0

    actor.backface_prop = None
    assert actor.backface_prop.opacity == actor.prop.opacity


# tests/test_axes.py

import numpy as np

import pyvista


def test_actors():
    axes = pyvista.Axes()
    actor = axes.actor

    # test showing
    assert not actor.GetVisibility()
    axes.show_actor()
    assert actor.GetVisibility()

    # test hiding
    assert actor.GetVisibility()
    axes.hide_actor()
    assert not actor.GetVisibility()


def test_origin():
    axes = pyvista.Axes()

    origin = np.random.random(3)
    axes.origin = origin
    assert np.all(axes.GetOrigin() == origin)
    assert np.all(axes.origin == origin)


def test_symmetric():
    axes = pyvista.Axes()

    # test showing
    assert not axes.GetSymmetric()
    axes.show_symmetric()
    assert axes.GetSymmetric()

    # test hiding
    assert axes.GetSymmetric()
    axes.hide_symmetric()
    assert not axes.GetSymmetric()


# tests/test_camera.py

import numpy as np
import pytest

import pyvista
from pyvista.utilities.misc import PyVistaDeprecationWarning

# pyvista attr -- value -- vtk name triples:
configuration = [
    ('position', (1, 1, 1), 'SetPosition'),
    ('focal_point', (2, 2, 2), 'SetFocalPoint'),
    ('model_transform_matrix', np.arange(4 * 4).reshape(4, 4), 'SetModelTransformMatrix'),
    ('thickness', 1, 'SetThickness'),
    ('parallel_scale', 2, 'SetParallelScale'),
    ('up', (0, 0, 1), 'SetViewUp'),
    ('clipping_range', (4, 5), 'SetClippingRange'),
    ('view_angle', 90.0, 'SetViewAngle'),
    ('roll', 180.0, 'SetRoll'),
]


@pytest.fixture()
def camera():
    return pyvista.Camera()


def test_invalid_init():
    with pytest.raises(TypeError):
        pyvista.Camera(1)


def test_camera_position(camera):
    position = np.random.random(3)
    camera.position = position
    assert np.all(camera.GetPosition() == position)
    assert np.all(camera.position == position)


def test_focal_point(camera):
    focal_point = np.random.random(3)
    camera.focal_point = focal_point
    assert np.all(camera.GetFocalPoint() == focal_point)
    assert np.all(camera.focal_point == focal_point)


def test_model_transform_matrix(camera):
    model_transform_matrix = np.random.random((4, 4))
    camera.model_transform_matrix = model_transform_matrix
    assert np.all(camera.model_transform_matrix == model_transform_matrix)


def test_distance(camera):
    focal_point = np.random.random(3)
    position = np.random.random(3)
    camera.position = position
    camera.focal_point = focal_point
    assert np.isclose(camera.distance, np.linalg.norm(focal_point - position, ord=2), rtol=1e-8)
    distance = np.random.random()
    camera.distance = distance
    assert np.isclose(camera.distance, distance, atol=0.0002)
    # large absolute tolerance because of
    # https://github.com/Kitware/VTK/blob/5f855ff8f1237cbb5e5fa55a5ace48149237006e/Rendering/Core/vtkCamera.cxx#L563-L577


def test_thickness(camera):
    thickness = np.random.random(1)
    camera.thickness = thickness
    assert camera.thickness == thickness


def test_parallel_scale(camera):
    parallel_scale = np.random.random(1)
    camera.parallel_scale = parallel_scale
    assert camera.parallel_scale == parallel_scale


def test_zoom(camera):
    camera.enable_parallel_projection()
    orig_scale = camera.parallel_scale
    zoom = np.random.random(1)
    camera.zoom(zoom)
    assert camera.parallel_scale == orig_scale / zoom


def test_up(camera):
    up = (0.410018, 0.217989, 0.885644)
    camera.up = up
    assert np.allclose(camera.up, up)


def test_enable_parallel_projection(camera):
    camera.enable_parallel_projection()
    assert camera.GetParallelProjection()
    assert camera.parallel_projection


def test_disable_parallel_projection(camera):
    camera.disable_parallel_projection()
    assert not camera.GetParallelProjection()
    assert not camera.parallel_projection


def test_clipping_range(camera):
    near_point = np.random.random(1)
    far_point = near_point + np.random.random(1)
    points = (near_point, far_point)
    camera.clipping_range = points
    assert camera.GetClippingRange() == points
    assert camera.clipping_range == points

    with pytest.raises(ValueError):
        far_point = near_point - np.random.random(1)
        points = (near_point, far_point)
        camera.clipping_range = points


def test_reset_clipping_range(camera):
    with pytest.raises(AttributeError):
        camera.reset_clipping_range()

    # requires renderer for this method
    crng = (1, 2)
    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.Sphere())
    pl.camera.clipping_range = crng
    assert pl.camera.clipping_range == crng
    pl.camera.reset_clipping_range()
    assert pl.camera.clipping_range != crng


def test_view_angle(camera):
    assert camera.GetViewAngle() == camera.view_angle
    view_angle = 60.0
    camera.view_angle = view_angle
    assert camera.GetViewAngle() == view_angle


def test_direction(camera):
    assert camera.GetDirectionOfProjection() == camera.direction


def test_view_frustum(camera):
    frustum = camera.view_frustum(1.0)
    assert frustum.n_points == 8
    assert frustum.n_cells == 6


def test_roll(camera):
    angle = 360.0 * (np.random.rand() - 0.5)
    camera.roll = angle
    assert np.allclose(camera.GetRoll(), angle)
    assert np.allclose(camera.roll, angle)


def test_elevation(camera):
    position = (1.0, 0.0, 0.0)
    elevation = 90.0
    camera.up = (0.0, 0.0, 1.0)
    camera.position = position
    camera.focal_point = (0.0, 0.0, 0.0)
    camera.elevation = elevation
    assert np.allclose(camera.position, (0.0, 0.0, 1.0))
    assert np.allclose(camera.GetPosition(), (0.0, 0.0, 1.0))
    assert np.allclose(camera.elevation, elevation)

    camera.position = (2.0, 0.0, 0.0)
    assert np.allclose(camera.GetPosition(), (2.0, 0.0, 0.0))

    camera.elevation = 180.0
    assert np.allclose(camera.GetPosition(), (-2.0, 0.0, 0.0))


def test_azimuth(camera):
    position = (1.0, 0.0, 0.0)
    azimuth = 90.0
    camera.up = (0.0, 0.0, 1.0)
    camera.position = position
    camera.focal_point = (0.0, 0.0, 0.0)
    camera.azimuth = azimuth
    assert np.allclose(camera.position, (0.0, 1.0, 0.0))
    assert np.allclose(camera.GetPosition(), (0.0, 1.0, 0.0))
    assert np.allclose(camera.azimuth, azimuth)

    camera.position = (2.0, 0.0, 0.0)
    assert np.allclose(camera.GetPosition(), (2.0, 0.0, 0.0))

    camera.azimuth = 180.0
    assert np.allclose(camera.GetPosition(), (-2.0, 0.0, 0.0))


def test_eq():
    camera = pyvista.Camera()
    other = pyvista.Camera()
    for camera_now in camera, other:
        for name, value, _ in configuration:
            setattr(camera_now, name, value)

    assert camera == other

    # check that changing anything will break equality
    for name, value, _ in configuration:
        original_value = getattr(other, name)
        if isinstance(value, bool):
            changed_value = not value
        elif isinstance(value, (int, float)):
            changed_value = 0
        elif isinstance(value, tuple):
            changed_value = (0.5, 0.5, 0.5)
        else:
            changed_value = -value
        setattr(other, name, changed_value)
        assert camera != other
        setattr(other, name, original_value)

    # sanity check that we managed to restore the original state
    assert camera == other


def test_copy():
    camera = pyvista.Camera()
    for name, value, _ in configuration:
        setattr(camera, name, value)

    deep = camera.copy()
    assert deep == camera


def test_deprecation_warning_of_is_parallel_projection(camera):
    with pytest.warns(PyVistaDeprecationWarning):
        _ = camera.is_parallel_projection


# tests/test_cells.py

import numpy as np
import pytest
import vtk
from vtk.util.numpy_support import vtk_to_numpy

import pyvista

CELL_LIST = [3, 0, 1, 2, 3, 3, 4, 5]
NCELLS = 2
FCONTIG_ARR = np.array(np.vstack(([3, 0, 1, 2], [3, 3, 4, 5])), order='F')


@pytest.mark.parametrize('deep', [False, True])
@pytest.mark.parametrize('n_cells', [None, NCELLS])
@pytest.mark.parametrize(
    'cells',
    [
        CELL_LIST,
        np.array(CELL_LIST, np.int16),
        np.array(CELL_LIST, np.int32),
        np.array(CELL_LIST, np.int64),
        FCONTIG_ARR,
    ],
)
def test_init_cell_array(cells, n_cells, deep):
    cell_array = pyvista.utilities.cells.CellArray(cells, n_cells, deep)
    assert np.allclose(np.array(cells).ravel(), cell_array.cells)
    assert cell_array.n_cells == cell_array.GetNumberOfCells() == NCELLS


def test_numpy_to_idarr_bool():
    mask = np.ones(10, np.bool_)
    idarr = pyvista.utilities.cells.numpy_to_idarr(mask)
    assert np.allclose(mask.nonzero()[0], vtk_to_numpy(idarr))


def test_cell_types():
    cell_types = [
        "EMPTY_CELL",
        "VERTEX",
        "POLY_VERTEX",
        "LINE",
        "POLY_LINE",
        "TRIANGLE",
        "TRIANGLE_STRIP",
        "POLYGON",
        "PIXEL",
        "QUAD",
        "TETRA",
        "VOXEL",
        "HEXAHEDRON",
        "WEDGE",
        "PYRAMID",
        "PENTAGONAL_PRISM",
        "HEXAGONAL_PRISM",
        "QUADRATIC_EDGE",
        "QUADRATIC_TRIANGLE",
        "QUADRATIC_QUAD",
        "QUADRATIC_POLYGON",
        "QUADRATIC_TETRA",
        "QUADRATIC_HEXAHEDRON",
        "QUADRATIC_WEDGE",
        "QUADRATIC_PYRAMID",
        "BIQUADRATIC_QUAD",
        "TRIQUADRATIC_HEXAHEDRON",
        "TRIQUADRATIC_PYRAMID",
        "QUADRATIC_LINEAR_QUAD",
        "QUADRATIC_LINEAR_WEDGE",
        "BIQUADRATIC_QUADRATIC_WEDGE",
        "BIQUADRATIC_QUADRATIC_HEXAHEDRON",
        "BIQUADRATIC_TRIANGLE",
        "CUBIC_LINE",
        "CONVEX_POINT_SET",
        "POLYHEDRON",
        "PARAMETRIC_CURVE",
        "PARAMETRIC_SURFACE",
        "PARAMETRIC_TRI_SURFACE",
        "PARAMETRIC_QUAD_SURFACE",
        "PARAMETRIC_TETRA_REGION",
        "PARAMETRIC_HEX_REGION",
        "HIGHER_ORDER_EDGE",
        "HIGHER_ORDER_TRIANGLE",
        "HIGHER_ORDER_QUAD",
        "HIGHER_ORDER_POLYGON",
        "HIGHER_ORDER_TETRAHEDRON",
        "HIGHER_ORDER_WEDGE",
        "HIGHER_ORDER_PYRAMID",
        "HIGHER_ORDER_HEXAHEDRON",
        "LAGRANGE_CURVE",
        "LAGRANGE_TRIANGLE",
        "LAGRANGE_QUADRILATERAL",
        "LAGRANGE_TETRAHEDRON",
        "LAGRANGE_HEXAHEDRON",
        "LAGRANGE_WEDGE",
        "LAGRANGE_PYRAMID",
        "BEZIER_CURVE",
        "BEZIER_TRIANGLE",
        "BEZIER_QUADRILATERAL",
        "BEZIER_TETRAHEDRON",
        "BEZIER_HEXAHEDRON",
        "BEZIER_WEDGE",
        "BEZIER_PYRAMID",
    ]
    for cell_type in cell_types:
        if hasattr(vtk, "VTK_" + cell_type):
            assert getattr(pyvista.CellType, cell_type) == getattr(vtk, 'VTK_' + cell_type)


# tests/test_charts.py

"""Test charting functionality"""

import itertools
import platform

import numpy as np
import pytest

import pyvista
from pyvista import examples
from pyvista.plotting import charts, system_supports_plotting
from pyvista.utilities.misc import can_create_mpl_figure

skip_mac = pytest.mark.skipif(
    platform.system() == 'Darwin', reason='MacOS CI fails when downloading examples'
)

skip_no_plotting = pytest.mark.skipif(
    not system_supports_plotting(), reason="Test requires system to support plotting"
)

skip_no_mpl_figure = pytest.mark.skipif(
    not can_create_mpl_figure(), reason="Cannot create a figure using matplotlib"
)

# skip all tests if VTK<9.1.0
if pyvista.vtk_version_info < (9, 1):
    pytestmark = pytest.mark.skip


def vtk_array_to_tuple(arr):
    return tuple(arr.GetValue(i) for i in range(arr.GetNumberOfValues()))


def to_vtk_scientific(val):
    parts = val.split('e')
    sign, exp = parts[1][0], parts[1][1:]
    exp = exp.lstrip("0")  # Remove leading zeros of exponent
    return (
        parts[0] + "e" + sign + exp if exp != "" else parts[0]
    )  # Remove exponent altogether if it is 0


@pytest.fixture
def pl():
    p = pyvista.Plotter(window_size=(600, 600))
    p.background_color = 'w'
    return p


@pytest.fixture
def chart_2d():
    return pyvista.Chart2D()


@pytest.fixture
def chart_box():
    return pyvista.ChartBox([[1, 2, 3]])


@pytest.fixture
def chart_pie():
    return pyvista.ChartPie([1, 2, 3])


@pytest.fixture
def chart_mpl():
    import matplotlib.pyplot as plt

    f, ax = plt.subplots()
    ax.plot([0, 1, 2], [3, 1, 2])
    return pyvista.ChartMPL(f)


@pytest.fixture
def line_plot_2d(chart_2d):
    return chart_2d.line([0, 1, 2], [3, 1, 2])


@pytest.fixture
def scatter_plot_2d(chart_2d):
    return chart_2d.scatter([0, 1, 2], [3, 1, 2])


@pytest.fixture
def area_plot(chart_2d):
    return chart_2d.area([0, 1, 2], [2, 1, 3], [0, 2, 0])


@pytest.fixture
def bar_plot(chart_2d):
    return chart_2d.bar([0, 1, 2], [[2, 1, 3], [1, 2, 0]])


@pytest.fixture
def stack_plot(chart_2d):
    return chart_2d.stack([0, 1, 2], [[2, 1, 3], [1, 2, 0]])


@pytest.fixture
def box_plot(chart_box):
    return chart_box.plot


@pytest.fixture
def pie_plot(chart_pie):
    return chart_pie.plot


@pytest.fixture
def axis(chart_2d):
    # Test properties, using the y axis of a 2D chart
    chart_2d.line([0, 1], [1, 10])
    chart_2d.show()
    return chart_2d.y_axis


def test_pen():
    c_red, c_blue = (1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0)
    w_thin, w_thick = 2, 10
    s_dash, s_dot, s_inv = "--", ":", "|"
    assert s_inv not in charts.Pen.LINE_STYLES, "New line styles added? Change this test."

    # Test constructor arguments
    pen = charts.Pen(color=c_red, width=w_thin, style=s_dash)
    assert pen.color == c_red
    assert np.isclose(pen.width, w_thin)
    assert pen.style == s_dash

    # Test properties
    pen.color = c_blue
    color = [0.0, 0.0, 0.0]
    pen.GetColorF(color)
    color.append(pen.GetOpacity() / 255)
    assert pen.color == c_blue
    assert np.allclose(color, c_blue)

    pen.width = w_thick
    assert np.isclose(pen.width, w_thick)
    assert np.isclose(pen.GetWidth(), w_thick)

    pen.style = s_dot
    assert pen.style == s_dot
    assert pen.GetLineType() == charts.Pen.LINE_STYLES[s_dot]["id"]
    with pytest.raises(ValueError):
        pen.style = s_inv


def test_wrapping():
    width = 5
    # Test wrapping of VTK Pen object
    vtkPen = pyvista._vtk.vtkPen()
    wrappedPen = charts.Pen(_wrap=vtkPen)
    assert wrappedPen.__this__ == vtkPen.__this__
    assert wrappedPen.width == vtkPen.GetWidth()
    wrappedPen.width = width
    assert wrappedPen.width == vtkPen.GetWidth()
    assert vtkPen.GetWidth() == width


@skip_mac
def test_brush():
    c_red, c_blue = (1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0)
    t_masonry = examples.download_masonry_texture()
    t_puppy = examples.download_puppy_texture()

    # Test constructor arguments
    brush = charts.Brush(color=c_red, texture=t_masonry)
    assert brush.color == c_red
    assert np.allclose(brush.texture.to_array(), t_masonry.to_array())

    # Test properties
    brush.color = c_blue
    color = [0.0, 0.0, 0.0, 0.0]
    brush.GetColorF(color)
    assert brush.color == c_blue
    assert np.allclose(color, c_blue)

    brush.texture = t_puppy
    t = pyvista.Texture(brush.GetTexture())
    assert np.allclose(brush.texture.to_array(), t_puppy.to_array())
    assert np.allclose(t.to_array(), t_puppy.to_array())

    brush.texture_interpolate = False
    assert not brush.texture_interpolate

    NEAREST = 0x01
    assert brush.GetTextureProperties() & NEAREST

    brush.texture_repeat = True
    assert brush.texture_repeat
    REPEAT = 0x08
    assert brush.GetTextureProperties() & REPEAT


def test_axis_init(chart_2d):
    label = "Y axis"
    r_fix = [2, 5]

    # Test constructor arguments
    axis = charts.Axis(label=label, range=r_fix, grid=True)
    assert axis.label == label
    assert np.allclose(axis.range, r_fix) and axis.behavior == "fixed"
    assert axis.grid


def test_axis_label(axis):
    label = "Y axis"
    axis.label = label
    assert axis.label == label
    assert axis.GetTitle() == label

    axis.label_visible = False
    assert not axis.label_visible
    assert not axis.GetTitleVisible()


def test_axis_range(axis):
    r_fix, r_auto = [2, 5], None
    axis.range = r_auto
    assert axis.behavior == "auto"
    axis.range = r_fix
    r = [0.0, 0.0]
    axis.GetRange(r)
    assert np.allclose(axis.range, r_fix)
    assert np.allclose(r, r_fix)
    assert axis.behavior == "fixed"

    assert axis.GetBehavior() == charts.Axis.BEHAVIORS["fixed"]
    axis.behavior = "auto"
    assert axis.behavior == "auto"
    assert axis.GetBehavior() == charts.Axis.BEHAVIORS["auto"]
    with pytest.raises(ValueError):
        axis.behavior = "invalid"


def test_axis_margin(axis):
    margin = 50
    axis.margin = margin
    assert axis.margin == margin
    assert axis.GetMargins()[0] == margin


@skip_no_plotting
def test_axis_scale(chart_2d, axis):
    axis.log_scale = True  # Log scale can be enabled for the currently drawn plot
    chart_2d.show()  # We have to call show to update all chart properties (calls Update and Paint methods of chart/plot objects).
    assert axis.log_scale
    assert axis.GetLogScaleActive()

    axis.log_scale = False
    chart_2d.show()
    assert not axis.log_scale
    assert not axis.GetLogScaleActive()
    # TODO: following lines cause "vtkMath::Jacobi: Error extracting eigenfunctions" warning to be printed.
    #  This is a VTK issue that will be fixed once PR (!8618) is merged.

    chart_2d.line([0, 1], [-10, 10])  # Plot for which log scale cannot be enabled
    axis.log_scale = True
    chart_2d.show()
    assert not axis.log_scale
    assert not axis.GetLogScaleActive()


def test_axis_grid(axis):
    axis.grid = False
    assert not axis.grid
    assert not axis.GetGridVisible()


def test_axis_visible(axis):
    axis.visible = False
    assert not axis.visible
    assert not axis.GetAxisVisible()
    axis.toggle()
    assert axis.visible
    assert axis.GetAxisVisible()


def test_axis_tick_count(axis):
    tc = 10
    tc0 = axis.tick_count
    axis.tick_count = tc
    assert axis.tick_count == tc
    assert axis.GetNumberOfTicks() == tc
    axis.tick_count = None
    assert axis.tick_count == tc0
    assert axis.GetNumberOfTicks() == tc0
    axis.tick_count = -1
    assert axis.tick_count == tc0
    assert axis.GetNumberOfTicks() == tc0


def test_axis_tick_locations(chart_2d, axis):
    tlocs, tlocs_large = [1, 5.5, 8], [5.2, 340, 9999.999]
    tlabels = ["Foo", "Blub", "Spam"]

    axis.tick_locations = tlocs
    axis.tick_labels = tlabels
    assert np.allclose(axis.tick_locations, tlocs)
    assert np.allclose(axis.GetTickPositions(), tlocs)
    assert tuple(axis.tick_labels) == tuple(tlabels)
    assert vtk_array_to_tuple(axis.GetTickLabels()) == tuple(tlabels)
    axis.tick_labels = "2f"
    chart_2d.show()
    assert tuple(axis.tick_labels) == tuple(f"{loc:.2f}" for loc in tlocs)
    assert vtk_array_to_tuple(axis.GetTickLabels()) == tuple(f"{loc:.2f}" for loc in tlocs)
    assert axis.GetNotation() == charts.Axis.FIXED_NOTATION
    assert axis.GetPrecision() == 2
    axis.tick_labels = "4e"
    axis.tick_locations = tlocs_large  # Add some more variety to labels
    chart_2d.show()

    assert tuple(axis.tick_labels) == tuple(to_vtk_scientific(f"{loc:.4e}") for loc in tlocs_large)
    assert vtk_array_to_tuple(axis.GetTickLabels()) == tuple(
        to_vtk_scientific(f"{loc:.4e}") for loc in tlocs_large
    )
    assert axis.GetNotation() == charts.Axis.SCIENTIFIC_NOTATION
    assert axis.GetPrecision() == 4


def test_axis_tick_size(axis):
    ts = 5
    axis.tick_size = ts
    assert axis.tick_size == ts
    assert axis.GetTickLength() == ts


def test_axis_tick_offset(axis):
    tlo = 10
    axis.tick_labels_offset = tlo
    assert axis.tick_labels_offset == tlo
    assert axis.GetLabelOffset() == tlo


def test_axis_tick_labels_visible(axis):
    axis.tick_labels_visible = False
    assert not axis.tick_labels_visible
    assert not axis.GetLabelsVisible()
    assert not axis.GetRangeLabelsVisible()


def test_axis_tick_visible(axis):
    axis.ticks_visible = False
    assert not axis.ticks_visible
    assert not axis.GetTicksVisible()


def test_axis_label_font_size(chart_2d):
    _ = chart_2d.line([0, 1, 2], [2, 1, 3])
    axis = chart_2d.x_axis
    font_size = 20

    axis.label_size = font_size
    assert axis.label_size == font_size
    assert axis.GetTitleProperties().GetFontSize() == font_size

    axis.tick_label_size = font_size
    assert axis.tick_label_size == font_size
    assert axis.GetLabelProperties().GetFontSize() == font_size


@skip_no_plotting
@skip_no_mpl_figure
@pytest.mark.parametrize("chart_f", ("chart_2d", "chart_box", "chart_pie", "chart_mpl"))
def test_chart_common(pl, chart_f, request):
    # Test the common chart functionalities
    chart = request.getfixturevalue(chart_f)
    title = "Chart title"
    c_red, c_blue = (1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0)
    bw = 10
    bs = "--"

    # Check scene and renderer properties
    assert chart._scene is None
    assert chart._renderer is None
    pl.add_chart(chart)
    assert chart._scene is pl.renderer._charts._scene
    assert chart._renderer is pl.renderer and chart._renderer is pl.renderer._charts._renderer

    with pytest.raises((AssertionError, ValueError)):
        chart.size = (-1, 1)
    with pytest.raises((AssertionError, ValueError)):
        chart.loc = (-1, 1)
    try:  # Try block for now as not all charts support a custom size and loc
        chart.size = (0.5, 0.5)
        chart.loc = (0.25, 0.25)
        assert chart.size == (0.5, 0.5)
        assert chart.loc == (0.25, 0.25)
    except ValueError:
        pass

    # Check geometry and resizing
    w, h = pl.window_size
    chart._render_event()
    assert chart._geometry == (
        chart.loc[0] * w,
        chart.loc[1] * h,
        chart.size[0] * w,
        chart.size[1] * h,
    )
    w, h = pl.window_size = [200, 200]
    chart._render_event()
    assert chart._geometry == (
        chart.loc[0] * w,
        chart.loc[1] * h,
        chart.size[0] * w,
        chart.size[1] * h,
    )

    # Check is_within
    assert chart._is_within(
        ((chart.loc[0] + chart.size[0] / 2) * w, (chart.loc[1] + chart.size[1] / 2) * h)
    )
    assert not chart._is_within(((chart.loc[0] + chart.size[0] / 2) * w, chart.loc[1] * h - 5))
    assert not chart._is_within((chart.loc[0] * w - 5, (chart.loc[1] + chart.size[1] / 2) * h))
    assert not chart._is_within((chart.loc[0] * w - 5, chart.loc[1] * h - 5))

    chart.border_color = c_red
    assert chart.border_color == c_red
    chart.border_width = bw
    assert chart.border_width == bw
    chart.border_style = bs
    assert chart.border_style == bs
    chart.background_color = c_blue
    assert chart.background_color == c_blue

    # Check remaining properties and methods
    chart.visible = False
    assert not chart.visible
    assert not chart.GetVisible()
    chart.toggle()
    assert chart.visible
    assert chart.GetVisible()
    chart.title = title
    assert chart.title == title
    chart.legend_visible = False
    assert not chart.legend_visible


@pytest.mark.parametrize(
    "plot_f",
    (
        "line_plot_2d",
        "scatter_plot_2d",
        "area_plot",
        "bar_plot",
        "stack_plot",
        "box_plot",
        "pie_plot",
    ),
)
def test_plot_common(plot_f, request):
    # Test the common plot functionalities
    plot = request.getfixturevalue(plot_f)
    c = (1.0, 0.0, 1.0, 1.0)
    w = 5
    s = "-."
    l = "Label"

    plot.color = c
    assert plot.color == c
    assert plot.brush.color == c

    if hasattr(plot, "GetPen"):
        assert plot.pen.__this__ == plot.GetPen().__this__
    if hasattr(plot, "GetBrush"):
        assert plot.brush.__this__ == plot.GetBrush().__this__

    plot.line_width = w
    assert plot.pen.width == w
    plot.line_style = s
    assert plot.pen.style == s

    plot.label = l
    assert plot.label == l
    assert plot.GetLabel() == l

    plot.visible = False
    assert not plot.visible
    assert not plot.GetVisible()
    plot.toggle()
    assert plot.visible
    assert plot.GetVisible()


@pytest.mark.parametrize("plot_f", ("bar_plot", "stack_plot", "box_plot", "pie_plot"))
def test_multicomp_plot_common(plot_f, request):
    # Test the common multicomp plot functionalities
    plot = request.getfixturevalue(plot_f)
    cs = "spectrum"
    cs_colors = [
        (0.0, 0.0, 0.0, 1.0),
        (0.8941176470588236, 0.10196078431372549, 0.10980392156862745, 1.0),
        (0.21568627450980393, 0.49411764705882355, 0.7215686274509804, 1.0),
        (0.30196078431372547, 0.6862745098039216, 0.2901960784313726, 1.0),
        (0.596078431372549, 0.3058823529411765, 0.6392156862745098, 1.0),
        (1.0, 0.4980392156862745, 0.0, 1.0),
        (0.6509803921568628, 0.33725490196078434, 0.1568627450980392, 1.0),
    ]
    colors = [(1.0, 0.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (1.0, 1.0, 0.0, 1.0)]
    labels = ["Foo", "Spam", "Bla"]

    plot.color_scheme = cs
    assert plot.color_scheme == cs
    assert plot._color_series.GetColorScheme() == plot.COLOR_SCHEMES[cs]["id"]
    assert all(pc == cs for pc, cs in zip(plot.colors, cs_colors))
    series_colors = [
        pyvista.Color(plot._color_series.GetColor(i)).float_rgba for i in range(len(cs_colors))
    ]
    assert np.allclose(series_colors, cs_colors)
    lookup_colors = [plot._lookup_table.GetTableValue(i) for i in range(len(cs_colors))]
    assert np.allclose(lookup_colors, cs_colors)
    assert plot.brush.color == cs_colors[0]

    plot.colors = None
    assert plot.color_scheme == plot.DEFAULT_COLOR_SCHEME
    plot.colors = cs
    assert plot.color_scheme == cs
    plot.colors = colors
    assert all(pc == c for pc, c in zip(plot.colors, colors))
    series_colors = [
        pyvista.Color(plot._color_series.GetColor(i)).float_rgba for i in range(len(colors))
    ]
    assert np.allclose(series_colors, colors)
    lookup_colors = [plot._lookup_table.GetTableValue(i) for i in range(len(colors))]
    assert np.allclose(lookup_colors, colors)
    assert plot.brush.color == colors[0]

    plot.color = colors[1]
    assert plot.color == colors[1]
    assert len(plot.colors) == 1 and plot.colors[0] == colors[1]
    assert plot.brush.color == colors[1]

    plot.labels = labels
    assert tuple(plot.labels) == tuple(labels)
    assert plot.label == labels[0]
    plot.labels = None
    assert plot.labels == []
    assert plot.label == ""

    plot.label = labels[1]
    assert tuple(plot.labels) == (labels[1],)
    assert plot.label == labels[1]
    plot.label = None
    assert plot.labels == []
    assert plot.label == ""


def test_lineplot2d(line_plot_2d):
    x = [-2, -1, 0, 1, 2]
    y = [4, 1, 0, -1, -4]
    c = (1.0, 0.0, 1.0, 1.0)
    w = 5
    s = "-."
    l = "Line"

    # Test constructor
    plot = charts.LinePlot2D(x, y, c, w, s, l)
    assert np.allclose(plot.x, x)
    assert np.allclose(plot.y, y)
    assert plot.color == c
    assert plot.line_width == w
    assert plot.line_style == s
    assert plot.label == l

    # Test remaining properties
    line_plot_2d.update(x, y)
    assert np.allclose(line_plot_2d.x, x)
    assert np.allclose(line_plot_2d.y, y)


def test_scatterplot2d(scatter_plot_2d):
    x = [-2, -1, 0, 1, 2]
    y = [4, 1, 0, -1, -4]
    c = (1.0, 0.0, 1.0, 1.0)
    sz = 5
    st, st_inv = "o", "^"
    l = "Scatter"
    assert (
        st_inv not in charts.ScatterPlot2D.MARKER_STYLES
    ), "New marker styles added? Change this test."

    # Test constructor
    plot = charts.ScatterPlot2D(x, y, c, sz, st, l)
    assert np.allclose(plot.x, x)
    assert np.allclose(plot.y, y)
    assert plot.color == c
    assert plot.marker_size == sz
    assert plot.marker_style == st
    assert plot.label == l

    # Test remaining properties
    scatter_plot_2d.update(x, y)
    assert np.allclose(scatter_plot_2d.x, x)
    assert np.allclose(scatter_plot_2d.y, y)

    scatter_plot_2d.marker_size = sz
    assert scatter_plot_2d.marker_size == sz
    assert scatter_plot_2d.GetMarkerSize() == sz

    scatter_plot_2d.marker_style = None
    assert scatter_plot_2d.marker_style == ""
    scatter_plot_2d.marker_style = st
    assert scatter_plot_2d.marker_style == st
    assert scatter_plot_2d.GetMarkerStyle() == scatter_plot_2d.MARKER_STYLES[st]["id"]
    with pytest.raises(ValueError):
        scatter_plot_2d.marker_style = st_inv


def test_areaplot(area_plot):
    x = [-2, -1, 0, 1, 2]
    y1 = [4, 1, 0, -1, -4]
    y2 = [-4, -2, 0, 2, 4]
    c = (1.0, 0.0, 1.0, 1.0)
    l = "Line"

    # Test constructor
    plot = charts.AreaPlot(x, y1, y2, c, l)
    assert np.allclose(plot.x, x)
    assert np.allclose(plot.y1, y1)
    assert np.allclose(plot.y2, y2)
    assert plot.color == c
    assert plot.label == l

    # Test remaining properties
    area_plot.update(x, y1, y2)
    assert np.allclose(area_plot.x, x)
    assert np.allclose(area_plot.y1, y1)
    assert np.allclose(area_plot.y2, y2)


def test_barplot(bar_plot):
    x = [0, 1, 2]
    y = [[1, 2, 3], [2, 1, 0], [1, 1, 1]]
    c = [(1.0, 0.0, 1.0, 1.0), (1.0, 1.0, 0.0, 1.0), (0.0, 1.0, 1.0, 1.0)]
    ori, ori_inv = "H", "I"
    l = ["Foo", "Spam", "Bla"]
    assert ori_inv not in charts.BarPlot.ORIENTATIONS, "New orientations added? Change this test."

    # Test multi comp constructor
    plot = charts.BarPlot(x, y, c, ori, l)
    assert np.allclose(plot.x, x)
    assert np.allclose(plot.y, y)
    assert all(pc == ci for pc, ci in zip(plot.colors, c))
    assert plot.orientation == ori
    assert plot.labels == l

    # Test single comp constructor
    plot = charts.BarPlot(x, y[0], c[0], ori, l[0])
    assert np.allclose(plot.x, x)
    assert np.allclose(plot.y, y[0])
    assert plot.color == c[0]
    assert plot.orientation == ori
    assert plot.label == l[0]

    # Test multi and single comp constructors with inconsistent arguments
    with pytest.raises(ValueError):
        charts.BarPlot(x, y, c[0], ori, l)
    # charts.BarPlot(x, y, c, off, ori, l[0])  # This one is valid
    with pytest.raises(ValueError):
        charts.BarPlot(x, y[0], c, ori, l[0])
    with pytest.raises(ValueError):
        charts.BarPlot(x, y[0], c[0], ori, l)

    # Test remaining properties
    bar_plot.update(x, y)
    assert np.allclose(bar_plot.x, x)
    assert np.allclose(bar_plot.y, y)

    bar_plot.orientation = ori
    assert bar_plot.orientation == ori
    assert bar_plot.GetOrientation() == bar_plot.ORIENTATIONS[ori]
    with pytest.raises(ValueError):
        bar_plot.orientation = ori_inv


def test_stackplot(stack_plot):
    x = [0, 1, 2]
    ys = [[1, 2, 3], [2, 1, 0], [1, 1, 1]]
    c = [(1.0, 0.0, 1.0, 1.0), (1.0, 1.0, 0.0, 1.0), (0.0, 1.0, 1.0, 1.0)]
    l = ["Foo", "Spam", "Bla"]

    # Test multi comp constructor
    plot = charts.StackPlot(x, ys, c, l)
    assert np.allclose(plot.x, x)
    assert np.allclose(plot.ys, ys)
    assert all(pc == ci for pc, ci in zip(plot.colors, c))
    assert plot.labels == l

    # Test single comp constructor
    plot = charts.StackPlot(x, ys[0], c[0], l[0])
    assert np.allclose(plot.x, x)
    assert np.allclose(plot.ys, ys[0])
    assert plot.color == c[0]
    assert plot.label == l[0]

    # Test multi and single comp constructors with inconsistent arguments
    with pytest.raises(ValueError):
        charts.StackPlot(x, ys, c[0], l)
    # charts.StackPlot(x, ys, c, l[0])  # This one is valid
    with pytest.raises(ValueError):
        charts.StackPlot(x, ys[0], c, l[0])
    with pytest.raises(ValueError):
        charts.StackPlot(x, ys[0], c[0], l)

    # Test remaining properties
    stack_plot.update(x, ys)
    assert np.allclose(stack_plot.x, x)
    assert np.allclose(stack_plot.ys, ys)


@skip_no_plotting
def test_chart_2d(pl, chart_2d):
    size = (0.5, 0.5)
    loc = (0.25, 0.25)
    lx = "X label"
    ly = "Y label"
    rx = [0, 5]
    ry = [0, 1]
    x = np.arange(11) - 5
    y = x**2
    ys = [np.sin(x), np.cos(x), np.tanh(x)]
    col = (1.0, 0.0, 1.0, 1.0)
    cs = "citrus"
    sz = 5
    ms = "d"
    w = 10
    ls = "-."
    ori = "V"

    # Test constructor
    chart = pyvista.Chart2D(size, loc, lx, ly, False)
    assert chart.size == size
    assert chart.loc == loc
    assert chart.x_label == lx
    assert chart.y_label == ly
    assert not chart.grid

    # Test geometry and resizing
    pl.add_chart(chart)
    r_w, r_h = chart._renderer.GetSize()
    pl.show(auto_close=False)
    assert np.allclose(chart._geometry, (loc[0] * r_w, loc[1] * r_h, size[0] * r_w, size[1] * r_h))
    pl.window_size = (int(pl.window_size[0] / 2), int(pl.window_size[1] / 2))
    pl.show()  # This will also call chart._resize
    assert np.allclose(
        chart._geometry, (loc[0] * r_w / 2, loc[1] * r_h / 2, size[0] * r_w / 2, size[1] * r_h / 2)
    )

    # Test parse_format
    hex_colors = ["#fa09b6", "0xa53a8d", "#b02239f0", "0xcee6927f"]
    colors = itertools.chain(pyvista.hexcolors, pyvista.colors.color_synonyms, [*hex_colors, ""])
    for m in charts.ScatterPlot2D.MARKER_STYLES:
        for l in charts.Pen.LINE_STYLES:
            for c in colors:
                cp = "b" if c == "" else c
                assert (m, l, cp) == chart_2d._parse_format(m + l + c)
                assert (m, l, cp) == chart_2d._parse_format(m + c + l)
                assert (m, l, cp) == chart_2d._parse_format(l + m + c)
                assert (m, l, cp) == chart_2d._parse_format(l + c + m)
                assert (m, l, cp) == chart_2d._parse_format(c + m + l)
                assert (m, l, cp) == chart_2d._parse_format(c + l + m)

    # Test plotting methods
    s, l = chart_2d.plot(x, y, "")
    assert s is None and l is None
    assert len([*chart_2d.plots()]) == 0
    s, l = chart_2d.plot(y, "-")
    assert s is None and l is not None
    assert l in chart_2d.plots("line")
    chart_2d.remove_plot(l)
    assert len([*chart_2d.plots()]) == 0
    s, l = chart_2d.plot(y, "x")
    assert s is not None and l is None
    assert s in chart_2d.plots("scatter")
    chart_2d.clear("scatter")
    assert len([*chart_2d.plots()]) == 0
    s, l = chart_2d.plot(x, y, "x-")
    assert s is not None and l is not None
    assert s in chart_2d.plots("scatter") and l in chart_2d.plots("line")
    chart_2d.plot(x, y, "x-")  # Check clearing of multiple plots (of the same type)
    chart_2d.clear()
    assert len([*chart_2d.plots()]) == 0

    s = chart_2d.scatter(x, y, col, sz, ms, lx)
    assert np.allclose(s.x, x)
    assert np.allclose(s.y, y)
    assert s.color == col
    assert s.marker_size == sz
    assert s.marker_style == ms
    assert s.label == lx
    assert s in chart_2d.plots("scatter")
    assert chart_2d.GetPlotIndex(s) >= 0

    l = chart_2d.line(x, y, col, w, ls, lx)
    assert np.allclose(l.x, x)
    assert np.allclose(l.y, y)
    assert l.color == col
    assert l.line_width == w
    assert l.line_style == ls
    assert l.label == lx
    assert l in chart_2d.plots("line")
    assert chart_2d.GetPlotIndex(l) >= 0

    a = chart_2d.area(x, -y, y, col, lx)
    assert np.allclose(a.x, x)
    assert np.allclose(a.y1, -y)
    assert np.allclose(a.y2, y)
    assert a.color == col
    assert a.label == lx
    assert a in chart_2d.plots("area")
    assert chart_2d.GetPlotIndex(a) >= 0

    b = chart_2d.bar(x, -y, col, ori, lx)
    assert np.allclose(b.x, x)
    assert np.allclose(b.y, -y)
    assert b.color == col
    assert b.orientation == ori
    assert b.label == lx
    assert b in chart_2d.plots("bar")
    assert chart_2d.GetPlotIndex(b) >= 0

    s = chart_2d.stack(x, ys, cs, [lx, ly])
    assert np.allclose(s.x, x)
    assert np.allclose(s.ys, ys)
    assert s.color_scheme == cs
    assert tuple(s.labels) == (lx, ly)
    assert s in chart_2d.plots("stack")
    assert chart_2d.GetPlotIndex(s) >= 0

    inv_type = "blub"
    with pytest.raises(KeyError):
        next(chart_2d.plots(inv_type))
    with pytest.raises(KeyError):
        chart_2d.clear(inv_type)
    assert len([*chart_2d.plots()]) == 5
    chart_2d.clear()
    assert len([*chart_2d.plots()]) == 0
    with pytest.raises(ValueError):
        chart_2d.remove_plot(s)

    # Check remaining properties
    assert chart_2d.x_axis.__this__ == chart_2d.GetAxis(charts.Axis.BOTTOM).__this__
    assert chart_2d.y_axis.__this__ == chart_2d.GetAxis(charts.Axis.LEFT).__this__

    chart_2d.x_label = lx
    assert chart_2d.x_label == lx
    assert chart_2d.x_axis.label == lx
    chart_2d.y_label = ly
    assert chart_2d.y_label == ly
    assert chart_2d.y_axis.label == ly

    chart_2d.x_range = rx
    assert np.allclose(chart_2d.x_range, rx)
    assert np.allclose(chart_2d.x_axis.range, rx)
    chart_2d.y_range = ry
    assert np.allclose(chart_2d.y_range, ry)
    assert np.allclose(chart_2d.y_axis.range, ry)

    chart_2d.grid = True
    assert chart_2d.grid
    assert chart_2d.x_axis.grid and chart_2d.y_axis.grid

    chart_2d.hide_axes()
    for axis in (chart_2d.x_axis, chart_2d.y_axis):
        assert not (
            axis.visible
            or axis.label_visible
            or axis.ticks_visible
            or axis.tick_labels_visible
            or axis.grid
        )


@skip_no_plotting
def test_chart_box(pl, chart_box, box_plot):
    data = [[0, 1, 1, 1, 2, 2, 3, 4, 4, 5, 5, 5, 6]]
    stats = [np.quantile(d, [0.0, 0.25, 0.5, 0.75, 1.0]) for d in data]
    cs = "wild_flower"
    ls = ["Datalabel"]

    # Test constructor
    chart = pyvista.ChartBox(data, cs, ls)
    assert np.allclose(chart.plot.data, data)
    assert chart.plot.color_scheme == cs
    assert tuple(chart.plot.labels) == tuple(ls)

    # Test geometry and resizing
    pl.add_chart(chart)
    r_w, r_h = chart._renderer.GetSize()
    pl.show(auto_close=False)
    assert np.allclose(chart._geometry, (0, 0, r_w, r_h))
    pl.window_size = (int(pl.window_size[0] / 2), int(pl.window_size[1] / 2))
    pl.show(auto_close=False)  # This will also call chart._resize
    assert np.allclose(chart._geometry, (0, 0, r_w / 2, r_h / 2))

    # Test remaining properties
    assert chart_box.loc == (0, 0)
    assert chart_box.size == (1, 1)
    assert chart_box.plot.__this__ == chart_box.GetPlot(0).__this__

    box_plot.update(data)
    assert np.allclose(box_plot.data, data)
    assert np.allclose(box_plot.stats, stats)


@skip_no_plotting
def test_chart_pie(pl, chart_pie, pie_plot):
    data = [3, 4, 5]
    cs = "wild_flower"
    ls = ["Tic", "Tac", "Toe"]

    # Test constructor
    chart = pyvista.ChartPie(data, cs, ls)
    assert np.allclose(chart.plot.data, data)
    assert chart.plot.color_scheme == cs
    assert tuple(chart.plot.labels) == tuple(ls)

    # Test geometry and resizing
    pl.add_chart(chart)
    r_w, r_h = chart._renderer.GetSize()
    pl.show(auto_close=False)
    assert np.allclose(chart._geometry, (0, 0, r_w, r_h))
    pl.window_size = (int(pl.window_size[0] / 2), int(pl.window_size[1] / 2))
    pl.show(auto_close=False)  # This will also call chart._resize
    assert np.allclose(chart._geometry, (0, 0, r_w / 2, r_h / 2))

    # Test remaining properties
    assert chart_pie.loc == (0, 0)
    assert chart_pie.size == (1, 1)
    assert chart_pie.plot.__this__ == chart_pie.GetPlot(0).__this__

    pie_plot.update(data)
    assert np.allclose(pie_plot.data, data)


@skip_no_plotting
@skip_no_mpl_figure
def test_chart_mpl(pl, chart_mpl):
    import matplotlib.pyplot as plt

    size = (0.5, 0.5)
    loc = (0.25, 0.25)

    # Test constructor
    f, ax = plt.subplots()
    chart = pyvista.ChartMPL(f, size, loc)
    assert chart.size == size
    assert chart.loc == loc

    # Test geometry and resizing
    pl.add_chart(chart)
    r_w, r_h = chart._renderer.GetSize()
    pl.show(auto_close=False)
    assert np.allclose(chart._geometry, (loc[0] * r_w, loc[1] * r_h, size[0] * r_w, size[1] * r_h))
    assert np.allclose(chart.position, (loc[0] * r_w, loc[1] * r_h))
    assert np.allclose(chart._canvas.get_width_height(), (size[0] * r_w, size[1] * r_h))
    pl.window_size = (int(pl.window_size[0] / 2), int(pl.window_size[1] / 2))
    pl.show(auto_close=False)  # This will also call chart._resize
    assert np.allclose(
        chart._geometry, (loc[0] * r_w / 2, loc[1] * r_h / 2, size[0] * r_w / 2, size[1] * r_h / 2)
    )
    assert np.allclose(chart.position, (loc[0] * r_w / 2, loc[1] * r_h / 2))
    assert np.allclose(chart._canvas.get_width_height(), (size[0] * r_w / 2, size[1] * r_h / 2))

    # test set position throw
    with pytest.raises(ValueError, match="must be length 2"):
        chart.position = (1, 2, 3)


@skip_no_plotting
def test_charts(pl):
    win_size = pl.window_size
    top_left = pyvista.Chart2D(size=(0.5, 0.5), loc=(0, 0.5))
    bottom_right = pyvista.Chart2D(size=(0.5, 0.5), loc=(0.5, 0))

    # Test add_chart
    pl.add_chart(top_left)
    assert pl.renderer.__this__ == top_left._renderer.__this__
    assert pl.renderer._charts._scene.__this__ == top_left._scene.__this__
    pl.add_chart(bottom_right)
    assert len(pl.renderer._charts) == 2

    # Test toggle_interaction
    pl.show(auto_close=False)  # We need to plot once to let the charts compute their true geometry
    assert not top_left.GetInteractive()
    assert not bottom_right.GetInteractive()
    assert (
        pl.renderer._charts.toggle_interaction((0.75 * win_size[0], 0.25 * win_size[1]))
        is bottom_right._scene
    )
    assert not top_left.GetInteractive()
    assert bottom_right.GetInteractive()
    assert pl.renderer._charts.toggle_interaction((0, 0)) is None
    assert not top_left.GetInteractive()
    assert not bottom_right.GetInteractive()

    # Test remove_chart
    pl.remove_chart(1)
    assert len(pl.renderer._charts) == 1
    assert pl.renderer._charts[0] == top_left
    assert top_left in pl.renderer._charts
    pl.remove_chart(top_left)
    assert len(pl.renderer._charts) == 0

    # Test deep_clean
    pl.add_chart(top_left, bottom_right)
    pl.deep_clean()
    assert len(pl.renderer._charts) == 0
    assert pl.renderer._charts._scene is None

    pl.add_chart(top_left, bottom_right)
    pl.clear()  # also calls deep_clean()
    assert len(pl.renderer._charts) == 0
    assert pl.renderer._charts._scene is None


@skip_no_plotting
def test_iren_context_style(pl):
    chart = pyvista.Chart2D(size=(0.5, 0.5), loc=(0.5, 0.5))
    win_size = pl.window_size
    pl.add_chart(chart)
    pl.show(auto_close=False)  # We need to plot once to let the charts compute their true geometry
    style = pl.iren._style
    style_class = pl.iren._style_class

    # Simulate double left click on the chart:
    pl.iren._mouse_left_button_click(int(0.75 * win_size[0]), int(0.75 * win_size[1]), count=2)
    assert chart.GetInteractive()
    assert pl.iren._style == "Context"
    assert pl.iren._style_class == pl.iren._context_style
    assert pl.iren._context_style.GetScene().__this__ == chart._scene.__this__

    # Simulate right click outside the chart:
    pl.iren._mouse_left_button_click(0, 0, count=2)
    assert not chart.GetInteractive()
    assert pl.iren._style == style
    assert pl.iren._style_class == style_class
    assert pl.iren._context_style.GetScene() is None


@skip_mac
def test_get_background_texture(chart_2d):
    t_puppy = examples.download_puppy_texture()
    chart_2d
    chart_2d.background_texture = t_puppy
    assert chart_2d.background_texture == t_puppy


# tests/test_colors.py

import pytest

from pyvista.plotting.colors import get_cmap_safe

COLORMAPS = []

try:
    import matplotlib

    COLORMAPS.append('Greys')
    HAS_MATPLOTLIB = True
except:
    HAS_MATPLOTLIB = False

try:
    import cmocean  # noqa: F401

    COLORMAPS.append('algae')
except ImportError:
    pass


try:
    import colorcet  # noqa: F401

    COLORMAPS.append('fire')
except:
    pass


@pytest.mark.skipif(not HAS_MATPLOTLIB, reason='Requires matplotlib')
@pytest.mark.parametrize("cmap", COLORMAPS)
def test_get_cmap_safe(cmap):
    assert isinstance(get_cmap_safe(cmap), matplotlib.colors.LinearSegmentedColormap)


# tests/test_composite.py

import pathlib
import platform
import weakref

import numpy as np
import pytest
import vtk

import pyvista
from pyvista import (
    MultiBlock,
    PolyData,
    RectilinearGrid,
    StructuredGrid,
    UniformGrid,
    examples as ex,
)

skip_mac = pytest.mark.skipif(platform.system() == 'Darwin', reason="Flaky Mac tests")


@pytest.fixture()
def vtk_multi():
    return vtk.vtkMultiBlockDataSet()


@pytest.fixture()
def pyvista_multi():
    return pyvista.MultiBlock


def multi_from_datasets(*datasets):
    """Return pyvista multiblock composed of any number of datasets."""
    return MultiBlock(datasets)


def test_multi_block_init_vtk():
    multi = vtk.vtkMultiBlockDataSet()
    multi.SetBlock(0, vtk.vtkRectilinearGrid())
    multi.SetBlock(1, vtk.vtkStructuredGrid())
    multi = MultiBlock(multi)
    assert isinstance(multi, MultiBlock)
    assert multi.n_blocks == 2
    assert isinstance(multi.GetBlock(0), RectilinearGrid)
    assert isinstance(multi.GetBlock(1), StructuredGrid)
    multi = vtk.vtkMultiBlockDataSet()
    multi.SetBlock(0, vtk.vtkRectilinearGrid())
    multi.SetBlock(1, vtk.vtkStructuredGrid())
    multi = MultiBlock(multi, deep=True)
    assert isinstance(multi, MultiBlock)
    assert multi.n_blocks == 2
    assert isinstance(multi.GetBlock(0), RectilinearGrid)
    assert isinstance(multi.GetBlock(1), StructuredGrid)
    # Test nested structure
    multi = vtk.vtkMultiBlockDataSet()
    multi.SetBlock(0, vtk.vtkRectilinearGrid())
    multi.SetBlock(1, vtk.vtkImageData())
    nested = vtk.vtkMultiBlockDataSet()
    nested.SetBlock(0, vtk.vtkUnstructuredGrid())
    nested.SetBlock(1, vtk.vtkStructuredGrid())
    multi.SetBlock(2, nested)
    # Wrap the nested structure
    multi = MultiBlock(multi)
    assert isinstance(multi, MultiBlock)
    assert multi.n_blocks == 3
    assert isinstance(multi.GetBlock(0), RectilinearGrid)
    assert isinstance(multi.GetBlock(1), UniformGrid)
    assert isinstance(multi.GetBlock(2), MultiBlock)


def test_multi_block_init_dict(rectilinear, airplane):
    data = {'grid': rectilinear, 'poly': airplane}
    multi = MultiBlock(data)
    assert isinstance(multi, MultiBlock)
    assert multi.n_blocks == 2
    # Note that dictionaries do not maintain order
    assert isinstance(multi.GetBlock(0), (RectilinearGrid, PolyData))
    assert multi.get_block_name(0) in ['grid', 'poly']
    assert isinstance(multi.GetBlock(1), (RectilinearGrid, PolyData))
    assert multi.get_block_name(1) in ['grid', 'poly']


def test_multi_block_keys(rectilinear, airplane):
    data = {'grid': rectilinear, 'poly': airplane}
    multi = MultiBlock(data)
    assert len(multi.keys()) == 2
    assert 'grid' in multi.keys()
    assert 'poly' in multi.keys()


def test_multi_block_init_list(rectilinear, airplane):
    data = [rectilinear, airplane]
    multi = MultiBlock(data)
    assert isinstance(multi, MultiBlock)
    assert multi.n_blocks == 2
    assert isinstance(multi.GetBlock(0), RectilinearGrid)
    assert isinstance(multi.GetBlock(1), PolyData)


def test_multi_block_append(ant, sphere, uniform, airplane, rectilinear):
    """This puts all of the example data objects into a a MultiBlock container"""
    multi = MultiBlock()
    # Add and test examples
    datasets = (ant, sphere, uniform, airplane, rectilinear)
    for i, dataset in enumerate(datasets):
        multi.append(dataset)
        assert multi.n_blocks == i + 1
        assert isinstance(multi[i], type(dataset))
    assert multi.bounds is not None
    # Now overwrite a block
    multi[4] = pyvista.Sphere()
    assert isinstance(multi[4], PolyData)
    multi[4] = vtk.vtkUnstructuredGrid()
    assert isinstance(multi[4], pyvista.UnstructuredGrid)

    with pytest.raises(ValueError, match="Cannot nest a composite dataset in itself."):
        multi.append(multi)


def test_multi_block_set_get_ers():
    """This puts all of the example data objects into a a MultiBlock container"""
    multi = MultiBlock()
    # Set the number of blocks
    multi.n_blocks = 6
    assert multi.GetNumberOfBlocks() == 6  # Check that VTK side registered it
    assert multi.n_blocks == 6  # Check pyvista side registered it
    # Add data to the MultiBlock
    data = ex.load_rectilinear()
    multi[1] = data
    multi.set_block_name(1, 'rect')
    # Make sure number of blocks is constant
    assert multi.n_blocks == 6
    # Check content
    assert isinstance(multi[1], RectilinearGrid)
    for i in [0, 2, 3, 4, 5]:
        assert multi[i] is None
    # Check the bounds
    assert multi.bounds == list(data.bounds)
    multi[5] = ex.load_uniform()
    multi.set_block_name(5, 'uni')
    multi.set_block_name(5, None)  # Make sure it doesn't get overwritten
    assert isinstance(multi.get(5), UniformGrid)
    # Test get by name
    assert isinstance(multi['uni'], UniformGrid)
    assert isinstance(multi['rect'], RectilinearGrid)
    assert isinstance(multi.get('uni'), UniformGrid)
    assert multi.get('no key') is None
    assert multi.get('no key', default=pyvista.Sphere()) == pyvista.Sphere()
    # Test the del operator
    del multi[0]
    assert multi.n_blocks == 5
    # Make sure the rect grid was moved up
    assert isinstance(multi[0], RectilinearGrid)
    assert multi.get_block_name(0) == 'rect'
    assert multi.get_block_name(2) is None
    # test del by name
    del multi['uni']
    assert multi.n_blocks == 4
    # test the pop operator
    pop = multi.pop(0)
    assert isinstance(pop, RectilinearGrid)
    assert multi.n_blocks == 3
    assert all([k is None for k in multi.keys()])

    multi["new key"] = pyvista.Sphere()
    assert multi.n_blocks == 4
    assert multi[3] == pyvista.Sphere()

    multi["new key"] = pyvista.Cube()
    assert multi.n_blocks == 4
    assert multi[3] == pyvista.Cube()

    with pytest.raises(KeyError):
        _ = multi.get_index_by_name('foo')

    with pytest.raises(IndexError):
        multi[4] = UniformGrid()

    with pytest.raises(KeyError):
        multi["not a key"]
    with pytest.raises(TypeError):
        data = multi[[0, 1]]

    with pytest.raises(TypeError):
        multi[1, 'foo'] = data


def test_replace():
    spheres = {f"{i}": pyvista.Sphere(phi_resolution=i + 3) for i in range(10)}
    multi = MultiBlock(spheres)
    cube = pyvista.Cube()
    multi.replace(3, cube)
    assert multi.get_block_name(3) == "3"
    assert multi[3] is cube


def test_pop():
    spheres = {f"{i}": pyvista.Sphere(phi_resolution=i + 3) for i in range(10)}
    multi = MultiBlock(spheres)
    assert multi.pop() == spheres["9"]
    assert spheres["9"] not in multi
    assert multi.pop(0) == spheres["0"]
    assert spheres["0"] not in multi


def test_del_slice(sphere):
    multi = MultiBlock({f"{i}": sphere for i in range(10)})
    del multi[0:10:2]
    assert len(multi) == 5
    assert all([f"{i}" in multi.keys() for i in range(1, 10, 2)])

    multi = MultiBlock({f"{i}": sphere for i in range(10)})
    del multi[5:2:-1]
    assert len(multi) == 7
    assert all([f"{i}" in multi.keys() for i in [0, 1, 2, 6, 7, 8, 9]])


def test_slicing_multiple_in_setitem(sphere):
    # equal length
    multi = MultiBlock({f"{i}": sphere for i in range(10)})
    multi[1:3] = [pyvista.Cube(), pyvista.Cube()]
    assert multi[1] == pyvista.Cube()
    assert multi[2] == pyvista.Cube()
    assert multi.count(pyvista.Cube()) == 2
    assert len(multi) == 10

    # len(slice) < len(data)
    multi = MultiBlock({f"{i}": sphere for i in range(10)})
    multi[1:3] = [pyvista.Cube(), pyvista.Cube(), pyvista.Cube()]
    assert multi[1] == pyvista.Cube()
    assert multi[2] == pyvista.Cube()
    assert multi[3] == pyvista.Cube()
    assert multi.count(pyvista.Cube()) == 3
    assert len(multi) == 11

    # len(slice) > len(data)
    multi = MultiBlock({f"{i}": sphere for i in range(10)})
    multi[1:3] = [pyvista.Cube()]
    assert multi[1] == pyvista.Cube()
    assert multi.count(pyvista.Cube()) == 1
    assert len(multi) == 9


def test_reverse(sphere):
    multi = MultiBlock({f"{i}": sphere for i in range(3)})
    multi.append(pyvista.Cube(), "cube")
    multi.reverse()
    assert multi[0] == pyvista.Cube()
    assert np.array_equal(multi.keys(), ["cube", "2", "1", "0"])


def test_insert(sphere):
    multi = MultiBlock({f"{i}": sphere for i in range(3)})
    cube = pyvista.Cube()
    multi.insert(0, cube)
    assert len(multi) == 4
    assert multi[0] is cube

    # test with negative index and name
    multi.insert(-1, pyvista.UniformGrid(), name="uni")
    assert len(multi) == 5
    # inserted before last element
    assert isinstance(multi[-2], pyvista.UniformGrid)  # inserted before last element
    assert multi.get_block_name(-2) == "uni"


def test_extend(sphere, uniform, ant):
    # test with Iterable
    multi = MultiBlock([sphere, ant])
    new_multi = [uniform, uniform]
    multi.extend(new_multi)
    assert len(multi) == 4
    assert multi.count(uniform) == 2

    # test with a MultiBlock
    multi = MultiBlock([sphere, ant])
    new_multi = MultiBlock({"uniform1": uniform, "uniform2": uniform})
    multi.extend(new_multi)
    assert len(multi) == 4
    assert multi.count(uniform) == 2
    assert multi.keys()[-2] == "uniform1"
    assert multi.keys()[-1] == "uniform2"


def test_multi_block_clean(rectilinear, uniform, ant):
    # now test a clean of the null values
    multi = MultiBlock()
    multi.n_blocks = 6
    multi[1] = rectilinear
    multi.set_block_name(1, 'rect')
    multi[2] = PolyData()
    multi.set_block_name(2, 'empty')
    multi[3] = MultiBlock()
    multi.set_block_name(3, 'mempty')
    multi[5] = uniform
    multi.set_block_name(5, 'uni')
    # perform the clean to remove all Null elements
    multi.clean()
    assert multi.n_blocks == 2
    assert multi.GetNumberOfBlocks() == 2
    assert isinstance(multi[0], RectilinearGrid)
    assert isinstance(multi[1], UniformGrid)
    assert multi.get_block_name(0) == 'rect'
    assert multi.get_block_name(1) == 'uni'
    # Test a nested data struct
    foo = MultiBlock()
    foo.n_blocks = 4
    foo[3] = ant
    assert foo.n_blocks == 4
    multi = MultiBlock()
    multi.n_blocks = 6
    multi[1] = rectilinear
    multi.set_block_name(1, 'rect')
    multi[5] = foo
    multi.set_block_name(5, 'multi')
    # perform the clean to remove all Null elements
    assert multi.n_blocks == 6
    multi.clean()
    assert multi.n_blocks == 2
    assert multi.GetNumberOfBlocks() == 2
    assert isinstance(multi[0], RectilinearGrid)
    assert isinstance(multi[1], MultiBlock)
    assert multi.get_block_name(0) == 'rect'
    assert multi.get_block_name(1) == 'multi'
    assert foo.n_blocks == 1


def test_multi_block_repr(ant, sphere, uniform, airplane):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, None)
    # Now check everything
    assert multi.n_blocks == 5
    assert multi._repr_html_() is not None
    assert repr(multi) is not None
    assert str(multi) is not None


def test_multi_block_eq(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, globe)
    other = multi.copy()

    assert multi is not other
    assert multi == other

    assert pyvista.MultiBlock() == pyvista.MultiBlock()

    other[0] = pyvista.Sphere()
    assert multi != other

    other = multi.copy()
    other.set_block_name(0, "not matching")
    assert multi != other

    other = multi.copy()
    other.append(pyvista.Sphere())
    assert multi != other


@pytest.mark.parametrize('binary', [True, False])
@pytest.mark.parametrize('extension', pyvista.core.composite.MultiBlock._WRITERS)
@pytest.mark.parametrize('use_pathlib', [True, False])
def test_multi_block_io(
    extension, binary, tmpdir, use_pathlib, ant, sphere, uniform, airplane, globe
):
    filename = str(tmpdir.mkdir("tmpdir").join(f'tmp.{extension}'))
    if use_pathlib:
        pathlib.Path(filename)
    multi = multi_from_datasets(ant, sphere, uniform, airplane, globe)
    # Now check everything
    assert multi.n_blocks == 5
    # Save it out
    multi.save(filename, binary)
    foo = MultiBlock(filename)
    assert foo.n_blocks == multi.n_blocks
    foo = pyvista.read(filename)
    assert foo.n_blocks == multi.n_blocks


@skip_mac  # fails due to download examples
@pytest.mark.parametrize('binary', [True, False])
@pytest.mark.parametrize('extension', ['vtm', 'vtmb'])
def test_ensight_multi_block_io(extension, binary, tmpdir, ant, sphere, uniform, airplane, globe):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.%s' % extension))
    # multi = ex.load_bfs()  # .case file
    multi = ex.download_backward_facing_step()  # .case file
    # Now check everything
    assert multi.n_blocks == 4
    array_names = ['v2', 'nut', 'k', 'nuTilda', 'p', 'omega', 'f', 'epsilon', 'U']
    for block in multi:
        assert block.array_names == array_names
    # Save it out
    multi.save(filename, binary)
    foo = MultiBlock(filename)
    assert foo.n_blocks == multi.n_blocks
    for block in foo:
        assert block.array_names == array_names
    foo = pyvista.read(filename)
    assert foo.n_blocks == multi.n_blocks
    for block in foo:
        assert block.array_names == array_names


def test_invalid_arg():
    with pytest.raises(TypeError):
        pyvista.MultiBlock(np.empty(10))
    with pytest.raises(ValueError):
        pyvista.MultiBlock(np.empty(10), np.empty(10))


def test_multi_io_erros(tmpdir):
    fdir = tmpdir.mkdir("tmpdir")
    multi = MultiBlock()
    # Check saving with bad extension
    bad_ext_name = str(fdir.join('tmp.npy'))
    with pytest.raises(ValueError):
        multi.save(bad_ext_name)
    arr = np.random.rand(10, 10)
    np.save(bad_ext_name, arr)
    # Load non existing file
    with pytest.raises(FileNotFoundError):
        _ = MultiBlock('foo.vtm')
    # Load bad extension
    with pytest.raises(IOError):
        _ = MultiBlock(bad_ext_name)


def test_extract_geometry(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform)
    nested = multi_from_datasets(airplane, globe)
    multi.append(nested)
    # Now check everything
    assert multi.n_blocks == 4
    # Now apply the geometry filter to combine a plethora of data blocks
    geom = multi.extract_geometry()
    assert isinstance(geom, PolyData)


def test_combine_filter(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform)
    nested = multi_from_datasets(airplane, globe)
    multi.append(nested)
    # Now check everything
    assert multi.n_blocks == 4
    # Now apply the append filter to combine a plethora of data blocks
    geom = multi.combine()
    assert isinstance(geom, pyvista.UnstructuredGrid)


def test_multi_block_copy(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, globe)
    # Now check everything
    multi_copy = multi.copy()
    assert multi.n_blocks == 5 == multi_copy.n_blocks
    assert id(multi[0]) != id(multi_copy[0])
    assert id(multi[-1]) != id(multi_copy[-1])
    for i in range(multi_copy.n_blocks):
        assert pyvista.is_pyvista_dataset(multi_copy.GetBlock(i))
    # Now check shallow
    multi_copy = multi.copy(deep=False)
    assert multi.n_blocks == 5 == multi_copy.n_blocks
    assert id(multi[0]) == id(multi_copy[0])
    assert id(multi[-1]) == id(multi_copy[-1])
    for i in range(multi_copy.n_blocks):
        assert pyvista.is_pyvista_dataset(multi_copy.GetBlock(i))


def test_multi_block_negative_index(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, globe)
    # Now check everything
    assert id(multi[-1]) == id(multi[4])
    assert id(multi[-2]) == id(multi[3])
    assert id(multi[-3]) == id(multi[2])
    assert id(multi[-4]) == id(multi[1])
    assert id(multi[-5]) == id(multi[0])
    with pytest.raises(IndexError):
        _ = multi[-6]

    multi[-1] = ant
    assert multi[4] == ant
    multi[-5] = globe
    assert multi[0] == globe

    with pytest.raises(IndexError):
        multi[-6] = uniform


def test_multi_slice_index(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, globe)
    # Now check everything
    sub = multi[0:3]
    assert len(sub) == 3
    for i in range(len(sub)):
        assert sub[i] is multi[i]
        assert sub.get_block_name(i) == multi.get_block_name(i)
    sub = multi[0:-1]
    assert len(sub) + 1 == len(multi)
    for i in range(len(sub)):
        assert sub[i] is multi[i]
        assert sub.get_block_name(i) == multi.get_block_name(i)
    sub = multi[0:-1:2]
    assert len(sub) == 2
    for i in range(len(sub)):
        j = i * 2
        assert sub[i] is multi[j]
        assert sub.get_block_name(i) == multi.get_block_name(j)

    sub = [airplane, globe]
    multi[0:2] = sub
    assert multi[0] is airplane
    assert multi[1] is globe


def test_slice_defaults(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, globe)
    assert multi[:] == multi[0 : len(multi)]


def test_slice_negatives(ant, sphere, uniform, airplane, globe):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, globe)
    test_multi = pyvista.MultiBlock({key: multi[key] for key in multi.keys()[::-1]})
    assert multi[::-1] == test_multi

    test_multi = pyvista.MultiBlock({key: multi[key] for key in multi.keys()[-2:]})
    assert multi[-2:] == test_multi

    test_multi = pyvista.MultiBlock({key: multi[key] for key in multi.keys()[:-1]})
    assert multi[:-1] == test_multi

    test_multi = pyvista.MultiBlock({key: multi[key] for key in multi.keys()[-1:-4:-2]})
    assert multi[-1:-4:-2] == test_multi


def test_multi_block_volume(ant, airplane, sphere, uniform):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, None)
    vols = ant.volume + sphere.volume + uniform.volume + airplane.volume
    assert multi.volume == pytest.approx(vols)


def test_multi_block_length(ant, sphere, uniform, airplane):
    multi = multi_from_datasets(ant, sphere, uniform, airplane, None)
    assert multi.length


def test_multi_block_save_lines(tmpdir):
    radius = 1
    xr = np.random.random(10)
    yr = np.random.random(10)
    x = radius * np.sin(yr) * np.cos(xr)
    y = radius * np.sin(yr) * np.sin(xr)
    z = radius * np.cos(yr)
    xyz = np.stack((x, y, z), axis=1)

    poly = pyvista.lines_from_points(xyz, close=False)
    blocks = pyvista.MultiBlock()
    for _ in range(2):
        blocks.append(poly)

    path = tmpdir.mkdir("tmpdir")
    line_filename = str(path.join('lines.vtk'))
    block_filename = str(path.join('blocks.vtmb'))
    poly.save(line_filename)
    blocks.save(block_filename)

    poly_load = pyvista.read(line_filename)
    assert np.allclose(poly_load.points, poly.points)

    blocks_load = pyvista.read(block_filename)
    assert np.allclose(blocks_load[0].points, blocks[0].points)


def test_multi_block_data_range():
    volume = pyvista.Wavelet()
    a = volume.slice_along_axis(5, 'x')
    with pytest.raises(KeyError):
        a.get_data_range('foo')
    mi, ma = a.get_data_range(volume.active_scalars_name)
    assert mi is not None
    assert ma is not None
    # Test on a nested MultiBlock
    b = volume.slice_along_axis(5, 'y')
    slices = pyvista.MultiBlock([a, b])
    with pytest.raises(KeyError):
        slices.get_data_range('foo')
    mi, ma = slices.get_data_range(volume.active_scalars_name)
    assert mi is not None
    assert ma is not None


def test_multiblock_ref():
    # can't use fixtures here as we need to remove all references for
    # garbage collection
    sphere = pyvista.Sphere()
    cube = pyvista.Cube()

    block = MultiBlock([sphere, cube])
    block[0]["a_new_var"] = np.zeros(block[0].n_points)
    assert "a_new_var" in block[0].array_names

    assert sphere is block[0]
    assert cube is block[1]

    wref_sphere = weakref.ref(sphere)
    wref_cube = weakref.ref(cube)

    # verify reference remains
    assert wref_sphere() is sphere
    del sphere
    assert wref_sphere() is not None

    # verify __delitem__ works and removes reference
    del block[0]
    assert wref_sphere() is None

    # verify reference remains
    assert wref_cube() is cube

    # verify the __setitem__(index, None) edge case
    del cube
    block[0] = None
    assert wref_cube() is None


def test_set_active_scalars(multiblock_all):
    for block in multiblock_all:
        block.clear_data()
        block.point_data['data'] = range(block.n_points)
        block.point_data['point_data_a'] = range(block.n_points)
        block.point_data['point_data_b'] = range(block.n_points)

        block.cell_data['data'] = range(block.n_cells)
        block.cell_data['cell_data_a'] = range(block.n_cells)
        block.cell_data['cell_data_b'] = range(block.n_cells)

    # test none
    multiblock_all.set_active_scalars(None)
    for block in multiblock_all:
        assert block.point_data.active_scalars_name is None
        assert block.cell_data.active_scalars_name is None

    # test set point_data
    active_scalars_name = 'point_data_a'
    multiblock_all.set_active_scalars(active_scalars_name)
    for block in multiblock_all:
        assert block.point_data.active_scalars_name == active_scalars_name

    # test set point_data
    active_scalars_name = 'cell_data_a'
    multiblock_all.set_active_scalars(active_scalars_name)
    for block in multiblock_all:
        assert block.cell_data.active_scalars_name == active_scalars_name

    # test set point_data
    multiblock_all.set_active_scalars(None)
    active_scalars_name = 'data'
    multiblock_all.set_active_scalars(active_scalars_name, preference='point')
    for block in multiblock_all:
        assert block.point_data.active_scalars_name == active_scalars_name
        assert block.cell_data.active_scalars_name is None

    multiblock_all.set_active_scalars(None)
    active_scalars_name = 'data'
    multiblock_all.set_active_scalars(active_scalars_name, preference='cell')
    for block in multiblock_all:
        assert block.point_data.active_scalars_name is None
        assert block.cell_data.active_scalars_name == active_scalars_name

    # test partial
    multiblock_all[0].clear_data()
    multiblock_all.set_active_scalars(None)
    with pytest.raises(KeyError, match='does not exist'):
        multiblock_all.set_active_scalars('point_data_a')
    multiblock_all.set_active_scalars('point_data_a', allow_missing=True)
    multiblock_all[1].point_data.active_scalars_name == 'point_data_a'

    with pytest.raises(KeyError, match='is missing from all'):
        multiblock_all.set_active_scalars('does not exist', allow_missing=True)


def test_set_active_scalars_multi(multiblock_poly):
    multiblock_poly.set_active_scalars(None)

    block = multiblock_poly[0]
    block.point_data.set_array(range(block.n_points), 'data')
    block.cell_data.set_array(range(block.n_cells), 'data')

    block = multiblock_poly[1]
    block.point_data.set_array(range(block.n_points), 'data')

    multiblock_poly.set_active_scalars('data', preference='point', allow_missing=True)
    for block in multiblock_poly:
        if 'data' in block.point_data:
            assert block.point_data.active_scalars_name == 'data'
        else:
            assert block.point_data.active_scalars_name is None

    multiblock_poly.set_active_scalars('data', preference='cell', allow_missing=True)
    for block in multiblock_poly:
        if 'data' in block.cell_data:
            assert block.cell_data.active_scalars_name == 'data'
        else:
            assert block.cell_data.active_scalars_name is None


def test_set_active_scalars_components(multiblock_poly):
    multiblock_poly[0].point_data['data'] = range(multiblock_poly[0].n_points)
    multiblock_poly[1].point_data['data'] = range(multiblock_poly[1].n_points)
    multiblock_poly[2].point_data['data'] = range(multiblock_poly[2].n_points)

    multiblock_poly.set_active_scalars(None)
    multiblock_poly.set_active_scalars('data')
    for block in multiblock_poly:
        assert multiblock_poly[0].point_data.active_scalars_name == 'data'

    data = np.zeros((multiblock_poly[2].n_points, 3))
    multiblock_poly[2].point_data['data'] = data
    with pytest.raises(ValueError, match='Inconsistent dimensions'):
        multiblock_poly.set_active_scalars('data')

    data = np.arange(multiblock_poly[2].n_points, dtype=np.complex128)
    multiblock_poly[2].point_data['data'] = data
    with pytest.raises(ValueError, match='Inconsistent complex and real'):
        multiblock_poly.set_active_scalars('data')


def test_set_active_multi_multi(multiblock_poly):
    multi_multi = MultiBlock([multiblock_poly, multiblock_poly])
    with pytest.raises(KeyError, match='missing from all'):
        multi_multi.set_active_scalars('does-not-exist', allow_missing=True)

    multi_multi.set_active_scalars('multi-comp', allow_missing=True)


def test_set_active_scalars_mixed(multiblock_poly):
    for block in multiblock_poly:
        block.clear_data()
        block.point_data.set_array(range(block.n_points), 'data')
        block.cell_data.set_array(range(block.n_cells), 'data')

    # remove data from the last block
    del multiblock_poly[-1].point_data['data']
    del multiblock_poly[-1].cell_data['data']

    multiblock_poly.set_active_scalars('data', preference='cell', allow_missing=True)

    for block in multiblock_poly:
        if 'data' in block.cell_data:
            assert block.cell_data.active_scalars_name == 'data'

    multiblock_poly.set_active_scalars('data', preference='point', allow_missing=True)

    for block in multiblock_poly:
        if 'data' in block.point_data:
            assert block.point_data.active_scalars_name == 'data'


def test_to_polydata(multiblock_all):
    assert not multiblock_all.is_all_polydata

    dataset_a = multiblock_all.as_polydata_blocks()
    assert not multiblock_all.is_all_polydata
    assert dataset_a.is_all_polydata

    # verify nested works
    nested_mblock = pyvista.MultiBlock([multiblock_all, multiblock_all])
    assert not nested_mblock.is_all_polydata
    dataset_b = nested_mblock.as_polydata_blocks()
    assert dataset_b.is_all_polydata


def test_compute_normals(multiblock_poly):
    for block in multiblock_poly:
        block.clear_data()
        block['point_data'] = range(block.n_points)
    mblock = multiblock_poly._compute_normals(
        cell_normals=False, split_vertices=True, track_vertices=True
    )
    for block in mblock:
        assert 'Normals' in block.point_data
        assert 'point_data' in block.point_data
        assert 'pyvistaOriginalPointIds' in block.point_data

    # test non-poly raises
    multiblock_poly.append(pyvista.UnstructuredGrid())
    with pytest.raises(RuntimeError, match='This multiblock contains non-PolyData'):
        multiblock_poly._compute_normals()


def test_activate_plotting_scalars(multiblock_poly):
    for block in multiblock_poly:
        data = np.array(['a'] * block.n_points)
        block.point_data.set_array(data, 'data')


# tests/test_composite_mapper.py

import pytest

import pyvista as pv
from pyvista.plotting.composite_mapper import BlockAttributes, CompositePolyDataMapper


@pytest.fixture()
def composite_mapper(multiblock_poly):
    pl = pv.Plotter()
    actor, mapper = pl.add_composite(multiblock_poly)
    return mapper


@pytest.fixture()
def block_attributes(composite_mapper):
    return composite_mapper.block_attr


@pytest.fixture()
def block_attr(block_attributes):
    return block_attributes[0]


def test_basic_mapper(composite_mapper):
    assert isinstance(composite_mapper, CompositePolyDataMapper)
    assert isinstance(composite_mapper.dataset, pv.MultiBlock)


def test_interpolate_before_map(composite_mapper):
    assert isinstance(composite_mapper.interpolate_before_map, bool)


def test_color_missing_with_nan(composite_mapper):
    assert isinstance(composite_mapper.color_missing_with_nan, bool)


def test_lookup_table(composite_mapper):
    isinstance(composite_mapper.lookup_table, pv._vtk.vtkLookupTable)

    table = pv._vtk.vtkLookupTable()
    composite_mapper.lookup_table = table
    assert composite_mapper.lookup_table is table


def test_scalar_visibility(composite_mapper):
    isinstance(composite_mapper.scalar_visibility, bool)


def test_scalar_map_mode(composite_mapper):
    isinstance(composite_mapper.scalar_map_mode, str)
    with pytest.raises(ValueError, match='Invalid `scalar_map_mode`'):
        composite_mapper.scalar_map_mode = 'foo'


@pytest.mark.parametrize(
    'value', ['default', 'point', 'cell', 'point_field', 'cell_field', 'field']
)
def test_scalar_map_mode_values(value, composite_mapper):
    composite_mapper.scalar_map_mode = value
    assert composite_mapper.scalar_map_mode == value


def test_nan_color(composite_mapper):
    isinstance(composite_mapper.nan_color, pv.Color)


def test_above_range_color(composite_mapper):
    composite_mapper.above_range_color = 'r'
    isinstance(composite_mapper.above_range_color, pv.Color)


def test_below_range_color(composite_mapper):
    composite_mapper.below_range_color = 'r'
    isinstance(composite_mapper.below_range_color, pv.Color)


def test_composite_mapper_non_poly(multiblock_all):
    # should run without raising
    pl = pv.Plotter()
    actor, mapper = pl.add_composite(multiblock_all)


def test_block_attr(block_attributes):
    block = block_attributes[0]
    assert isinstance(block, BlockAttributes)
    with pytest.raises(KeyError, match='Invalid block key'):
        block_attributes[-1]
    with pytest.raises(KeyError, match='out of bounds'):
        block_attributes[100000]
    with pytest.raises(TypeError):
        block_attributes[0.5]


def test_block_attr_get_item_(multiblock_poly):
    """Verify flat indexing is correct."""
    block_b = multiblock_poly.copy()
    block_c = multiblock_poly.copy()
    block_a = pv.MultiBlock([block_b, block_c])
    pl = pv.Plotter()
    actor, mapper = pl.add_composite(block_a)
    assert len(mapper.block_attr) == len(block_b) + len(block_c) + 3
    with pytest.raises(KeyError):
        mapper.block_attr[len(mapper.block_attr)]


def test_visible(block_attr):
    assert block_attr.visible is None

    visible = True
    block_attr.visible = visible
    assert block_attr.visible is visible

    block_attr.visible = None
    assert block_attr.visible is None


def test_opacity(block_attr):
    # unset must be none
    assert block_attr.opacity is None

    opacity = 0.5
    block_attr.opacity = opacity
    assert block_attr.opacity == opacity

    block_attr.opacity = None
    assert block_attr.opacity is None


def test_color(block_attr):
    # when unset this must be None
    assert block_attr.color is None

    color = 'red'
    block_attr.color = color
    assert block_attr.color == pv.Color(color).float_rgb

    color = (1, 1, 0)
    block_attr.color = color
    assert block_attr.color == pv.Color(color).float_rgb

    block_attr.color = None
    assert block_attr.color is None


def test_attr_repr(block_attr):
    block_attr.visible = True
    block_attr.opacity = 0.9
    block_attr.color = 'blue'
    block_attr.pickable = False

    repr_ = repr(block_attr)
    assert 'True' in repr_
    assert 'False' in repr_
    assert 'blue' in repr_
    assert '0.9' in repr_


def test_block_attributes(block_attributes):
    color = (1.0, 1.0, 1.0)
    pickable = False
    opacity = 0.5
    visible = False
    block_attributes[0].color = color
    block_attributes[0].pickable = pickable
    block_attributes[0].opacity = opacity
    block_attributes[0].visible = visible

    assert block_attributes[0].color == color
    assert block_attributes[0].pickable == pickable
    assert block_attributes[0].opacity == opacity
    assert block_attributes[0].visible == visible

    block_attributes[0].pickable = None
    assert block_attributes[0].pickable is None
    block_attributes[0].pickable = True

    block_attributes.reset_colors()
    assert block_attributes[0].color is None

    block_attributes.reset_pickabilities()
    assert block_attributes[0].pickable is None

    block_attributes.reset_opacities()
    assert block_attributes[0].opacity is None

    block_attributes.reset_visibilities()
    assert block_attributes[0].visible is None


# tests/test_dataobject.py

import numpy as np

import pyvista
from pyvista import examples


def test_eq_wrong_type(sphere):
    assert sphere != [1, 2, 3]


def test_uniform_eq():
    orig = examples.load_uniform()
    copy = orig.copy(deep=True)
    copy.origin = [1, 1, 1]
    assert orig != copy

    copy.origin = [0, 0, 0]
    assert orig == copy

    copy.point_data.clear()
    assert orig != copy


def test_polydata_eq(sphere):
    sphere.clear_data()
    sphere.point_data['data0'] = np.zeros(sphere.n_points)
    sphere.point_data['data1'] = np.arange(sphere.n_points)

    copy = sphere.copy(deep=True)
    assert sphere == copy

    copy.faces = [3, 0, 1, 2]
    assert sphere != copy

    copy = sphere.copy(deep=True)
    copy.field_data['new'] = [1]
    assert sphere != copy

    copy = sphere.copy(deep=True)
    copy.point_data['new'] = range(sphere.n_points)
    assert sphere != copy

    copy = sphere.copy(deep=True)
    copy.cell_data['new'] = range(sphere.n_cells)
    assert sphere != copy

    copy = sphere.copy(deep=True)
    copy.point_data.active_scalars_name = 'data0'
    assert sphere != copy

    copy = sphere.copy(deep=True)
    copy.lines = [2, 0, 1]
    assert sphere != copy

    copy = sphere.copy(deep=True)
    copy.verts = [1, 0]
    assert sphere != copy


def test_unstructured_grid_eq(hexbeam):
    copy = hexbeam.copy()
    assert hexbeam == copy

    copy = hexbeam.copy()
    hexbeam.celltypes[0] = 0
    assert hexbeam != copy

    copy = hexbeam.copy()

    if pyvista._vtk.VTK9:
        hexbeam.cell_connectivity[0] += 1
    else:
        hexbeam.cells[0] += 1
    assert hexbeam != copy


def test_metadata_save(hexbeam, tmpdir):
    """Test if complex and bool metadata is saved and restored."""
    filename = tmpdir.join('hexbeam.vtk')

    hexbeam.clear_data()
    point_data = np.arange(hexbeam.n_points)
    hexbeam.point_data['pt_data0'] = point_data + 1j * point_data

    bool_pt_data = np.zeros(hexbeam.n_points, dtype=bool)
    bool_pt_data[::2] = 1
    hexbeam.point_data['bool_data'] = bool_pt_data

    cell_data = np.arange(hexbeam.n_cells)
    bool_cell_data = np.zeros(hexbeam.n_cells, dtype=bool)
    bool_cell_data[::2] = 1
    hexbeam.cell_data['my_complex_cell_data'] = cell_data + 1j * cell_data
    hexbeam.cell_data['my_other_complex_cell_data'] = -cell_data - 1j * cell_data
    hexbeam.cell_data['bool_data'] = bool_cell_data

    # verify that complex data is restored
    hexbeam.save(filename)
    hexbeam_in = pyvista.read(filename)
    assert hexbeam_in.point_data['pt_data0'].dtype == np.complex128
    assert hexbeam_in.point_data['bool_data'].dtype == bool
    assert hexbeam_in.cell_data['my_complex_cell_data'].dtype == np.complex128
    assert hexbeam_in.cell_data['my_other_complex_cell_data'].dtype == np.complex128
    assert hexbeam_in.cell_data['bool_data'].dtype == bool

    # metadata should be removed from the field data
    assert not hexbeam_in.field_data


# tests/test_dataset.py

"""Tests for pyvista.core.dataset."""

import multiprocessing
import pickle

from hypothesis import HealthCheck, assume, given, settings
from hypothesis.extra.numpy import array_shapes, arrays
from hypothesis.strategies import composite, floats, integers, one_of
import numpy as np
import pytest
import vtk
from vtk.util.numpy_support import vtk_to_numpy

import pyvista
from pyvista import Texture, examples
from pyvista.core.errors import VTKVersionError
from pyvista.utilities.misc import PyVistaDeprecationWarning

HYPOTHESIS_MAX_EXAMPLES = 20


@pytest.fixture()
def grid():
    return pyvista.UnstructuredGrid(examples.hexbeamfile)


def test_invalid_overwrite(grid):
    with pytest.raises(TypeError):
        grid.copy_from(pyvista.Plane())


def test_overwrite_deprecation(grid):
    mesh = type(grid)()
    with pytest.warns(PyVistaDeprecationWarning):
        mesh.overwrite(grid)


@composite
def n_numbers(draw, n):
    numbers = []
    for _ in range(n):
        number = draw(one_of(floats(), integers()))
        numbers.append(number)
    return numbers


def test_memory_address(grid):
    assert isinstance(grid.memory_address, str)
    assert 'Addr' in grid.memory_address


def test_point_data(grid):
    key = 'test_array_points'
    grid[key] = np.arange(grid.n_points)
    assert key in grid.point_data

    orig_value = grid.point_data[key][0] / 1.0
    grid.point_data[key][0] += 1
    assert orig_value == grid.point_data[key][0] - 1

    del grid.point_data[key]
    assert key not in grid.point_data

    grid.point_data[key] = np.arange(grid.n_points)
    assert key in grid.point_data

    assert np.allclose(grid[key], np.arange(grid.n_points))

    grid.clear_point_data()
    assert len(grid.point_data.keys()) == 0

    grid.point_data['list'] = np.arange(grid.n_points).tolist()
    assert isinstance(grid.point_data['list'], np.ndarray)
    assert np.allclose(grid.point_data['list'], np.arange(grid.n_points))


def test_point_data_bad_value(grid):
    with pytest.raises(TypeError):
        grid.point_data['new_array'] = None

    with pytest.raises(ValueError):
        grid.point_data['new_array'] = np.arange(grid.n_points - 1)


def test_ipython_key_completions(grid):
    assert isinstance(grid._ipython_key_completions_(), list)


def test_cell_data(grid):
    key = 'test_array_cells'
    grid[key] = np.arange(grid.n_cells)
    assert key in grid.cell_data

    orig_value = grid.cell_data[key][0] / 1.0
    grid.cell_data[key][0] += 1
    assert orig_value == grid.cell_data[key][0] - 1

    del grid.cell_data[key]
    assert key not in grid.cell_data

    grid.cell_data[key] = np.arange(grid.n_cells)
    assert key in grid.cell_data

    assert np.allclose(grid[key], np.arange(grid.n_cells))

    grid.cell_data['list'] = np.arange(grid.n_cells).tolist()
    assert isinstance(grid.cell_data['list'], np.ndarray)
    assert np.allclose(grid.cell_data['list'], np.arange(grid.n_cells))


def test_cell_array_range(grid):
    rng = range(grid.n_cells)
    grid.cell_data['tmp'] = rng
    assert np.allclose(rng, grid.cell_data['tmp'])


def test_cell_data_bad_value(grid):
    with pytest.raises(TypeError):
        grid.cell_data['new_array'] = None

    with pytest.raises(ValueError):
        grid.cell_data['new_array'] = np.arange(grid.n_cells - 1)


def test_field_data(grid):
    key = 'test_array_field'
    # Add array of length not equal to n_cells or n_points
    n = grid.n_cells // 3
    grid.field_data[key] = np.arange(n)
    assert key in grid.field_data
    assert np.allclose(grid.field_data[key], np.arange(n))
    assert np.allclose(grid[key], np.arange(n))

    orig_value = grid.field_data[key][0] / 1.0
    grid.field_data[key][0] += 1
    assert orig_value == grid.field_data[key][0] - 1

    assert key in grid.array_names

    del grid.field_data[key]
    assert key not in grid.field_data

    grid.field_data['list'] = np.arange(n).tolist()
    assert isinstance(grid.field_data['list'], np.ndarray)
    assert np.allclose(grid.field_data['list'], np.arange(n))

    foo = np.arange(n) * 5
    grid.add_field_data(foo, 'foo')
    assert isinstance(grid.field_data['foo'], np.ndarray)
    assert np.allclose(grid.field_data['foo'], foo)

    with pytest.raises(ValueError):
        grid.set_active_scalars('foo')


@pytest.mark.parametrize('field', (range(5), np.ones((3, 3))[:, 0]))
def test_add_field_data(grid, field):
    grid.add_field_data(field, 'foo')
    assert isinstance(grid.field_data['foo'], np.ndarray)
    assert np.allclose(grid.field_data['foo'], field)


def test_modify_field_data(grid):
    field = range(4)
    grid.add_field_data(range(5), 'foo')
    grid.add_field_data(field, 'foo')
    assert np.allclose(grid.field_data['foo'], field)

    field = range(8)
    grid.field_data['foo'] = field
    assert np.allclose(grid.field_data['foo'], field)


def test_active_scalars_cell(grid):
    grid.add_field_data(range(5), 'foo')
    del grid.point_data['sample_point_scalars']
    del grid.point_data['VTKorigID']
    assert grid.active_scalars_info[1] == 'sample_cell_scalars'


def test_field_data_bad_value(grid):
    with pytest.raises(TypeError):
        grid.field_data['new_array'] = None


def test_copy(grid):
    grid_copy = grid.copy(deep=True)
    grid_copy.points[0] = np.nan
    assert not np.any(np.isnan(grid.points[0]))

    grid_copy_shallow = grid.copy(deep=False)
    grid_copy.points[0] += 0.1
    assert np.all(grid_copy_shallow.points[0] == grid.points[0])


def test_copy_metadata(globe):
    """Ensure metadata is copied correctly."""
    globe.point_data['bitarray'] = np.zeros(globe.n_points, dtype=bool)
    globe.point_data['complex_data'] = np.zeros(globe.n_points, dtype=np.complex128)

    globe_shallow = globe.copy(deep=False)
    assert globe_shallow._active_scalars_info is globe._active_scalars_info
    assert globe_shallow._active_vectors_info is globe._active_vectors_info
    assert globe_shallow._active_tensors_info is globe._active_tensors_info
    assert globe_shallow.textures is globe.textures
    assert globe_shallow.point_data['bitarray'].dtype == np.bool_
    assert globe_shallow.point_data['complex_data'].dtype == np.complex128
    assert globe_shallow._association_bitarray_names is globe._association_bitarray_names
    assert globe_shallow._association_complex_names is globe._association_complex_names

    globe_deep = globe.copy(deep=True)
    assert globe_deep.textures is not globe.textures
    assert globe_deep._active_scalars_info is not globe._active_scalars_info
    assert globe_deep._active_vectors_info is not globe._active_vectors_info
    assert globe_deep._active_tensors_info is not globe._active_tensors_info
    assert globe_deep._active_scalars_info == globe._active_scalars_info
    assert globe_deep._active_vectors_info == globe._active_vectors_info
    assert globe_deep._active_tensors_info == globe._active_tensors_info
    assert globe_deep.textures == globe.textures
    assert globe_deep.point_data['bitarray'].dtype == np.bool_
    assert globe_deep.point_data['complex_data'].dtype == np.complex128
    assert (
        globe_deep._association_bitarray_names['POINT']
        is not globe._association_bitarray_names['POINT']
    )
    assert (
        globe_deep._association_complex_names['POINT']
        is not globe._association_complex_names['POINT']
    )

    globe.clear_textures()
    assert not globe.textures
    assert globe_deep.textures
    assert not globe_shallow.textures


@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None)
@given(rotate_amounts=n_numbers(4), translate_amounts=n_numbers(3))
def test_translate_should_match_vtk_transformation(rotate_amounts, translate_amounts, grid):
    trans = vtk.vtkTransform()
    trans.RotateWXYZ(*rotate_amounts)
    trans.Translate(translate_amounts)
    trans.Update()

    grid_a = grid.copy()
    grid_b = grid.copy()
    grid_c = grid.copy()
    grid_a.transform(trans)
    grid_b.transform(trans.GetMatrix())
    grid_c.transform(pyvista.array_from_vtkmatrix(trans.GetMatrix()))

    # treat INF as NAN (necessary for allclose)
    grid_a.points[np.isinf(grid_a.points)] = np.nan
    assert np.allclose(grid_a.points, grid_b.points, equal_nan=True)
    assert np.allclose(grid_a.points, grid_c.points, equal_nan=True)

    # test non homogeneous transform
    trans_rotate_only = vtk.vtkTransform()
    trans_rotate_only.RotateWXYZ(*rotate_amounts)
    trans_rotate_only.Update()

    grid_d = grid.copy()
    grid_d.transform(trans_rotate_only)

    from pyvista.utilities.transformations import apply_transformation_to_points

    trans_arr = pyvista.array_from_vtkmatrix(trans_rotate_only.GetMatrix())[:3, :3]
    trans_pts = apply_transformation_to_points(trans_arr, grid.points)
    assert np.allclose(grid_d.points, trans_pts, equal_nan=True)


def test_translate_should_fail_given_none(grid):
    with pytest.raises(TypeError):
        grid.transform(None)


def test_translate_deprecation(grid):
    with pytest.warns(PyVistaDeprecationWarning):
        grid.translate((0.0, 0.0, 0.0))


def test_set_points():
    dataset = pyvista.UnstructuredGrid()
    points = np.random.random((10, 3))
    dataset.points = pyvista.vtk_points(points)


def test_translate_should_fail_bad_points_or_transform(grid):
    points = np.random.random((10, 2))
    bad_points = np.random.random((10, 2))
    trans = np.random.random((4, 4))
    bad_trans = np.random.random((2, 4))
    with pytest.raises(ValueError):
        pyvista.utilities.transformations.apply_transformation_to_points(trans, bad_points)

    with pytest.raises(ValueError):
        pyvista.utilities.transformations.apply_transformation_to_points(bad_trans, points)


@settings(
    suppress_health_check=[HealthCheck.function_scoped_fixture],
    max_examples=HYPOTHESIS_MAX_EXAMPLES,
)
@given(array=arrays(dtype=np.float32, shape=array_shapes(max_dims=5, max_side=5)))
def test_transform_should_fail_given_wrong_numpy_shape(array, grid):
    assume(array.shape != (4, 4))
    with pytest.raises(ValueError):
        grid.transform(array)


@pytest.mark.parametrize('axis_amounts', [[1, 1, 1], [0, 0, 0], [-1, -1, -1]])
def test_translate_should_translate_grid(grid, axis_amounts):
    grid_copy = grid.copy()
    grid_copy.translate(axis_amounts, inplace=True)

    grid_points = grid.points.copy() + np.array(axis_amounts)
    assert np.allclose(grid_copy.points, grid_points)


@settings(
    suppress_health_check=[HealthCheck.function_scoped_fixture],
    max_examples=HYPOTHESIS_MAX_EXAMPLES,
)
@given(angle=one_of(floats(allow_infinity=False, allow_nan=False), integers()))
@pytest.mark.parametrize('axis', ('x', 'y', 'z'))
def test_rotate_should_match_vtk_rotation(angle, axis, grid):
    trans = vtk.vtkTransform()
    getattr(trans, f'Rotate{axis.upper()}')(angle)
    trans.Update()

    trans_filter = vtk.vtkTransformFilter()
    trans_filter.SetTransform(trans)
    trans_filter.SetInputData(grid)
    trans_filter.Update()
    grid_a = pyvista.UnstructuredGrid(trans_filter.GetOutput())

    grid_b = grid.copy()
    getattr(grid_b, f'rotate_{axis}')(angle, inplace=True)
    assert np.allclose(grid_a.points, grid_b.points, equal_nan=True)


def test_make_points_double(grid):
    grid.points = grid.points.astype(np.float32)
    assert grid.points.dtype == np.float32
    grid.points_to_double()
    assert grid.points.dtype == np.double


def test_invalid_points(grid):
    with pytest.raises(TypeError):
        grid.points = None


def test_points_np_bool(grid):
    bool_arr = np.zeros(grid.n_points, np.bool_)
    grid.point_data['bool_arr'] = bool_arr
    bool_arr[:] = True
    assert grid.point_data['bool_arr'].all()
    assert grid.point_data['bool_arr'].all()
    assert grid.point_data['bool_arr'].dtype == np.bool_


def test_cells_np_bool(grid):
    bool_arr = np.zeros(grid.n_cells, np.bool_)
    grid.cell_data['bool_arr'] = bool_arr
    bool_arr[:] = True
    assert grid.cell_data['bool_arr'].all()
    assert grid.cell_data['bool_arr'].all()
    assert grid.cell_data['bool_arr'].dtype == np.bool_


def test_field_np_bool(grid):
    bool_arr = np.zeros(grid.n_cells // 3, np.bool_)
    grid.field_data['bool_arr'] = bool_arr
    bool_arr[:] = True
    assert grid.field_data['bool_arr'].all()
    assert grid.field_data['bool_arr'].all()
    assert grid.field_data['bool_arr'].dtype == np.bool_


def test_cells_uint8(grid):
    arr = np.zeros(grid.n_cells, np.uint8)
    grid.cell_data['arr'] = arr
    arr[:] = np.arange(grid.n_cells)
    assert np.allclose(grid.cell_data['arr'], np.arange(grid.n_cells))


def test_points_uint8(grid):
    arr = np.zeros(grid.n_points, np.uint8)
    grid.point_data['arr'] = arr
    arr[:] = np.arange(grid.n_points)
    assert np.allclose(grid.point_data['arr'], np.arange(grid.n_points))


def test_field_uint8(grid):
    n = grid.n_points // 3
    arr = np.zeros(n, np.uint8)
    grid.field_data['arr'] = arr
    arr[:] = np.arange(n)
    assert np.allclose(grid.field_data['arr'], np.arange(n))


def test_bitarray_points(grid):
    n = grid.n_points
    vtk_array = vtk.vtkBitArray()
    np_array = np.empty(n, np.bool_)
    vtk_array.SetNumberOfTuples(n)
    vtk_array.SetName('bint_arr')
    for i in range(n):
        value = i % 2
        vtk_array.SetValue(i, value)
        np_array[i] = value

    grid.GetPointData().AddArray(vtk_array)
    assert np.allclose(grid.point_data['bint_arr'], np_array)


def test_bitarray_cells(grid):
    n = grid.n_cells
    vtk_array = vtk.vtkBitArray()
    np_array = np.empty(n, np.bool_)
    vtk_array.SetNumberOfTuples(n)
    vtk_array.SetName('bint_arr')
    for i in range(n):
        value = i % 2
        vtk_array.SetValue(i, value)
        np_array[i] = value

    grid.GetCellData().AddArray(vtk_array)
    assert np.allclose(grid.cell_data['bint_arr'], np_array)


def test_bitarray_field(grid):
    n = grid.n_cells // 3
    vtk_array = vtk.vtkBitArray()
    np_array = np.empty(n, np.bool_)
    vtk_array.SetNumberOfTuples(n)
    vtk_array.SetName('bint_arr')
    for i in range(n):
        value = i % 2
        vtk_array.SetValue(i, value)
        np_array[i] = value

    grid.GetFieldData().AddArray(vtk_array)
    assert np.allclose(grid.field_data['bint_arr'], np_array)


def test_html_repr(grid):
    """
    This just tests to make sure no errors are thrown on the HTML
    representation method for DataSet.
    """
    assert grid._repr_html_() is not None


@pytest.mark.parametrize('html', (True, False))
@pytest.mark.parametrize('display', (True, False))
def test_print_repr(grid, display, html):
    """
    This just tests to make sure no errors are thrown on the text friendly
    representation method for DataSet.
    """
    result = grid.head(display=display, html=html)
    if display and html:
        assert result is None
    else:
        assert result is not None


def test_texture():
    """Test adding texture coordinates"""
    # create a rectangle vertices
    vertices = np.array(
        [
            [0, 0, 0],
            [1, 0, 0],
            [1, 0.5, 0],
            [0, 0.5, 0],
        ]
    )

    # mesh faces
    faces = np.hstack([[3, 0, 1, 2], [3, 0, 3, 2]]).astype(np.int8)

    # Create simple texture coordinates
    t_coords = np.array([[0, 0], [1, 0], [1, 1], [0, 1]])
    # Create the poly data
    mesh = pyvista.PolyData(vertices, faces)
    # Attempt setting the texture coordinates
    mesh.active_t_coords = t_coords
    # now grab the texture coordinates
    foo = mesh.active_t_coords
    assert np.allclose(foo, t_coords)
    texture = Texture(examples.mapfile)
    mesh.textures['map'] = texture
    assert mesh.textures['map'] is not None
    mesh.clear_textures()
    assert len(mesh.textures) == 0


def test_multiple_texture_coordinates():
    mesh = examples.load_airplane()
    mesh.texture_map_to_plane(inplace=True, name="tex_a", use_bounds=False)
    mesh.texture_map_to_plane(inplace=True, name="tex_b", use_bounds=True)
    assert not np.allclose(mesh["tex_a"], mesh["tex_b"])
    texture = Texture(examples.mapfile)
    mesh.textures["tex_a"] = texture.copy()
    mesh.textures["tex_b"] = texture.copy()
    mesh._activate_texture("tex_a")
    assert np.allclose(mesh.active_t_coords, mesh["tex_a"])
    mesh._activate_texture("tex_b")
    assert np.allclose(mesh.active_t_coords, mesh["tex_b"])

    # Now test copying
    cmesh = mesh.copy()
    assert len(cmesh.textures) == 2
    assert "tex_a" in cmesh.textures
    assert "tex_b" in cmesh.textures


def test_inplace_no_overwrite_texture_coordinates():
    mesh = pyvista.Box()
    truth = mesh.texture_map_to_plane(inplace=False)
    mesh.texture_map_to_sphere(inplace=True)
    test = mesh.texture_map_to_plane(inplace=True)
    assert np.allclose(truth.active_t_coords, test.active_t_coords)


def test_invalid_vector(grid):
    with pytest.raises(ValueError):
        grid["vectors"] = np.empty(10)

    with pytest.raises(ValueError):
        grid["vectors"] = np.empty((3, 2))

    with pytest.raises(ValueError):
        grid["vectors"] = np.empty((3, 3))


def test_no_t_coords(grid):
    assert grid.active_t_coords is None


def test_no_arrows(grid):
    assert grid.arrows is None


def test_arrows():
    sphere = pyvista.Sphere(radius=3.14)

    # make cool swirly pattern
    vectors = np.vstack(
        (np.sin(sphere.points[:, 0]), np.cos(sphere.points[:, 1]), np.cos(sphere.points[:, 2]))
    ).T

    # add and scales
    sphere["vectors"] = vectors * 0.3
    sphere.set_active_vectors("vectors")
    assert np.allclose(sphere.active_vectors, vectors * 0.3)
    assert np.allclose(sphere["vectors"], vectors * 0.3)

    assert sphere.active_vectors_info[1] == 'vectors'
    arrows = sphere.arrows
    assert isinstance(arrows, pyvista.PolyData)
    assert np.any(arrows.points)
    assert arrows.active_vectors_name == 'GlyphVector'


def active_component_consistency_check(grid, component_type, field_association="point"):
    """
    Tests if the active component (scalars, vectors, tensors) actually reflects the underlying VTK dataset
    """
    component_type = component_type.lower()
    vtk_component_type = component_type.capitalize()

    field_association = field_association.lower()
    vtk_field_association = field_association.capitalize()

    pv_arr = getattr(grid, "active_" + component_type)
    vtk_arr = getattr(
        getattr(grid, f"Get{vtk_field_association}Data")(), f"Get{vtk_component_type}"
    )()

    assert (pv_arr is None and vtk_arr is None) or np.allclose(pv_arr, vtk_to_numpy(vtk_arr))


def test_set_active_vectors(grid):
    vector_arr = np.arange(grid.n_points * 3).reshape([grid.n_points, 3])
    grid.point_data['vector_arr'] = vector_arr
    grid.active_vectors_name = 'vector_arr'
    active_component_consistency_check(grid, "vectors", "point")
    assert grid.active_vectors_name == 'vector_arr'
    assert np.allclose(grid.active_vectors, vector_arr)

    grid.active_vectors_name = None
    assert grid.active_vectors_name is None
    active_component_consistency_check(grid, "vectors", "point")


def test_set_active_tensors(grid):
    tensor_arr = np.arange(grid.n_points * 9).reshape([grid.n_points, 9])
    grid.point_data['tensor_arr'] = tensor_arr
    grid.active_tensors_name = 'tensor_arr'
    active_component_consistency_check(grid, "tensors", "point")
    assert grid.active_tensors_name == 'tensor_arr'
    assert np.allclose(grid.active_tensors, tensor_arr)

    grid.active_tensors_name = None
    assert grid.active_tensors_name is None
    active_component_consistency_check(grid, "tensors", "point")


def test_set_t_coords(grid):
    with pytest.raises(TypeError):
        grid.active_t_coords = [1, 2, 3]

    with pytest.raises(ValueError):
        grid.active_t_coords = np.empty(10)

    with pytest.raises(ValueError):
        grid.active_t_coords = np.empty((3, 3))

    with pytest.raises(ValueError):
        grid.active_t_coords = np.empty((grid.n_points, 1))


def test_activate_texture_none(grid):
    assert grid._activate_texture('not a key') is None
    assert grid._activate_texture(True) is None


def test_set_active_vectors_fail(grid):
    with pytest.raises(ValueError):
        grid.set_active_vectors('not a vector')

    active_component_consistency_check(grid, "vectors", "point")
    vector_arr = np.arange(grid.n_points * 3).reshape([grid.n_points, 3])
    grid.point_data['vector_arr'] = vector_arr
    grid.active_vectors_name = 'vector_arr'
    active_component_consistency_check(grid, "vectors", "point")

    grid.point_data['scalar_arr'] = np.zeros([grid.n_points])

    with pytest.raises(ValueError):
        grid.set_active_vectors('scalar_arr')

    assert grid.active_vectors_name == 'vector_arr'
    active_component_consistency_check(grid, "vectors", "point")


def test_set_active_tensors_fail(grid):
    with pytest.raises(ValueError):
        grid.set_active_tensors('not a tensor')

    active_component_consistency_check(grid, "tensors", "point")
    tensor_arr = np.arange(grid.n_points * 9).reshape([grid.n_points, 9])
    grid.point_data['tensor_arr'] = tensor_arr
    grid.active_tensors_name = 'tensor_arr'
    active_component_consistency_check(grid, "tensors", "point")

    grid.point_data['scalar_arr'] = np.zeros([grid.n_points])
    grid.point_data['vector_arr'] = np.zeros([grid.n_points, 3])

    with pytest.raises(ValueError):
        grid.set_active_tensors('scalar_arr')

    with pytest.raises(ValueError):
        grid.set_active_tensors('vector_arr')

    assert grid.active_tensors_name == 'tensor_arr'
    active_component_consistency_check(grid, "tensors", "point")


def test_set_active_scalars(grid):
    arr = np.arange(grid.n_cells)
    grid.cell_data['tmp'] = arr
    grid.set_active_scalars('tmp')
    assert np.allclose(grid.active_scalars, arr)
    # Make sure we can set no active scalars
    grid.set_active_scalars(None)
    assert grid.GetPointData().GetScalars() is None
    assert grid.GetCellData().GetScalars() is None


def test_set_active_scalars_name(grid):
    point_keys = list(grid.point_data.keys())
    grid.active_scalars_name = point_keys[0]
    grid.active_scalars_name = None


def test_rename_array_point(grid):
    point_keys = list(grid.point_data.keys())
    old_name = point_keys[0]
    new_name = 'point changed'
    grid.set_active_scalars(old_name, preference='point')
    grid.rename_array(old_name, new_name, preference='point')
    assert new_name in grid.point_data
    assert old_name not in grid.point_data
    assert new_name == grid.active_scalars_name


def test_rename_array_cell(grid):
    cell_keys = list(grid.cell_data.keys())
    old_name = cell_keys[0]
    new_name = 'cell changed'
    grid.rename_array(old_name, new_name)
    assert new_name in grid.cell_data
    assert old_name not in grid.cell_data


def test_rename_array_field(grid):
    grid.field_data['fieldfoo'] = np.array([8, 6, 7])
    field_keys = list(grid.field_data.keys())
    old_name = field_keys[0]
    new_name = 'cell changed'
    grid.rename_array(old_name, new_name)
    assert new_name in grid.field_data
    assert old_name not in grid.field_data


def test_change_name_fail(grid):
    with pytest.raises(KeyError):
        grid.rename_array('not a key', '')


def test_get_cell_array_fail():
    sphere = pyvista.Sphere()
    with pytest.raises(TypeError):
        sphere.cell_data[None]


def test_get_item(grid):
    with pytest.raises(KeyError):
        grid[0]


def test_set_item(grid):
    with pytest.raises(TypeError):
        grid['tmp'] = None

    # field data
    with pytest.raises(ValueError):
        grid['bad_field'] = range(5)


def test_set_item_range(grid):
    rng = range(grid.n_points)
    grid['pt_rng'] = rng
    assert np.allclose(grid['pt_rng'], rng)


def test_str(grid):
    assert 'UnstructuredGrid' in str(grid)


def test_set_cell_vectors(grid):
    arr = np.random.random((grid.n_cells, 3))
    grid.cell_data['_cell_vectors'] = arr
    grid.set_active_vectors('_cell_vectors')
    assert grid.active_vectors_name == '_cell_vectors'
    assert np.allclose(grid.active_vectors, arr)


def test_axis_rotation_invalid():
    with pytest.raises(ValueError):
        pyvista.utilities.axis_rotation(np.empty((3, 3)), 0, False, axis='not')


def test_axis_rotation_not_inplace():
    p = np.eye(3)
    p_out = pyvista.utilities.axis_rotation(p, 1, False, axis='x')
    assert not np.allclose(p, p_out)


def test_bad_instantiation():
    with pytest.raises(TypeError):
        pyvista.DataSet()
    with pytest.raises(TypeError):
        pyvista.Grid()
    with pytest.raises(TypeError):
        pyvista.DataSetFilters()
    with pytest.raises(TypeError):
        pyvista.PointGrid()
    with pytest.raises(TypeError):
        pyvista.BasePlotter()
    with pytest.raises(TypeError):
        pyvista.DataObject()


def test_string_arrays():
    poly = pyvista.PolyData(np.random.rand(10, 3))
    arr = np.array([f'foo{i}' for i in range(10)])
    poly['foo'] = arr
    back = poly['foo']
    assert len(back) == 10


def test_clear_data():
    # First try on an empty mesh
    grid = pyvista.UniformGrid(dims=(10, 10, 10))
    # Now try something more complicated
    grid.clear_data()
    grid['foo-p'] = np.random.rand(grid.n_points)
    grid['foo-c'] = np.random.rand(grid.n_cells)
    grid.field_data['foo-f'] = np.random.rand(grid.n_points * grid.n_cells)
    assert grid.n_arrays == 3
    grid.clear_data()
    assert grid.n_arrays == 0


def test_scalars_dict_update():
    mesh = examples.load_uniform()
    n = len(mesh.point_data)
    arrays = {'foo': np.arange(mesh.n_points), 'rand': np.random.random(mesh.n_points)}
    mesh.point_data.update(arrays)
    assert 'foo' in mesh.array_names
    assert 'rand' in mesh.array_names
    assert len(mesh.point_data) == n + 2

    # Test update from Table
    table = pyvista.Table(arrays)
    mesh = examples.load_uniform()
    mesh.point_data.update(table)
    assert 'foo' in mesh.array_names
    assert 'rand' in mesh.array_names
    assert len(mesh.point_data) == n + 2


def test_handle_array_with_null_name():
    poly = pyvista.PolyData()
    # Add point array with no name
    poly.GetPointData().AddArray(pyvista.convert_array(np.array([])))
    html = poly._repr_html_()
    assert html is not None
    pdata = poly.point_data
    assert pdata is not None
    assert len(pdata) == 1
    # Add cell array with no name
    poly.GetCellData().AddArray(pyvista.convert_array(np.array([])))
    html = poly._repr_html_()
    assert html is not None
    cdata = poly.cell_data
    assert cdata is not None
    assert len(cdata) == 1
    # Add field array with no name
    poly.GetFieldData().AddArray(pyvista.convert_array(np.array([5, 6])))
    html = poly._repr_html_()
    assert html is not None
    fdata = poly.field_data
    assert fdata is not None
    assert len(fdata) == 1


def test_add_point_array_list(grid):
    rng = range(grid.n_points)
    grid.point_data['tmp'] = rng
    assert np.allclose(grid.point_data['tmp'], rng)


def test_shallow_copy_back_propagation():
    """Test that the original data object's points get modified after a
    shallow copy.

    Reference: https://github.com/pyvista/pyvista/issues/375#issuecomment-531691483
    """
    # Case 1
    points = vtk.vtkPoints()
    points.InsertNextPoint(0.0, 0.0, 0.0)
    points.InsertNextPoint(1.0, 0.0, 0.0)
    points.InsertNextPoint(2.0, 0.0, 0.0)
    original = vtk.vtkPolyData()
    original.SetPoints(points)
    wrapped = pyvista.PolyData(original, deep=False)
    wrapped.points[:] = 2.8
    orig_points = vtk_to_numpy(original.GetPoints().GetData())
    assert np.allclose(orig_points, wrapped.points)
    # Case 2
    original = vtk.vtkPolyData()
    wrapped = pyvista.PolyData(original, deep=False)
    wrapped.points = np.random.rand(5, 3)
    orig_points = vtk_to_numpy(original.GetPoints().GetData())
    assert np.allclose(orig_points, wrapped.points)


def test_find_closest_point():
    sphere = pyvista.Sphere()
    node = np.array([0, 0.2, 0.2])

    with pytest.raises(TypeError):
        sphere.find_closest_point([1, 2])

    with pytest.raises(ValueError):
        sphere.find_closest_point([0, 0, 0], n=0)

    with pytest.raises(TypeError):
        sphere.find_closest_point([0, 0, 0], n=3.0)

    with pytest.raises(TypeError):
        # allow Sequence but not Iterable
        sphere.find_closest_point({1, 2, 3})

    index = sphere.find_closest_point(node)
    assert isinstance(index, int)
    # Make sure we can fetch that point
    closest = sphere.points[index]
    assert len(closest) == 3
    # n points
    node = np.array([0, 0.2, 0.2])
    index = sphere.find_closest_point(node, 5)
    assert len(index) == 5


def test_find_closest_cell():
    mesh = pyvista.Wavelet()
    node = np.array([0, 0.2, 0.2])
    index = mesh.find_closest_cell(node)
    assert isinstance(index, int)


def test_find_closest_cells():
    mesh = pyvista.Sphere()
    # simply get the face centers, ordered by cell Id
    fcent = mesh.points[mesh.faces.reshape(-1, 4)[:, 1:]].mean(1)
    fcent_copy = fcent.copy()
    indices = mesh.find_closest_cell(fcent)

    # Make sure we match the face centers
    assert np.allclose(indices, np.arange(mesh.n_faces))

    # Make sure arg was not modified
    assert np.array_equal(fcent, fcent_copy)


def test_find_closest_cell_surface_point():
    mesh = pyvista.Rectangle()

    point = np.array([0.5, 0.5, -1.0])
    point2 = np.array([1.0, 1.0, -1.0])
    points = np.vstack((point, point2))

    _, closest_point = mesh.find_closest_cell(point, return_closest_point=True)
    assert np.allclose(closest_point, [0.5, 0.5, 0])

    _, closest_points = mesh.find_closest_cell(points, return_closest_point=True)
    assert np.allclose(closest_points, [[0.5, 0.5, 0], [1.0, 1.0, 0]])


def test_find_containing_cell():
    mesh = pyvista.UniformGrid(dims=[5, 5, 1], spacing=[1 / 4, 1 / 4, 0])
    node = np.array([0.3, 0.3, 0.0])
    index = mesh.find_containing_cell(node)
    assert index == 5


def test_find_containing_cells():
    mesh = pyvista.UniformGrid(dims=[5, 5, 1], spacing=[1 / 4, 1 / 4, 0])
    points = np.array([[0.3, 0.3, 0], [0.6, 0.6, 0]])
    points_copy = points.copy()
    indices = mesh.find_containing_cell(points)
    assert np.allclose(indices, [5, 10])
    assert np.array_equal(points, points_copy)


def test_find_cells_along_line():
    mesh = pyvista.Cube()
    indices = mesh.find_cells_along_line([0, 0, -1], [0, 0, 1])
    assert len(indices) == 2


def test_find_cells_within_bounds():
    mesh = pyvista.Cube()

    bounds = [
        mesh.bounds[0] * 2.0,
        mesh.bounds[1] * 2.0,
        mesh.bounds[2] * 2.0,
        mesh.bounds[3] * 2.0,
        mesh.bounds[4] * 2.0,
        mesh.bounds[5] * 2.0,
    ]
    indices = mesh.find_cells_within_bounds(bounds)
    assert len(indices) == mesh.n_cells

    bounds = [
        mesh.bounds[0] * 0.5,
        mesh.bounds[1] * 0.5,
        mesh.bounds[2] * 0.5,
        mesh.bounds[3] * 0.5,
        mesh.bounds[4] * 0.5,
        mesh.bounds[5] * 0.5,
    ]
    indices = mesh.find_cells_within_bounds(bounds)
    assert len(indices) == 0


def test_setting_points_by_different_types(grid):
    grid_copy = grid.copy()
    grid.points = grid_copy.points
    assert np.array_equal(grid.points, grid_copy.points)

    grid.points = np.array(grid_copy.points)
    assert np.array_equal(grid.points, grid_copy.points)

    grid.points = grid_copy.points.tolist()
    assert np.array_equal(grid.points, grid_copy.points)

    pgrid = pyvista.PolyData([0.0, 0.0, 0.0])
    pgrid.points = [1.0, 1.0, 1.0]
    assert np.array_equal(pgrid.points, [[1.0, 1.0, 1.0]])

    pgrid.points = np.array([2.0, 2.0, 2.0])
    assert np.array_equal(pgrid.points, [[2.0, 2.0, 2.0]])


def test_empty_points():
    pdata = pyvista.PolyData()
    assert np.allclose(pdata.points, np.empty(3))


def test_no_active():
    pdata = pyvista.PolyData()
    assert pdata.active_scalars is None

    with pytest.raises(TypeError):
        pdata.point_data[None]


def test_get_data_range(grid):
    # Test with blank mesh
    mesh = pyvista.Sphere()
    mesh.clear_data()
    rng = mesh.get_data_range()
    assert all(np.isnan(rng))
    with pytest.raises(KeyError):
        rng = mesh.get_data_range('some data')

    # Test with some data
    grid.active_scalars_name = 'sample_point_scalars'
    rng = grid.get_data_range()  # active scalars
    assert len(rng) == 2
    assert np.allclose(rng, (1, 302))

    rng = grid.get_data_range('sample_point_scalars')
    assert len(rng) == 2
    assert np.allclose(rng, (1, 302))

    rng = grid.get_data_range('sample_cell_scalars')
    assert len(rng) == 2
    assert np.allclose(rng, (1, 40))


def test_actual_memory_size(grid):
    size = grid.actual_memory_size
    assert isinstance(size, int)
    assert size >= 0


def test_copy_structure(grid):
    classname = grid.__class__.__name__
    copy = eval(f'pyvista.{classname}')()
    copy.copy_structure(grid)
    assert copy.n_cells == grid.n_cells
    assert copy.n_points == grid.n_points
    assert len(copy.field_data) == 0
    assert len(copy.cell_data) == 0
    assert len(copy.point_data) == 0


def test_copy_attributes(grid):
    classname = grid.__class__.__name__
    copy = eval(f'pyvista.{classname}')()
    copy.copy_attributes(grid)
    assert copy.n_cells == 0
    assert copy.n_points == 0
    assert copy.field_data.keys() == grid.field_data.keys()
    assert copy.cell_data.keys() == grid.cell_data.keys()
    assert copy.point_data.keys() == grid.point_data.keys()


def test_cell_n_points(grid):
    npoints = grid.cell_n_points(0)
    assert isinstance(npoints, int)
    assert npoints >= 0


def test_cell_points(grid):
    points = grid.cell_points(0)
    assert isinstance(points, np.ndarray)
    assert points.ndim == 2
    assert points.shape[0] > 0
    assert points.shape[1] == 3


def test_cell_point_ids(grid):
    point_ids = grid.cell_point_ids(0)
    assert isinstance(point_ids, list)
    assert len(point_ids) == grid.cell_n_points(0)
    assert all([isinstance(id, int) for id in point_ids])
    assert all([0 <= id < grid.n_points for id in point_ids])


def test_cell_bounds(grid):
    bounds = grid.cell_bounds(0)
    assert isinstance(bounds, tuple)
    assert len(bounds) == 6


def test_cell_type(grid):
    ctype = grid.cell_type(0)
    assert isinstance(ctype, int)


def test_point_is_inside_cell():
    grid = pyvista.UniformGrid(dims=(2, 2, 2))
    assert grid.point_is_inside_cell(0, [0.5, 0.5, 0.5])
    assert not grid.point_is_inside_cell(0, [-0.5, -0.5, -0.5])

    assert grid.point_is_inside_cell(0, np.array([0.5, 0.5, 0.5]))

    # cell ind out of range
    with pytest.raises(ValueError):
        grid.point_is_inside_cell(100000, [0.5, 0.5, 0.5])
    with pytest.raises(ValueError):
        grid.point_is_inside_cell(-1, [0.5, 0.5, 0.5])

    # cell ind wrong type
    with pytest.raises(TypeError):
        grid.point_is_inside_cell(0.1, [0.5, 0.5, 0.5])

    # point not well formed
    with pytest.raises(TypeError):
        grid.point_is_inside_cell(0, 0.5)
    with pytest.raises(ValueError):
        grid.point_is_inside_cell(0, [0.5, 0.5])

    # multi-dimensional
    in_cell = grid.point_is_inside_cell(0, [[0.5, 0.5, 0.5], [-0.5, -0.5, -0.5]])
    assert np.array_equal(in_cell, np.array([True, False]))


@pytest.mark.parametrize('pickle_format', ['xml', 'legacy'])
def test_serialize_deserialize(datasets, pickle_format):
    pyvista.set_pickle_format(pickle_format)
    for dataset in datasets:
        dataset_2 = pickle.loads(pickle.dumps(dataset))

        # check python attributes are the same
        for attr in dataset.__dict__:
            assert getattr(dataset_2, attr) == getattr(dataset, attr)

        # check data is the same
        for attr in ('n_cells', 'n_points', 'n_arrays'):
            if hasattr(dataset, attr):
                assert getattr(dataset_2, attr) == getattr(dataset, attr)

        for attr in ('cells', 'points'):
            if hasattr(dataset, attr):
                arr_have = getattr(dataset_2, attr)
                arr_expected = getattr(dataset, attr)
                assert arr_have == pytest.approx(arr_expected)

        for name in dataset.point_data:
            arr_have = dataset_2.point_data[name]
            arr_expected = dataset.point_data[name]
            assert arr_have == pytest.approx(arr_expected)

        for name in dataset.cell_data:
            arr_have = dataset_2.cell_data[name]
            arr_expected = dataset.cell_data[name]
            assert arr_have == pytest.approx(arr_expected)

        for name in dataset.field_data:
            arr_have = dataset_2.field_data[name]
            arr_expected = dataset.field_data[name]
            assert arr_have == pytest.approx(arr_expected)


def n_points(dataset):
    # used in multiprocessing test
    return dataset.n_points


@pytest.mark.parametrize('pickle_format', ['xml', 'legacy'])
def test_multiprocessing(datasets, pickle_format):
    # exercise pickling via multiprocessing
    pyvista.set_pickle_format(pickle_format)
    with multiprocessing.Pool(2) as p:
        res = p.map(n_points, datasets)
    for res, dataset in zip(res, datasets):
        assert res == dataset.n_points


def test_rotations_should_match_by_a_360_degree_difference():
    mesh = examples.load_airplane()

    point = np.random.random(3) - 0.5
    angle = (np.random.random() - 0.5) * 360.0
    vector = np.random.random(3) - 0.5

    # Rotate about x axis.
    rot1 = mesh.copy()
    rot2 = mesh.copy()
    rot1.rotate_x(angle=angle, point=point, inplace=True)
    rot2.rotate_x(angle=angle - 360.0, point=point, inplace=True)
    assert np.allclose(rot1.points, rot2.points)

    # Rotate about y axis.
    rot1 = mesh.copy()
    rot2 = mesh.copy()
    rot1.rotate_y(angle=angle, point=point, inplace=True)
    rot2.rotate_y(angle=angle - 360.0, point=point, inplace=True)
    assert np.allclose(rot1.points, rot2.points)

    # Rotate about z axis.
    rot1 = mesh.copy()
    rot2 = mesh.copy()
    rot1.rotate_z(angle=angle, point=point, inplace=True)
    rot2.rotate_z(angle=angle - 360.0, point=point, inplace=True)
    assert np.allclose(rot1.points, rot2.points)

    # Rotate about custom vector.
    rot1 = mesh.copy()
    rot2 = mesh.copy()
    rot1.rotate_vector(vector=vector, angle=angle, point=point, inplace=True)
    rot2.rotate_vector(vector=vector, angle=angle - 360.0, point=point, inplace=True)
    assert np.allclose(rot1.points, rot2.points)


def test_rotate_x():
    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.rotate_x(30)
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.raises(TypeError):
        out = mesh.rotate_x(30, point=5)
    with pytest.raises(ValueError):
        out = mesh.rotate_x(30, point=[1, 3])


def test_rotate_y():
    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.rotate_y(30)
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.raises(TypeError):
        out = mesh.rotate_y(30, point=5)
    with pytest.raises(ValueError):
        out = mesh.rotate_y(30, point=[1, 3])


def test_rotate_z():
    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.rotate_z(30)
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.raises(TypeError):
        out = mesh.rotate_z(30, point=5)
    with pytest.raises(ValueError):
        out = mesh.rotate_z(30, point=[1, 3])


@pytest.mark.parametrize('method', ['rotate_x', 'rotate_y', 'rotate_z'])
def test_deprecation_rotate(sphere, method):
    meth = getattr(sphere, method)
    with pytest.warns(PyVistaDeprecationWarning):
        meth(30)


def test_rotate_vector():
    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.rotate_vector([1, 1, 1], 33)
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.raises(ValueError):
        out = mesh.rotate_vector([1, 1], 33)
    with pytest.raises(TypeError):
        out = mesh.rotate_vector(30, 33)


def test_transform_integers():
    # regression test for gh-1943
    points = [
        [0, 0, 0],
        [1, 0, 0],
        [0, 1, 0],
    ]
    # build vtkPolyData from scratch to enforce int data
    poly = vtk.vtkPolyData()
    poly.SetPoints(pyvista.vtk_points(points))
    poly = pyvista.wrap(poly)
    poly.verts = [1, 0, 1, 1, 1, 2]
    # define active and inactive vectors with int values
    for dataset_attrs in poly.point_data, poly.cell_data:
        for key in 'active_v', 'inactive_v', 'active_n', 'inactive_n':
            dataset_attrs[key] = poly.points
        dataset_attrs.active_vectors_name = 'active_v'
        dataset_attrs.active_normals_name = 'active_n'

    # active vectors and normals should be converted by default
    for key in 'active_v', 'inactive_v', 'active_n', 'inactive_n':
        assert poly.point_data[key].dtype == np.int_
        assert poly.cell_data[key].dtype == np.int_

    with pytest.warns(UserWarning):
        poly.rotate_x(angle=10, inplace=True)

    # check that points were converted and transformed correctly
    assert poly.points.dtype == np.float32
    assert poly.points[-1, 1] != 0
    # assert that exactly active vectors and normals were converted
    for key in 'active_v', 'active_n':
        assert poly.point_data[key].dtype == np.float32
        assert poly.cell_data[key].dtype == np.float32
    for key in 'inactive_v', 'inactive_n':
        assert poly.point_data[key].dtype == np.int_
        assert poly.cell_data[key].dtype == np.int_


@pytest.mark.xfail(reason='VTK bug')
def test_transform_integers_vtkbug_present():
    # verify that the VTK transform bug is still there
    # if this test starts to pass, we can remove the
    # automatic float conversion from ``DataSet.transform``
    # along with this test
    points = [
        [0, 0, 0],
        [1, 0, 0],
        [0, 1, 0],
    ]
    # build vtkPolyData from scratch to enforce int data
    poly = vtk.vtkPolyData()
    poly.SetPoints(pyvista.vtk_points(points))

    # manually put together a rotate_x(10) transform
    trans_arr = pyvista.transformations.axis_angle_rotation((1, 0, 0), 10, deg=True)
    trans_mat = pyvista.vtkmatrix_from_array(trans_arr)
    trans = vtk.vtkTransform()
    trans.SetMatrix(trans_mat)
    trans_filt = vtk.vtkTransformFilter()
    trans_filt.SetInputDataObject(poly)
    trans_filt.SetTransform(trans)
    trans_filt.Update()
    poly = pyvista.wrap(trans_filt.GetOutputDataObject(0))
    # the bug is that e.g. 0.98 gets truncated to 0
    assert poly.points[-1, 1] != 0


def test_deprecation_vector(sphere):
    with pytest.warns(PyVistaDeprecationWarning):
        sphere.rotate_vector([1, 1, 1], 33)


def test_scale():
    mesh = examples.load_airplane()

    xyz = np.random.random(3)
    scale1 = mesh.copy()
    scale2 = mesh.copy()
    scale1.scale(xyz, inplace=True)
    scale2.points *= xyz
    scale3 = mesh.scale(xyz, inplace=False)
    assert np.allclose(scale1.points, scale2.points)
    assert np.allclose(scale3.points, scale2.points)
    # test scalar scale case
    scale1 = mesh.copy()
    scale2 = mesh.copy()
    xyz = 4.0
    scale1.scale(xyz, inplace=True)
    scale2.scale([xyz] * 3, inplace=True)
    assert np.allclose(scale1.points, scale2.points)
    # test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.scale(xyz)
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.warns(PyVistaDeprecationWarning):
        scale1.scale(xyz)


def test_flip_x():
    mesh = examples.load_airplane()
    flip_x1 = mesh.copy()
    flip_x2 = mesh.copy()
    flip_x1.flip_x(point=(0, 0, 0), inplace=True)
    flip_x2.points[:, 0] *= -1.0
    assert np.allclose(flip_x1.points, flip_x2.points)
    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.flip_x()
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.warns(PyVistaDeprecationWarning):
        flip_x1.flip_x(point=(0, 0, 0))


def test_flip_y():
    mesh = examples.load_airplane()
    flip_y1 = mesh.copy()
    flip_y2 = mesh.copy()
    flip_y1.flip_y(point=(0, 0, 0), inplace=True)
    flip_y2.points[:, 1] *= -1.0
    assert np.allclose(flip_y1.points, flip_y2.points)
    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.flip_y()
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.warns(PyVistaDeprecationWarning):
        flip_y1.flip_y(point=(0, 0, 0))


def test_flip_z():
    mesh = examples.load_airplane()
    flip_z1 = mesh.copy()
    flip_z2 = mesh.copy()
    flip_z1.flip_z(point=(0, 0, 0), inplace=True)
    flip_z2.points[:, 2] *= -1.0
    assert np.allclose(flip_z1.points, flip_z2.points)
    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.flip_z()
    assert isinstance(out, pyvista.StructuredGrid)
    with pytest.warns(PyVistaDeprecationWarning):
        flip_z1.flip_z(point=(0, 0, 0))


def test_flip_normal():
    mesh = examples.load_airplane()
    flip_normal1 = mesh.copy()
    flip_normal2 = mesh.copy()
    flip_normal1.flip_normal(normal=[1.0, 0.0, 0.0], inplace=True)
    flip_normal2.flip_x(inplace=True)
    assert np.allclose(flip_normal1.points, flip_normal2.points)

    flip_normal3 = mesh.copy()
    flip_normal4 = mesh.copy()
    flip_normal3.flip_normal(normal=[0.0, 1.0, 0.0], inplace=True)
    flip_normal4.flip_y(inplace=True)
    assert np.allclose(flip_normal3.points, flip_normal4.points)

    flip_normal5 = mesh.copy()
    flip_normal6 = mesh.copy()
    flip_normal5.flip_normal(normal=[0.0, 0.0, 1.0], inplace=True)
    flip_normal6.flip_z(inplace=True)
    assert np.allclose(flip_normal5.points, flip_normal6.points)

    with pytest.warns(PyVistaDeprecationWarning):
        flip_normal5.flip_normal(normal=[0.0, 0.0, 1.0])

    # Test non-point-based mesh doesn't fail
    mesh = examples.load_uniform()
    out = mesh.flip_normal(normal=[1.0, 0.0, 0.5])
    assert isinstance(out, pyvista.StructuredGrid)


def test_active_normals(sphere):
    # both cell and point normals
    mesh = sphere.compute_normals()
    assert mesh.active_normals.shape[0] == mesh.n_points

    mesh = sphere.compute_normals(point_normals=False)
    assert mesh.active_normals.shape[0] == mesh.n_cells


@pytest.mark.skipif(
    pyvista.vtk_version_info < (9, 1, 0), reason="Requires VTK>=9.1.0 for a concrete PointSet class"
)
@pytest.mark.parametrize('deep', [False, True])
def test_cast_to_pointset(sphere, deep):
    pointset = sphere.cast_to_pointset(deep=deep)
    assert isinstance(pointset, pyvista.PointSet)

    pointset.points[:] = 0
    if deep:
        assert not np.allclose(sphere.points, pointset.points)
    else:
        assert np.allclose(sphere.points, pointset.points)


def test_partition(hexbeam):
    if pyvista.vtk_version_info < (9, 1, 0):
        with pytest.raises(VTKVersionError):
            hexbeam.partition(2)
        return
    # split as composite
    n_part = 2
    out = hexbeam.partition(n_part)
    assert isinstance(out, pyvista.MultiBlock)
    assert len(out) == 2

    # split as unstrucutred grid
    out = hexbeam.partition(hexbeam.n_cells, as_composite=False)
    assert isinstance(hexbeam, pyvista.UnstructuredGrid)
    assert out.n_points > hexbeam.n_points


def test_explode(datasets):
    for dataset in datasets:
        out = dataset.explode()
        assert out.n_cells == dataset.n_cells
        assert out.n_points > dataset.n_points


def test_separate_cells(hexbeam):
    assert hexbeam.n_points != hexbeam.n_cells * 8
    sep_grid = hexbeam.separate_cells()
    assert sep_grid.n_points == hexbeam.n_cells * 8


def test_volume_area():
    def assert_volume(grid):
        assert np.isclose(grid.volume, 64.0)
        assert np.isclose(grid.area, 0.0)

    def assert_area(grid):
        assert np.isclose(grid.volume, 0.0)
        assert np.isclose(grid.area, 16.0)

    # UniformGrid 3D size 4x4x4
    vol_grid = pyvista.UniformGrid(dims=(5, 5, 5))
    assert_volume(vol_grid)

    # 2D grid size 4x4
    surf_grid = pyvista.UniformGrid(dims=(5, 5, 1))
    assert_area(surf_grid)

    # UnstructuredGrid
    assert_volume(vol_grid.cast_to_unstructured_grid())
    assert_area(surf_grid.cast_to_unstructured_grid())

    # StructuredGrid
    assert_volume(vol_grid.cast_to_structured_grid())
    assert_area(surf_grid.cast_to_structured_grid())

    # Rectilinear
    assert_volume(vol_grid.cast_to_rectilinear_grid())
    assert_area(surf_grid.cast_to_rectilinear_grid())

    # PolyData
    # cube of size 4
    # PolyData is special because it is a 2D surface that can enclose a volume
    grid = pyvista.UniformGrid(dims=(5, 5, 5)).extract_surface()
    assert np.isclose(grid.volume, 64.0)
    assert np.isclose(grid.area, 96.0)


# tests/test_datasetattributes.py

import os
from string import ascii_letters, digits, whitespace
import sys

from hypothesis import HealthCheck, given, settings
from hypothesis.extra.numpy import arrays
from hypothesis.strategies import integers, lists, text
import numpy as np
from pytest import fixture, mark, raises

import pyvista
from pyvista.utilities import FieldAssociation

skip_windows = mark.skipif(os.name == 'nt', reason='Test fails on Windows')


@fixture()
def hexbeam_point_attributes(hexbeam):
    return hexbeam.point_data


@fixture()
def hexbeam_field_attributes(hexbeam):
    return hexbeam.field_data


@fixture()
def insert_arange_narray(hexbeam_point_attributes):
    n_points = hexbeam_point_attributes.dataset.GetNumberOfPoints()
    sample_array = np.arange(n_points)
    hexbeam_point_attributes.set_array(sample_array, 'sample_array')
    return hexbeam_point_attributes, sample_array


@fixture()
def insert_bool_array(hexbeam_point_attributes):
    n_points = hexbeam_point_attributes.dataset.GetNumberOfPoints()
    sample_array = np.ones(n_points, np.bool_)
    hexbeam_point_attributes.set_array(sample_array, 'sample_array')
    return hexbeam_point_attributes, sample_array


@fixture()
def insert_string_array(hexbeam_point_attributes):
    n_points = hexbeam_point_attributes.dataset.GetNumberOfPoints()
    sample_array = np.repeat("A", n_points)
    hexbeam_point_attributes.set_array(sample_array, 'sample_array')
    return hexbeam_point_attributes, sample_array


def test_init(hexbeam):
    attributes = pyvista.DataSetAttributes(
        hexbeam.GetPointData(), dataset=hexbeam, association=FieldAssociation.POINT
    )
    assert attributes.VTKObject == hexbeam.GetPointData()
    assert attributes.dataset == hexbeam
    assert attributes.association == FieldAssociation.POINT


def test_bool(hexbeam_point_attributes):
    assert bool(len(hexbeam_point_attributes)) is bool(hexbeam_point_attributes)
    hexbeam_point_attributes.clear()
    assert bool(len(hexbeam_point_attributes)) is bool(hexbeam_point_attributes)


def test_getitem(hexbeam_point_attributes):
    with raises(TypeError, match='Only strings'):
        hexbeam_point_attributes[0]


def test_setitem(hexbeam_point_attributes):
    with raises(TypeError, match='Only strings'):
        hexbeam_point_attributes[0]


def test_repr(hexbeam_point_attributes):
    repr_str = str(hexbeam_point_attributes)
    assert 'POINT' in repr_str
    assert 'DataSetAttributes' in repr_str
    assert 'Contains arrays' in repr_str
    assert '...' not in repr_str

    # ensure long names are abbreviated
    sz = hexbeam_point_attributes.values()[0].size
    data = np.zeros(sz)
    hexbeam_point_attributes['thisisaverylongnameover20char'] = data
    assert '...' in str(hexbeam_point_attributes)

    # ensure datatype str is in repr
    assert str(data.dtype) in str(hexbeam_point_attributes)

    # ensure VECTOR in repr
    vectors0 = np.random.random((sz, 3))
    hexbeam_point_attributes.set_vectors(vectors0, 'vectors0')
    assert 'VECTOR' in str(hexbeam_point_attributes)


def test_empty_active_vectors(hexbeam):
    assert hexbeam.active_vectors is None


def test_valid_array_len_points(hexbeam):
    assert hexbeam.point_data.valid_array_len == hexbeam.n_points


def test_valid_array_len_cells(hexbeam):
    assert hexbeam.cell_data.valid_array_len == hexbeam.n_cells


def test_valid_array_len_field(hexbeam):
    assert hexbeam.field_data.valid_array_len is None


def test_get(sphere):
    point_data = np.arange(sphere.n_points)
    sphere.clear_data()
    key = 'my-data'
    sphere.point_data[key] = point_data
    assert np.array_equal(sphere.point_data.get(key), point_data)
    assert sphere.point_data.get('invalid-key') is None

    default = 'default'
    assert sphere.point_data.get('invalid-key', default) is default


def test_active_scalars_name(sphere):
    sphere.clear_data()
    assert sphere.point_data.active_scalars_name is None

    key = 'data0'
    sphere.point_data[key] = range(sphere.n_points)
    assert sphere.point_data.active_scalars_name == key

    sphere.point_data.active_scalars_name = None
    assert sphere.point_data.active_scalars_name is None


def test_set_scalars(sphere):
    scalars = np.array(sphere.n_points)
    key = 'scalars'
    sphere.point_data.set_scalars(scalars, key)
    assert sphere.point_data.active_scalars_name == key


def test_eq(sphere):
    sphere = pyvista.Sphere()
    sphere.clear_data()

    # check wrong type
    assert sphere.point_data != [1, 2, 3]

    sphere.point_data['data0'] = np.zeros(sphere.n_points)
    sphere.point_data['data1'] = np.arange(sphere.n_points)
    deep_cp = sphere.copy(deep=True)
    shal_cp = sphere.copy(deep=False)
    assert sphere.point_data == deep_cp.point_data
    assert sphere.point_data == shal_cp.point_data

    # verify inplace change
    sphere.point_data['data0'] += 1
    assert sphere.point_data != deep_cp.point_data
    assert sphere.point_data == shal_cp.point_data

    # verify key removal
    deep_cp = sphere.copy(deep=True)
    del deep_cp.point_data['data0']
    assert sphere.point_data != deep_cp.point_data


def test_add_matrix(hexbeam):
    mat_shape = (hexbeam.n_points, 3, 2)
    mat = np.random.random(mat_shape)
    hexbeam.point_data.set_array(mat, 'mat')
    matout = hexbeam.point_data['mat'].reshape(mat_shape)
    assert np.allclose(mat, matout)


def test_set_active_scalars_fail(hexbeam):
    with raises(ValueError):
        hexbeam.set_active_scalars('foo', preference='field')
    with raises(KeyError):
        hexbeam.set_active_scalars('foo')


def test_set_active_vectors(hexbeam):
    vectors = np.random.random((hexbeam.n_points, 3))
    hexbeam['vectors'] = vectors
    hexbeam.set_active_vectors('vectors')
    assert np.allclose(hexbeam.active_vectors, vectors)


def test_set_vectors(hexbeam):
    assert hexbeam.point_data.active_vectors is None
    vectors = np.random.random((hexbeam.n_points, 3))
    hexbeam.point_data.set_vectors(vectors, 'my-vectors')
    assert np.allclose(hexbeam.point_data.active_vectors, vectors)

    # check clearing
    hexbeam.point_data.active_vectors_name = None
    assert hexbeam.point_data.active_vectors_name is None


def test_set_invalid_vectors(hexbeam):
    # verify non-vector data does not become active vectors
    not_vectors = np.random.random(hexbeam.n_points)
    with raises(ValueError):
        hexbeam.point_data.set_vectors(not_vectors, 'my-vectors')


def test_set_tcoords_name():
    mesh = pyvista.Cube()
    old_name = mesh.point_data.active_t_coords_name
    assert mesh.point_data.active_t_coords_name is not None
    mesh.point_data.active_t_coords_name = None
    assert mesh.point_data.active_t_coords_name is None

    mesh.point_data.active_t_coords_name = old_name
    assert mesh.point_data.active_t_coords_name == old_name


def test_set_bitarray(hexbeam):
    """Test bitarrays are properly loaded and represented in datasetattributes."""
    hexbeam.clear_data()
    assert 'bool' not in str(hexbeam.point_data)

    arr = np.zeros(hexbeam.n_points, dtype=bool)
    arr[::2] = 1
    hexbeam.point_data['bitarray'] = arr

    assert hexbeam.point_data['bitarray'].dtype == np.bool_
    assert 'bool' in str(hexbeam.point_data)
    assert np.allclose(hexbeam.point_data['bitarray'], arr)

    # ensure overwriting the type changes association
    hexbeam.point_data['bitarray'] = arr.astype(np.int32)
    assert hexbeam.point_data['bitarray'].dtype == np.int32


@mark.parametrize('array_key', ['invalid_array_name', -1])
def test_get_array_should_fail_if_does_not_exist(array_key, hexbeam_point_attributes):
    with raises(KeyError):
        hexbeam_point_attributes.get_array(array_key)


def test_get_array_should_return_bool_array(insert_bool_array):
    dsa, _ = insert_bool_array
    output_array = dsa.get_array('sample_array')
    assert output_array.dtype == np.bool_


def test_get_array_bool_array_should_be_identical(insert_bool_array):
    dsa, sample_array = insert_bool_array
    output_array = dsa.get_array('sample_array')
    assert np.array_equal(output_array, sample_array)


def test_add_should_not_add_none_array(hexbeam_point_attributes):
    with raises(TypeError):
        hexbeam_point_attributes.set_array(None, 'sample_array')


def test_add_should_contain_array_name(insert_arange_narray):
    dsa, _ = insert_arange_narray
    assert 'sample_array' in dsa


def test_add_should_contain_exact_array(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    assert np.array_equal(sample_array, dsa['sample_array'])


def test_getters_should_return_same_result(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    result_a = dsa.get_array('sample_array')
    result_b = dsa['sample_array']
    assert np.array_equal(result_a, result_b)


def test_contains_should_contain_when_added(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    assert 'sample_array' in dsa


def test_set_array_catch(hexbeam):
    data = np.zeros(hexbeam.n_points)
    with raises(TypeError, match='`name` must be a string'):
        hexbeam.point_data.set_array(data, name=['foo'])


@settings(max_examples=20, suppress_health_check=[HealthCheck.function_scoped_fixture])
@given(scalar=integers(min_value=-sys.maxsize - 1, max_value=sys.maxsize))
def test_set_array_should_accept_scalar_value(scalar, hexbeam_point_attributes):
    hexbeam_point_attributes.set_array(scalar, name='int_array')


@settings(max_examples=20, suppress_health_check=[HealthCheck.function_scoped_fixture])
@given(scalar=integers(min_value=-sys.maxsize - 1, max_value=sys.maxsize))
def test_set_array_scalar_value_should_give_array(scalar, hexbeam_point_attributes):
    hexbeam_point_attributes.set_array(scalar, name='int_array')
    expected = np.full(hexbeam_point_attributes.dataset.n_points, scalar)
    assert np.array_equal(expected, hexbeam_point_attributes['int_array'])


@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
@given(arr=lists(text(alphabet=ascii_letters + digits + whitespace), max_size=16))
def test_set_array_string_lists_should_equal(arr, hexbeam_field_attributes):
    hexbeam_field_attributes['string_arr'] = arr
    assert arr == hexbeam_field_attributes['string_arr'].tolist()


@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
@given(arr=arrays(dtype='U', shape=10))
def test_set_array_string_array_should_equal(arr, hexbeam_field_attributes):
    if not ''.join(arr).isascii():
        with raises(ValueError, match='non-ASCII'):
            hexbeam_field_attributes['string_arr'] = arr
        return

    hexbeam_field_attributes['string_arr'] = arr
    assert np.array_equiv(arr, hexbeam_field_attributes['string_arr'])


def test_hexbeam_field_attributes_active_scalars(hexbeam_field_attributes):
    with raises(TypeError):
        hexbeam_field_attributes.active_scalars


def test_should_remove_array(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    dsa.remove('sample_array')
    assert 'sample_array' not in dsa


def test_should_del_array(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    del dsa['sample_array']
    assert 'sample_array' not in dsa


def test_should_pop_array(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    dsa.pop('sample_array')
    assert 'sample_array' not in dsa


def test_pop_should_return_arange_narray(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    other_array = dsa.pop('sample_array')
    assert np.array_equal(other_array, sample_array)


def test_pop_should_return_bool_array(insert_bool_array):
    dsa, sample_array = insert_bool_array
    other_array = dsa.pop('sample_array')
    assert np.array_equal(other_array, sample_array)


def test_pop_should_return_string_array(insert_string_array):
    dsa, sample_array = insert_string_array
    other_array = dsa.pop('sample_array')
    assert np.array_equal(other_array, sample_array)


def test_should_pop_array_invalid(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    key = 'invalid_key'
    assert key not in dsa
    default = 20
    assert dsa.pop(key, default) is default


@mark.parametrize('removed_key', [None, 'nonexistent_array_name', '', -1])
def test_remove_should_fail_on_bad_argument(removed_key, hexbeam_point_attributes):
    if removed_key in [None, -1]:
        with raises(TypeError):
            hexbeam_point_attributes.remove(removed_key)
    else:
        with raises(KeyError):
            hexbeam_point_attributes.remove(removed_key)


@mark.parametrize('removed_key', [None, 'nonexistent_array_name', '', -1])
def test_del_should_fail_bad_argument(removed_key, hexbeam_point_attributes):
    if removed_key in [None, -1]:
        with raises(TypeError):
            del hexbeam_point_attributes[removed_key]
    else:
        with raises(KeyError):
            del hexbeam_point_attributes[removed_key]


@mark.parametrize('removed_key', [None, 'nonexistent_array_name', '', -1])
def test_pop_should_fail_bad_argument(removed_key, hexbeam_point_attributes):
    if removed_key in [None, -1]:
        with raises(TypeError):
            hexbeam_point_attributes.pop(removed_key)
    else:
        with raises(KeyError):
            hexbeam_point_attributes.pop(removed_key)


def test_length_should_increment_on_set_array(hexbeam_point_attributes):
    initial_len = len(hexbeam_point_attributes)
    n_points = hexbeam_point_attributes.dataset.GetNumberOfPoints()
    sample_array = np.arange(n_points)
    hexbeam_point_attributes.set_array(sample_array, 'sample_array')
    assert len(hexbeam_point_attributes) == initial_len + 1


def test_length_should_decrement_on_remove(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    initial_len = len(dsa)
    dsa.remove('sample_array')
    assert len(dsa) == initial_len - 1


def test_length_should_decrement_on_pop(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    initial_len = len(dsa)
    dsa.pop('sample_array')
    assert len(dsa) == initial_len - 1


def test_length_should_be_0_on_clear(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    assert len(dsa) != 0
    dsa.clear()
    assert len(dsa) == 0


def test_keys_should_be_strings(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    for name in dsa.keys():
        assert type(name) == str


def test_key_should_exist(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    assert 'sample_array' in dsa.keys()


def test_values_should_be_pyvista_ndarrays(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    for arr in dsa.values():
        assert type(arr) == pyvista.pyvista_ndarray


def test_value_should_exist(insert_arange_narray):
    dsa, sample_array = insert_arange_narray
    for arr in dsa.values():
        if np.array_equal(sample_array, arr):
            return
    raise AssertionError('Array not in values.')


def test_active_scalars_setter(hexbeam_point_attributes):
    dsa = hexbeam_point_attributes
    assert dsa.active_scalars is None

    dsa.active_scalars_name = 'sample_point_scalars'
    assert dsa.active_scalars is not None
    assert dsa.GetScalars().GetName() == 'sample_point_scalars'


@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
@given(arr=arrays(dtype='U', shape=10))
def test_preserve_field_data_after_extract_cells(hexbeam, arr):
    if not ''.join(arr).isascii():
        with raises(ValueError, match='non-ASCII'):
            hexbeam.field_data["foo"] = arr
        return

    # https://github.com/pyvista/pyvista/pull/934
    hexbeam.field_data["foo"] = arr
    extracted = hexbeam.extract_cells([0, 1, 2, 3])
    assert "foo" in extracted.field_data


def test_assign_labels_to_points(hexbeam):
    hexbeam.point_data.clear()
    labels = [f"Label {i}" for i in range(hexbeam.n_points)]
    hexbeam['labels'] = labels
    assert (hexbeam['labels'] == labels).all()


def test_normals_get(plane):
    plane.clear_data()
    assert plane.point_data.active_normals is None

    plane_w_normals = plane.compute_normals()
    assert np.array_equal(plane_w_normals.point_data.active_normals, plane_w_normals.point_normals)

    plane.point_data.active_normals_name = None
    assert plane.point_data.active_normals_name is None


def test_normals_set():
    plane = pyvista.Plane(i_resolution=1, j_resolution=1)
    plane.point_data.normals = plane.point_normals
    assert np.array_equal(plane.point_data.active_normals, plane.point_normals)

    with raises(ValueError, match='must be a 2-dim'):
        plane.point_data.active_normals = [1]
    with raises(ValueError, match='must match number of points'):
        plane.point_data.active_normals = [[1, 1, 1], [0, 0, 0]]
    with raises(ValueError, match='Normals must have exactly 3 components'):
        plane.point_data.active_normals = [[1, 1], [0, 0], [0, 0], [0, 0]]


def test_normals_name(plane):
    plane.clear_data()
    assert plane.point_data.active_normals_name is None

    key = 'data'
    plane.point_data.set_array(plane.point_normals, key)
    plane.point_data.active_normals_name = key
    assert plane.point_data.active_normals_name == key


def test_normals_raise_field(plane):
    with raises(AttributeError):
        plane.field_data.active_normals


def test_add_two_vectors():
    """Ensure we can add two vectors"""
    mesh = pyvista.Plane(i_resolution=1, j_resolution=1)
    mesh.point_data.set_array(range(4), 'my-scalars')
    mesh.point_data.set_array(range(5, 9), 'my-other-scalars')
    vectors0 = np.random.random((4, 3))
    mesh.point_data.set_vectors(vectors0, 'vectors0')
    vectors1 = np.random.random((4, 3))
    mesh.point_data.set_vectors(vectors1, 'vectors1')

    assert 'vectors0' in mesh.point_data
    assert 'vectors1' in mesh.point_data


def test_active_vectors_name_setter():
    mesh = pyvista.Plane(i_resolution=1, j_resolution=1)
    mesh.point_data.set_array(range(4), 'my-scalars')
    vectors0 = np.random.random((4, 3))
    mesh.point_data.set_vectors(vectors0, 'vectors0')
    vectors1 = np.random.random((4, 3))
    mesh.point_data.set_vectors(vectors1, 'vectors1')

    assert mesh.point_data.active_vectors_name == 'vectors1'
    mesh.point_data.active_vectors_name = 'vectors0'
    assert mesh.point_data.active_vectors_name == 'vectors0'

    with raises(KeyError, match='does not contain'):
        mesh.point_data.active_vectors_name = 'not a valid key'

    with raises(ValueError, match='needs 3 components'):
        mesh.point_data.active_vectors_name = 'my-scalars'


def test_active_vectors_eq():
    mesh = pyvista.Plane(i_resolution=1, j_resolution=1)
    vectors0 = np.random.random((4, 3))
    mesh.point_data.set_vectors(vectors0, 'vectors0')
    vectors1 = np.random.random((4, 3))
    mesh.point_data.set_vectors(vectors1, 'vectors1')

    other_mesh = mesh.copy(deep=True)
    assert mesh == other_mesh

    mesh.point_data.active_vectors_name = 'vectors0'
    assert mesh != other_mesh


def test_active_t_coords_name(plane):
    plane.point_data['arr'] = plane.point_data.active_t_coords
    plane.point_data.active_t_coords_name = 'arr'

    with raises(AttributeError):
        plane.field_data.active_t_coords_name = 'arr'


@skip_windows  # windows doesn't support np.complex256
def test_complex_raises(plane):
    with raises(ValueError, match='Only numpy.complex64'):
        plane.point_data['data'] = np.empty(plane.n_points, dtype=np.complex256)


@mark.parametrize('dtype_str', ['complex64', 'complex128'])
def test_complex(plane, dtype_str):
    """Test if complex data can be properly represented in datasetattributes."""
    dtype = np.dtype(dtype_str)
    name = 'my_data'

    with raises(ValueError, match='Complex data must be single dimensional'):
        plane.point_data[name] = np.empty((plane.n_points, 2), dtype=dtype)

    real_type = np.float32 if dtype == np.complex64 else np.float64
    data = np.random.random((plane.n_points, 2)).astype(real_type).view(dtype).ravel()
    plane.point_data[name] = data
    assert np.array_equal(plane.point_data[name], data)

    assert dtype_str in str(plane.point_data)

    # test setter
    plane.active_scalars_name = name

    # ensure that association is removed when changing datatype
    assert plane.point_data[name].dtype == dtype
    plane.point_data[name] = plane.point_data[name].real
    assert np.issubdtype(plane.point_data[name].dtype, real_type)


# tests/test_demos.py

import os
import platform

import numpy as np
import pytest

import pyvista
from pyvista import demos
from pyvista.plotting import system_supports_plotting

skip_no_plotting = pytest.mark.skipif(
    not system_supports_plotting(), reason="Test requires system to support plotting"
)


@skip_no_plotting
def test_plot_glyphs():
    demos.plot_glyphs(2)


def test_atomized():
    grid = demos.logo_atomized(density=0.2, scale=0.6)
    assert grid.n_cells


def test_logo_basic():
    pd = demos.logo_basic()
    assert pd.n_faces


def test_logo_voxel():
    grid = demos.logo_voxel()
    assert grid.n_cells


@pytest.mark.skipif(
    platform.system() == 'Darwin', reason='MacOS testing on Azure fails when downloading'
)
@skip_no_plotting
@pytest.mark.skipif(os.name == 'nt', reason='Test fails on Windows')
def test_plot_logo():
    # simply should not fail
    demos.plot_logo()


@skip_no_plotting
def test_plot_datasets():
    # simply should not fail
    demos.plot_datasets()


@skip_no_plotting
def test_plot_wave():
    points = demos.plot_wave(wavetime=0.1)
    assert isinstance(points, np.ndarray)


@skip_no_plotting
def test_beam_example():
    demos.plot_beam()


@skip_no_plotting
def test_plot_ants_plane():
    demos.plot_ants_plane()


@skip_no_plotting
def test_orientation_cube():
    pl = demos.orientation_plotter()
    assert isinstance(pl, pyvista.Plotter)
    pl.show()


# tests/test_export.py

import os

import numpy as np
import pytest

import pyvista
from pyvista import examples as ex
from pyvista.plotting import system_supports_plotting


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_export_single(tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('scene-single'))
    data = ex.load_airplane()
    # Create the scene
    plotter = pyvista.Plotter()
    plotter.add_mesh(data)
    plotter.export_vtkjs(filename)
    # Now make sure the file is there
    assert os.path.isfile(f'{filename}.vtkjs')


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_export_multi(tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('scene-multi'))
    multi = pyvista.MultiBlock()
    # Add examples
    multi.append(ex.load_ant())
    multi.append(ex.load_sphere())
    multi.append(ex.load_uniform())
    multi.append(ex.load_airplane())
    multi.append(ex.load_rectilinear())
    # Create the scene
    plotter = pyvista.Plotter()
    plotter.add_mesh(multi)
    plotter.export_vtkjs(filename, compress_arrays=True)
    # Now make sure the file is there
    assert os.path.isfile(f'{filename}.vtkjs')


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_export_texture(tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('scene-texture'))
    data = ex.load_globe()
    # Create the scene
    plotter = pyvista.Plotter()
    plotter.add_mesh(data, texture=True)
    plotter.export_vtkjs(filename)
    # Now make sure the file is there
    assert os.path.isfile(f'{filename}.vtkjs')


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_export_verts(tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('scene-verts'))
    data = pyvista.PolyData(np.random.rand(100, 3))
    # Create the scene
    plotter = pyvista.Plotter()
    plotter.add_mesh(data)
    plotter.export_vtkjs(filename)
    # Now make sure the file is there
    assert os.path.isfile(f'{filename}.vtkjs')


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_export_color(tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('scene-color'))
    data = ex.load_airplane()
    # Create the scene
    plotter = pyvista.Plotter()
    plotter.add_mesh(data, color='yellow')
    plotter.export_vtkjs(filename)
    # Now make sure the file is there
    assert os.path.isfile(f'{filename}.vtkjs')


def test_vtkjs_url():
    file_url = 'https://www.dropbox.com/s/6m5ttdbv5bf4ngj/ripple.vtkjs?dl=0'
    vtkjs_url = 'http://viewer.pyvista.org/?fileURL=https://dl.dropbox.com/s/6m5ttdbv5bf4ngj/ripple.vtkjs?dl=0'
    assert vtkjs_url in pyvista.get_vtkjs_url(file_url)
    assert vtkjs_url in pyvista.get_vtkjs_url('dropbox', file_url)


# tests/test_filters.py

import itertools
import os
import platform
import sys
from unittest.mock import Mock, patch

import numpy as np
import pytest
from vtk import VTK_QUADRATIC_HEXAHEDRON, VTK_QUADRATIC_TRIANGLE

import pyvista
from pyvista import examples
from pyvista._vtk import VTK9, vtkStaticCellLocator
from pyvista.core.errors import NotAllTrianglesError, VTKVersionError
from pyvista.errors import MissingDataError
from pyvista.utilities.misc import can_create_mpl_figure

normals = ['x', 'y', '-z', (1, 1, 1), (3.3, 5.4, 0.8)]

skip_py2_nobind = pytest.mark.skipif(
    int(sys.version[0]) < 3, reason="Python 2 doesn't support binding methods"
)

skip_mac = pytest.mark.skipif(platform.system() == 'Darwin', reason="Flaky Mac tests")
skip_no_mpl_figure = pytest.mark.skipif(
    not can_create_mpl_figure(), reason="Cannot create a figure using matplotlib"
)


def aprox_le(a, b, rtol=1e-5, atol=1e-8):
    """Return if that ``a <= b`` within a tolerance.

    See numpy.isclose for the description of ``rtol`` and ``atol``.

    """
    if a < b:
        return True
    else:
        return np.isclose(a, b, rtol, atol)


class GetOutput:
    """Helper class to patch ``pyvista.filters._get_output`` which captures the raw VTK algorithm objects at the time
    ``_get_output`` is invoked.
    """

    def __init__(self):
        self._mock = Mock()

    def __call__(self, algorithm, *args, **kwargs):
        self._mock(algorithm, *args, **kwargs)
        return pyvista.core.filters._get_output(algorithm)

    def reset(self, *args, **kwargs):
        self._mock.reset_mock(*args, **kwargs)

    @property
    def latest_algorithm(self):
        return self._mock.call_args_list[-1][0][0]


@pytest.fixture
def composite(datasets):
    return pyvista.MultiBlock(datasets)


@pytest.fixture()
def grid():
    return pyvista.UnstructuredGrid(examples.hexbeamfile)


@pytest.fixture(scope='module')
def uniform_vec():
    nx, ny, nz = 20, 15, 5
    origin = (-(nx - 1) * 0.1 / 2, -(ny - 1) * 0.1 / 2, -(nz - 1) * 0.1 / 2)
    mesh = pyvista.UniformGrid(dims=(nx, ny, nz), spacing=(0.1, 0.1, 0.1), origin=origin)
    mesh['vectors'] = mesh.points
    return mesh


def test_datasetfilters_init():
    with pytest.raises(TypeError):
        pyvista.core.filters.DataSetFilters()


def test_clip_filter(datasets):
    """This tests the clip filter on all datatypes available filters"""
    for i, dataset in enumerate(datasets):
        clp = dataset.clip(normal=normals[i], invert=True)
        assert clp is not None
        if isinstance(dataset, pyvista.PolyData):
            assert isinstance(clp, pyvista.PolyData)
        else:
            assert isinstance(clp, pyvista.UnstructuredGrid)

    # clip with get_clipped=True
    for i, dataset in enumerate(datasets):
        clp1, clp2 = dataset.clip(normal=normals[i], invert=True, return_clipped=True)
        for clp in (clp1, clp2):
            if isinstance(dataset, pyvista.PolyData):
                assert isinstance(clp, pyvista.PolyData)
            else:
                assert isinstance(clp, pyvista.UnstructuredGrid)

    # crinkle clip
    clp = pyvista.Wavelet().clip(normal=(1, 1, 1), crinkle=True)
    assert clp is not None
    clp1, clp2 = pyvista.Wavelet().clip(normal=(1, 1, 1), return_clipped=True, crinkle=True)
    assert clp1 is not None
    assert clp2 is not None


@skip_mac
@pytest.mark.parametrize('both', [False, True])
@pytest.mark.parametrize('invert', [False, True])
def test_clip_by_scalars_filter(datasets, both, invert):
    """This tests the clip filter on all datatypes available filters"""
    for dataset_in in datasets:
        dataset = dataset_in.copy()  # don't modify in-place
        dataset.point_data['to_clip'] = np.arange(dataset.n_points)

        clip_value = dataset.n_points / 2

        if both:
            clps = dataset.clip_scalar(
                scalars='to_clip', value=clip_value, both=True, invert=invert
            )
            assert len(clps) == 2
            expect_les = (invert, not invert)
        else:
            clps = (
                dataset.clip_scalar(scalars='to_clip', value=clip_value, both=False, invert=invert),
            )
            assert len(clps) == 1
            expect_les = (invert,)

        for clp, expect_le in zip(clps, expect_les):
            assert clp is not None
            if isinstance(dataset, pyvista.PolyData):
                assert isinstance(clp, pyvista.PolyData)
            else:
                assert isinstance(clp, pyvista.UnstructuredGrid)

            if expect_le:
                # VTK clip filter appears to not clip exactly to the clip value.
                # here we allow for a wider range of acceptable values
                assert aprox_le(clp.point_data['to_clip'].max(), clip_value, rtol=1e-1)
            else:
                assert clp.point_data['to_clip'].max() >= clip_value


def test_clip_filter_no_active(sphere):
    # test no active scalars case
    sphere.point_data.set_array(sphere.points[:, 2], 'data')
    assert sphere.active_scalars_name is None
    clp = sphere.clip_scalar()
    assert clp.n_points < sphere.n_points


def test_clip_filter_scalar_multiple():
    mesh = pyvista.Plane()
    mesh['x'] = mesh.points[:, 0].copy()
    mesh['y'] = mesh.points[:, 1].copy()
    mesh['z'] = mesh.points[:, 2].copy()

    mesh_clip_x = mesh.clip_scalar(scalars='x', value=0.0)
    assert np.isclose(mesh_clip_x['x'].max(), 0.0)
    mesh_clip_y = mesh.clip_scalar(scalars='y', value=0.0)
    assert np.isclose(mesh_clip_y['y'].max(), 0.0)
    mesh_clip_z = mesh.clip_scalar(scalars='z', value=0.0)
    assert np.isclose(mesh_clip_z['z'].max(), 0.0)


@skip_py2_nobind
def test_clip_filter_composite(composite):
    # Now test composite data structures
    output = composite.clip(normal=normals[0], invert=False)
    assert output.n_blocks == composite.n_blocks


def test_clip_box(datasets):
    for dataset in datasets:
        clp = dataset.clip_box(invert=True, progress_bar=True)
        assert clp is not None
        assert isinstance(clp, pyvista.UnstructuredGrid)
        clp2 = dataset.clip_box(merge_points=False)
        assert clp2 is not None

    dataset = examples.load_airplane()
    # test length 3 bounds
    result = dataset.clip_box(bounds=(900, 900, 200), invert=False, progress_bar=True)
    dataset = examples.load_uniform()
    result = dataset.clip_box(bounds=0.5, progress_bar=True)
    assert result.n_cells
    with pytest.raises(ValueError):
        dataset.clip_box(bounds=(5, 6), progress_bar=True)
    # allow Sequence but not Iterable bounds
    with pytest.raises(TypeError):
        dataset.clip_box(bounds={5, 6, 7}, progress_bar=True)
    # Test with a poly data box
    mesh = examples.load_airplane()
    box = pyvista.Cube(
        center=(0.9e3, 0.2e3, mesh.center[2]), x_length=500, y_length=500, z_length=500
    )
    box.rotate_z(33, inplace=True)
    result = mesh.clip_box(box, invert=False, progress_bar=True)
    assert result.n_cells
    result = mesh.clip_box(box, invert=True, progress_bar=True)
    assert result.n_cells

    with pytest.raises(ValueError):
        dataset.clip_box(bounds=pyvista.Sphere(), progress_bar=True)

    # crinkle clip
    surf = pyvista.Sphere(radius=3)
    vol = pyvista.voxelize(surf)
    cube = pyvista.Cube().rotate_x(33, inplace=False)
    clp = vol.clip_box(bounds=cube, invert=False, crinkle=True)
    assert clp is not None


@skip_py2_nobind
def test_clip_box_composite(composite):
    # Now test composite data structures
    output = composite.clip_box(invert=False, progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_clip_surface():
    surface = pyvista.Cone(
        direction=(0, 0, -1),
        height=3.0,
        radius=1,
        resolution=50,
    )
    xx = yy = zz = 1 - np.linspace(0, 51, 11) * 2 / 50
    dataset = pyvista.RectilinearGrid(xx, yy, zz)
    clipped = dataset.clip_surface(surface, invert=False, progress_bar=True)
    assert isinstance(clipped, pyvista.UnstructuredGrid)
    clipped = dataset.clip_surface(surface, invert=False, compute_distance=True, progress_bar=True)
    assert isinstance(clipped, pyvista.UnstructuredGrid)
    assert 'implicit_distance' in clipped.array_names
    clipped = dataset.clip_surface(surface.cast_to_unstructured_grid(), progress_bar=True)
    assert isinstance(clipped, pyvista.UnstructuredGrid)
    assert 'implicit_distance' in clipped.array_names
    # Test crinkle
    clipped = dataset.clip_surface(surface, invert=False, progress_bar=True, crinkle=True)
    assert isinstance(clipped, pyvista.UnstructuredGrid)


def test_clip_closed_surface():
    closed_surface = pyvista.Sphere()
    clipped = closed_surface.clip_closed_surface(progress_bar=True)
    assert clipped.n_open_edges == 0
    open_surface = closed_surface.clip(progress_bar=True)
    with pytest.raises(ValueError):
        _ = open_surface.clip_closed_surface()


def test_implicit_distance():
    surface = pyvista.Cone(
        direction=(0, 0, -1),
        height=3.0,
        radius=1,
        resolution=50,
    )
    xx = yy = zz = 1 - np.linspace(0, 51, 11) * 2 / 50
    dataset = pyvista.RectilinearGrid(xx, yy, zz)
    res = dataset.compute_implicit_distance(surface)
    assert "implicit_distance" in res.point_data
    dataset.compute_implicit_distance(surface, inplace=True)
    assert "implicit_distance" in dataset.point_data


def test_slice_filter(datasets):
    """This tests the slice filter on all datatypes available filters"""
    for i, dataset in enumerate(datasets):
        slc = dataset.slice(normal=normals[i], progress_bar=True)
        assert slc is not None
        assert isinstance(slc, pyvista.PolyData)
    dataset = examples.load_uniform()
    slc = dataset.slice(contour=True, progress_bar=True)
    assert slc is not None
    assert isinstance(slc, pyvista.PolyData)
    result = dataset.slice(origin=(10, 15, 15), progress_bar=True)
    assert result.n_points < 1


@skip_py2_nobind
def test_slice_filter_composite(composite):
    # Now test composite data structures
    output = composite.slice(normal=normals[0], progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_slice_orthogonal_filter(datasets):
    """This tests the slice filter on all datatypes available filters"""

    for dataset in datasets:
        slices = dataset.slice_orthogonal(progress_bar=True)
        assert slices is not None
        assert isinstance(slices, pyvista.MultiBlock)
        assert slices.n_blocks == 3
        for slc in slices:
            assert isinstance(slc, pyvista.PolyData)


@skip_py2_nobind
def test_slice_orthogonal_filter_composite(composite):
    # Now test composite data structures
    output = composite.slice_orthogonal(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_slice_along_axis(datasets):
    """Test the many slices along axis filter"""
    axii = ['x', 'y', 'z', 'y', 0]
    ns = [2, 3, 4, 10, 20, 13]
    for i, dataset in enumerate(datasets):
        slices = dataset.slice_along_axis(n=ns[i], axis=axii[i], progress_bar=True)
        assert slices is not None
        assert isinstance(slices, pyvista.MultiBlock)
        assert slices.n_blocks == ns[i]
        for slc in slices:
            assert isinstance(slc, pyvista.PolyData)
    dataset = examples.load_uniform()
    with pytest.raises(ValueError):
        dataset.slice_along_axis(axis='u')


@skip_py2_nobind
def test_slice_along_axis_composite(composite):
    # Now test composite data structures
    output = composite.slice_along_axis(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_threshold(datasets):
    for dataset in datasets[0:3]:
        thresh = dataset.threshold(progress_bar=True)
        assert thresh is not None
        assert isinstance(thresh, pyvista.UnstructuredGrid)
    # Test value ranges
    dataset = examples.load_uniform()  # UniformGrid
    thresh = dataset.threshold(100, invert=False, progress_bar=True)
    assert thresh is not None
    assert isinstance(thresh, pyvista.UnstructuredGrid)
    thresh = dataset.threshold([100, 500], invert=False, progress_bar=True)
    assert thresh is not None
    assert isinstance(thresh, pyvista.UnstructuredGrid)
    thresh = dataset.threshold([100, 500], invert=True, progress_bar=True)
    assert thresh is not None
    assert isinstance(thresh, pyvista.UnstructuredGrid)
    # allow Sequence but not Iterable
    with pytest.raises(TypeError):
        dataset.threshold({100, 500}, progress_bar=True)

    # Now test DataSet without arrays
    with pytest.raises(ValueError):
        dataset = datasets[3]  # polydata with no arrays
        thresh = dataset.threshold()

    dataset = examples.load_uniform()
    with pytest.raises(ValueError):
        dataset.threshold([10, 100, 300], progress_bar=True)


def test_threshold_all_scalars():
    mesh = pyvista.Sphere()
    mesh.clear_data()

    mesh["scalar0"] = np.zeros(mesh.n_points)
    mesh["scalar1"] = np.ones(mesh.n_points)
    mesh.set_active_scalars("scalar1")
    thresh_all = mesh.threshold(value=0.5, all_scalars=True)  # only uses scalar1
    assert thresh_all.n_points == mesh.n_points
    assert thresh_all.n_cells == mesh.n_cells

    mesh["scalar1"][0 : int(mesh.n_points / 2)] = 0.0
    thresh = mesh.threshold(value=0.5, all_scalars=False)
    thresh_all = mesh.threshold(value=0.5, all_scalars=True)
    assert thresh_all.n_points < mesh.n_points
    # removes additional cells/points due to all_scalars
    assert thresh_all.n_points < thresh.n_points
    assert thresh_all.n_cells < mesh.n_cells
    assert thresh_all.n_cells < thresh.n_cells

    mesh.clear_data()
    mesh["scalar0"] = np.zeros(mesh.n_cells)
    mesh["scalar1"] = np.ones(mesh.n_cells)
    mesh["scalar1"][0 : int(mesh.n_cells / 2)] = 0.0
    mesh.set_active_scalars("scalar1")
    thresh = mesh.threshold(value=0.5, all_scalars=False)
    thresh_all = mesh.threshold(value=0.5, all_scalars=True)
    # when thresholding by cell data, all_scalars has no effect since it has 1 value per cell
    assert thresh_all.n_points < mesh.n_points
    assert thresh_all.n_points == thresh.n_points
    assert thresh_all.n_cells < mesh.n_cells
    assert thresh_all.n_cells == thresh.n_cells


def test_threshold_multicomponent():
    mesh = pyvista.Plane()
    data = np.zeros((mesh.n_cells, 3))
    data[0:3, 0] = 1
    data[2:4, 1] = 2
    data[2, 2] = 3
    mesh["data"] = data

    thresh = mesh.threshold(value=0.5, scalars="data", component_mode="component", component=0)
    assert thresh.n_cells == 3
    thresh = mesh.threshold(value=0.5, scalars="data", component_mode="component", component=1)
    assert thresh.n_cells == 2
    thresh = mesh.threshold(value=0.5, scalars="data", component_mode="all")
    assert thresh.n_cells == 1
    thresh = mesh.threshold(value=0.5, scalars="data", component_mode="any")
    assert thresh.n_cells == 4

    with pytest.raises(ValueError):
        mesh.threshold(value=0.5, scalars="data", component_mode="not a mode")

    with pytest.raises(ValueError):
        mesh.threshold(value=0.5, scalars="data", component_mode="component", component=-1)

    with pytest.raises(ValueError):
        mesh.threshold(value=0.5, scalars="data", component_mode="component", component=3)

    with pytest.raises(TypeError):
        mesh.threshold(value=0.5, scalars="data", component_mode="component", component=0.5)


def test_threshold_percent(datasets):
    percents = [25, 50, [18.0, 85.0], [19.0, 80.0], 0.70]
    inverts = [False, True, False, True, False]
    # Only test data sets that have arrays
    for i, dataset in enumerate(datasets[0:3]):
        thresh = dataset.threshold_percent(
            percent=percents[i], invert=inverts[i], progress_bar=True
        )
        assert thresh is not None
        assert isinstance(thresh, pyvista.UnstructuredGrid)
    dataset = examples.load_uniform()
    _ = dataset.threshold_percent(0.75, scalars='Spatial Cell Data', progress_bar=True)
    with pytest.raises(ValueError):
        dataset.threshold_percent(20000)
    with pytest.raises(ValueError):
        dataset.threshold_percent(0.0)
    # allow Sequence but not Iterable
    with pytest.raises(TypeError):
        dataset.threshold_percent({18.0, 85.0})


def test_outline(datasets):
    for dataset in datasets:
        outline = dataset.outline(progress_bar=True)
        assert outline is not None
        assert isinstance(outline, pyvista.PolyData)


@skip_py2_nobind
def test_outline_composite(composite):
    # Now test composite data structures
    output = composite.outline(progress_bar=True)
    assert isinstance(output, pyvista.PolyData)
    output = composite.outline(nested=True, progress_bar=True)

    # vtk 9.0.0 returns polydata
    assert isinstance(output, (pyvista.MultiBlock, pyvista.PolyData))
    if isinstance(output, pyvista.MultiBlock):
        assert output.n_blocks == composite.n_blocks


def test_outline_corners(datasets):
    for dataset in datasets:
        outline = dataset.outline_corners(progress_bar=True)
        assert outline is not None
        assert isinstance(outline, pyvista.PolyData)


@skip_py2_nobind
def test_outline_corners_composite(composite):
    # Now test composite data structures
    output = composite.outline_corners(progress_bar=True)
    assert isinstance(output, pyvista.PolyData)
    output = composite.outline_corners(nested=True)
    assert output.n_blocks == composite.n_blocks


def test_extract_geometry(datasets, composite):
    for dataset in datasets:
        geom = dataset.extract_geometry(progress_bar=True)
        assert geom is not None
        assert isinstance(geom, pyvista.PolyData)
    # Now test composite data structures
    output = composite.extract_geometry()
    assert isinstance(output, pyvista.PolyData)


def test_extract_geometry_extent(uniform):
    geom = uniform.extract_geometry(extent=(0, 5, 0, 100, 0, 100))
    assert isinstance(geom, pyvista.PolyData)
    assert geom.bounds == (0.0, 5.0, 0.0, 9.0, 0.0, 9.0)


def test_wireframe(datasets):
    for dataset in datasets:
        wire = dataset.extract_all_edges(progress_bar=True)
        assert wire is not None
        assert isinstance(wire, pyvista.PolyData)


@skip_py2_nobind
def test_wireframe_composite(composite):
    # Now test composite data structures
    output = composite.extract_all_edges(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_delaunay_2d_unstructured(datasets):
    mesh = examples.load_hexbeam().delaunay_2d(progress_bar=True)  # UnstructuredGrid
    assert isinstance(mesh, pyvista.PolyData)
    assert mesh.n_points
    assert len(mesh.point_data.keys()) > 0


@pytest.mark.parametrize('method', ['contour', 'marching_cubes', 'flying_edges'])
def test_contour(uniform, method):
    iso = uniform.contour(method=method, progress_bar=True)
    assert iso is not None
    iso = uniform.contour(isosurfaces=[100, 300, 500], method=method, progress_bar=True)
    assert iso is not None

    # ensure filter can work with non-string inputs
    iso_new_scalars = uniform.contour(
        isosurfaces=[100, 300, 500],
        scalars=range(uniform.n_points),
        method=method,
    )

    assert 'Contour Data' in iso_new_scalars.point_data


def test_contour_errors(uniform):
    with pytest.raises(TypeError):
        uniform.contour(scalars='Spatial Cell Data')
    with pytest.raises(TypeError):
        uniform.contour(isosurfaces=pyvista.PolyData())
    with pytest.raises(TypeError):
        uniform.contour(isosurfaces={100, 300, 500})
    uniform = examples.load_airplane()
    with pytest.raises(ValueError):
        uniform.contour()
    with pytest.raises(ValueError):
        uniform.contour(method='invalid method')
    with pytest.raises(TypeError, match='Invalid type for `scalars`'):
        uniform.contour(scalars=1)
    with pytest.raises(TypeError):
        uniform.contour(rng={})
    with pytest.raises(ValueError, match='rng must be a two-length'):
        uniform.contour(rng=[1])
    with pytest.raises(ValueError, match='rng must be a sorted'):
        uniform.contour(rng=[2, 1])


def test_elevation():
    dataset = examples.load_uniform()
    # Test default params
    elev = dataset.elevation(progress_bar=True)
    assert 'Elevation' in elev.array_names
    assert 'Elevation' == elev.active_scalars_name
    assert elev.get_data_range() == (dataset.bounds[4], dataset.bounds[5])
    # test vector args
    c = list(dataset.center)
    t = list(c)  # cast so it does not point to `c`
    t[2] = dataset.bounds[-1]
    elev = dataset.elevation(low_point=c, high_point=t, progress_bar=True)
    assert 'Elevation' in elev.array_names
    assert 'Elevation' == elev.active_scalars_name
    assert elev.get_data_range() == (dataset.center[2], dataset.bounds[5])
    # Test not setting active
    elev = dataset.elevation(set_active=False, progress_bar=True)
    assert 'Elevation' in elev.array_names
    assert 'Elevation' != elev.active_scalars_name
    # Set use a range by scalar name
    elev = dataset.elevation(scalar_range='Spatial Point Data', progress_bar=True)
    assert 'Elevation' in elev.array_names
    assert 'Elevation' == elev.active_scalars_name
    assert dataset.get_data_range('Spatial Point Data') == (elev.get_data_range('Elevation'))
    # Set use a user defined range
    elev = dataset.elevation(scalar_range=[1.0, 100.0], progress_bar=True)
    assert 'Elevation' in elev.array_names
    assert 'Elevation' == elev.active_scalars_name
    assert elev.get_data_range('Elevation') == (1.0, 100.0)
    # test errors
    with pytest.raises(TypeError):
        elev = dataset.elevation(scalar_range=0.5, progress_bar=True)
    with pytest.raises(ValueError):
        elev = dataset.elevation(scalar_range=[1, 2, 3], progress_bar=True)
    with pytest.raises(TypeError):
        elev = dataset.elevation(scalar_range={1, 2}, progress_bar=True)


@skip_py2_nobind
def test_elevation_composite(composite):
    # Now test composite data structures
    output = composite.elevation(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_texture_map_to_plane():
    dataset = examples.load_airplane()
    # Automatically decide plane
    out = dataset.texture_map_to_plane(inplace=False, progress_bar=True)
    assert isinstance(out, type(dataset))
    # Define the plane explicitly
    bnds = dataset.bounds
    origin = bnds[0::2]
    point_u = (bnds[1], bnds[2], bnds[4])
    point_v = (bnds[0], bnds[3], bnds[4])
    out = dataset.texture_map_to_plane(
        origin=origin, point_u=point_u, point_v=point_v, progress_bar=True
    )
    assert isinstance(out, type(dataset))
    assert 'Texture Coordinates' in out.array_names
    # FINAL: Test in place modifiacation
    dataset.texture_map_to_plane(inplace=True)
    assert 'Texture Coordinates' in dataset.array_names


def test_texture_map_to_sphere():
    dataset = pyvista.Sphere(radius=1.0)
    # Automatically decide plane
    out = dataset.texture_map_to_sphere(inplace=False, prevent_seam=False, progress_bar=True)
    assert isinstance(out, type(dataset))
    # Define the center explicitly
    out = dataset.texture_map_to_sphere(
        center=(0.1, 0.0, 0.0), prevent_seam=True, progress_bar=True
    )
    assert isinstance(out, type(dataset))
    assert 'Texture Coordinates' in out.array_names
    # FINAL: Test in place modifiacation
    dataset.texture_map_to_sphere(inplace=True, progress_bar=True)
    assert 'Texture Coordinates' in dataset.array_names


def test_compute_cell_sizes(datasets):
    for dataset in datasets:
        result = dataset.compute_cell_sizes(progress_bar=True)
        assert result is not None
        assert isinstance(result, type(dataset))
        assert 'Area' in result.array_names
        assert 'Volume' in result.array_names
    # Test the volume property
    grid = pyvista.UniformGrid(dims=(10, 10, 10))
    volume = float(np.prod(np.array(grid.dimensions) - 1))
    assert np.allclose(grid.volume, volume)


@skip_py2_nobind
def test_compute_cell_sizes_composite(composite):
    # Now test composite data structures
    output = composite.compute_cell_sizes(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_cell_centers(datasets):
    for dataset in datasets:
        result = dataset.cell_centers(progress_bar=True)
        assert result is not None
        assert isinstance(result, pyvista.PolyData)


@skip_py2_nobind
def test_cell_centers_composite(composite):
    # Now test composite data structures
    output = composite.cell_centers(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_glyph(datasets, sphere):
    for dataset in datasets:
        dataset["vectors"] = np.ones_like(dataset.points)
        result = dataset.glyph(progress_bar=True)
        assert result is not None
        assert isinstance(result, pyvista.PolyData)
    # Test different options for glyph filter
    sphere_sans_arrays = sphere.copy()
    sphere.compute_normals(inplace=True)
    sphere["vectors"] = np.ones([sphere.n_points, 3])
    sphere.set_active_vectors("vectors")
    sphere.point_data['arr'] = np.ones(sphere.n_points)

    assert sphere.glyph(scale=False, progress_bar=True)
    assert sphere.glyph(scale='arr', progress_bar=True)
    assert sphere.glyph(scale='arr', orient='Normals', factor=0.1, progress_bar=True)
    assert sphere.glyph(scale='arr', orient='Normals', factor=0.1, tolerance=0.1, progress_bar=True)
    assert sphere.glyph(
        scale='arr',
        orient='Normals',
        factor=0.1,
        tolerance=0.1,
        clamping=False,
        rng=[1, 1],
        progress_bar=True,
    )
    # passing one or more custom glyphs; many cases for full coverage
    geoms = [
        pyvista.Sphere(theta_resolution=5, phi_resolution=5),
        pyvista.Arrow(tip_resolution=5, shaft_resolution=5),
        pyvista.ParametricSuperToroid(u_res=10, v_res=10, w_res=10),
    ]
    indices = range(len(geoms))
    assert sphere.glyph(geom=geoms[0], progress_bar=True)
    assert sphere.glyph(geom=geoms, indices=indices, rng=(0, len(geoms)), progress_bar=True)
    assert sphere.glyph(geom=geoms, progress_bar=True)
    assert sphere.glyph(
        geom=geoms, scale='arr', orient='Normals', factor=0.1, tolerance=0.1, progress_bar=True
    )
    assert sphere.glyph(geom=geoms[:1], indices=[None], progress_bar=True)

    with pytest.warns(Warning):  # tries to orient but no orientation vector available
        assert sphere_sans_arrays.glyph(geom=geoms, progress_bar=True)

    sphere_sans_arrays["vec1"] = np.ones((sphere_sans_arrays.n_points, 3))
    sphere_sans_arrays["vec2"] = np.ones((sphere_sans_arrays.n_points, 3))
    with pytest.warns(Warning):  # tries to orient but multiple orientation vectors are possible
        assert sphere_sans_arrays.glyph(geom=geoms, progress_bar=True)

    with pytest.raises(TypeError):
        # wrong type for the glyph
        sphere.glyph(geom=pyvista.StructuredGrid())
    with pytest.raises(TypeError):
        # wrong type for the indices
        sphere.glyph(geom=geoms, indices=set(indices))
    with pytest.raises(ValueError):
        # wrong length for the indices
        sphere.glyph(geom=geoms, indices=indices[:-1])


def test_glyph_cell_point_data(sphere):
    sphere['vectors_cell'] = np.ones([sphere.n_cells, 3])
    sphere['vectors_points'] = np.ones([sphere.n_points, 3])
    sphere['arr_cell'] = np.ones(sphere.n_cells)
    sphere['arr_points'] = np.ones(sphere.n_points)

    assert sphere.glyph(orient='vectors_cell', scale='arr_cell', progress_bar=True)
    assert sphere.glyph(orient='vectors_points', scale='arr_points', progress_bar=True)
    with pytest.raises(ValueError):
        sphere.glyph(orient='vectors_cell', scale='arr_points', progress_bar=True)
    with pytest.raises(ValueError):
        sphere.glyph(orient='vectors_points', scale='arr_cell', progress_bar=True)


class InterrogateVTKGlyph3D:
    def __init__(self, alg: pyvista._vtk.vtkGlyph3D):
        self.alg = alg

    @property
    def input_data_object(self):
        return pyvista.wrap(self.alg.GetInputDataObject(0, 0))

    @property
    def input_active_scalars_info(self):
        return self.input_data_object.active_scalars_info

    @property
    def input_active_vectors_info(self):
        return self.input_data_object.active_vectors_info

    @property
    def scaling(self):
        return self.alg.GetScaling()

    @property
    def scale_mode(self):
        return self.alg.GetScaleModeAsString()

    @property
    def scale_factor(self):
        return self.alg.GetScaleFactor()

    @property
    def clamping(self):
        return self.alg.GetClamping()

    @property
    def vector_mode(self):
        return self.alg.GetVectorModeAsString()


def test_glyph_settings(sphere):
    sphere['vectors_cell'] = np.ones([sphere.n_cells, 3])
    sphere['vectors_points'] = np.ones([sphere.n_points, 3])
    sphere['arr_cell'] = np.ones(sphere.n_cells)
    sphere['arr_points'] = np.ones(sphere.n_points)

    sphere['arr_both'] = np.ones(sphere.n_points)
    sphere['arr_both'] = np.ones(sphere.n_cells)
    sphere['vectors_both'] = np.ones([sphere.n_points, 3])
    sphere['vectors_both'] = np.ones([sphere.n_cells, 3])

    sphere['active_arr_points'] = np.ones(sphere.n_points)
    sphere['active_vectors_points'] = np.ones([sphere.n_points, 3])

    with patch('pyvista.core.filters.data_set._get_output', GetOutput()) as go:
        # no orient with cell scale
        sphere.glyph(scale='arr_cell', orient=False)
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_cell'
        assert alg.scale_mode == 'ScaleByScalar'
        go.reset()

        # cell orient with no scale
        sphere.glyph(scale=False, orient='vectors_cell')
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_vectors_info.name == 'vectors_cell'
        assert alg.scale_mode == 'DataScalingOff'
        go.reset()

        # cell orient with cell scale
        sphere.glyph(scale='arr_cell', orient='vectors_cell')
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_cell'
        assert alg.input_active_vectors_info.name == 'vectors_cell'
        assert alg.scale_mode == 'ScaleByScalar'
        go.reset()

        # cell orient with cell scale and tolerance
        sphere.glyph(scale='arr_cell', orient='vectors_cell', tolerance=0.05)
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_cell'
        assert alg.input_active_vectors_info.name == 'vectors_cell'
        assert alg.scale_mode == 'ScaleByScalar'
        go.reset()

        # no orient with point scale
        sphere.glyph(scale='arr_points', orient=False)
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_points'
        assert alg.scale_mode == 'ScaleByScalar'
        go.reset()

        # point orient with no scale
        sphere.glyph(scale=False, orient='vectors_points')
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_vectors_info.name == 'vectors_points'
        assert alg.scale_mode == 'DataScalingOff'
        go.reset()

        # point orient with point scale
        sphere.glyph(scale='arr_points', orient='vectors_points')
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_points'
        assert alg.input_active_vectors_info.name == 'vectors_points'
        assert alg.scale_mode == 'ScaleByScalar'
        go.reset()

        # point orient with point scale and tolerance
        sphere.glyph(scale='arr_points', orient='vectors_points', tolerance=0.05)
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_points'
        assert alg.input_active_vectors_info.name == 'vectors_points'
        assert alg.scale_mode == 'ScaleByScalar'
        go.reset()

        # point orient with point scale + factor
        sphere.glyph(scale='arr_points', orient='vectors_points', factor=5)
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_points'
        assert alg.input_active_vectors_info.name == 'vectors_points'
        assert alg.scale_factor == 5

        # ambiguous point/cell prefers points
        sphere.glyph(scale='arr_both', orient='vectors_both')
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'arr_both'
        assert alg.input_active_vectors_info.name == 'vectors_both'
        ## Test the length of the field and not the FieldAssociation, because the vtkGlyph3D filter takes POINT data
        assert len(alg.input_data_object.active_scalars) == sphere.n_cells
        assert len(alg.input_data_object.active_scalars) == sphere.n_cells

        # no fields selected uses active
        sphere.set_active_scalars('active_arr_points')
        sphere.set_active_vectors('active_vectors_points')
        sphere.glyph(scale=True, orient=True)
        alg = InterrogateVTKGlyph3D(go.latest_algorithm)
        assert alg.input_active_scalars_info.name == 'active_arr_points'
        assert alg.input_active_vectors_info.name == 'active_vectors_points'


def test_glyph_orient_and_scale():
    grid = pyvista.UniformGrid(dims=(1, 1, 1))
    geom = pyvista.Line()
    scale = 10.0
    orient = np.array([[0.0, 0.0, 1.0]])
    grid["z_axis"] = orient * scale
    glyph1 = grid.glyph(geom=geom, orient="z_axis", scale="z_axis")
    glyph2 = grid.glyph(geom=geom, orient=False, scale="z_axis")
    glyph3 = grid.glyph(geom=geom, orient="z_axis", scale=False)
    glyph4 = grid.glyph(geom=geom, orient=False, scale=False)
    assert glyph1.bounds[4] == geom.bounds[0] * scale and glyph1.bounds[5] == geom.bounds[1] * scale
    assert glyph2.bounds[0] == geom.bounds[0] * scale and glyph2.bounds[1] == geom.bounds[1] * scale
    assert glyph3.bounds[4] == geom.bounds[0] and glyph3.bounds[5] == geom.bounds[1]
    assert glyph4.bounds[0] == geom.bounds[0] and glyph4.bounds[1] == geom.bounds[1]


def test_split_and_connectivity():
    # Load a simple example mesh
    dataset = examples.load_uniform()
    dataset.set_active_scalars('Spatial Cell Data')
    threshed = dataset.threshold_percent([0.15, 0.50], invert=True, progress_bar=True)

    bodies = threshed.split_bodies(progress_bar=True)

    volumes = [518.0, 35.0]
    assert len(volumes) == bodies.n_blocks
    for i, body in enumerate(bodies):
        assert np.allclose(body.volume, volumes[i], rtol=0.1)


def test_warp_by_scalar():
    data = examples.load_uniform()
    warped = data.warp_by_scalar(progress_bar=True)
    assert data.n_points == warped.n_points
    warped = data.warp_by_scalar(scale_factor=3, progress_bar=True)
    assert data.n_points == warped.n_points
    warped = data.warp_by_scalar(normal=[1, 1, 3], progress_bar=True)
    assert data.n_points == warped.n_points
    # Test in place!
    foo = examples.load_hexbeam()
    foo.point_data.active_scalars_name = 'sample_point_scalars'
    warped = foo.warp_by_scalar(progress_bar=True)
    foo.warp_by_scalar(inplace=True, progress_bar=True)
    assert np.allclose(foo.points, warped.points)


def test_warp_by_vector():
    # Test when inplace=False (default)
    data = examples.load_sphere_vectors()
    warped = data.warp_by_vector(progress_bar=True)
    assert data.n_points == warped.n_points
    assert not np.allclose(data.points, warped.points)
    warped = data.warp_by_vector(factor=3, progress_bar=True)
    assert data.n_points == warped.n_points
    assert not np.allclose(data.points, warped.points)
    # Test when inplace=True
    foo = examples.load_sphere_vectors()
    warped = foo.warp_by_vector(progress_bar=True)
    foo.warp_by_vector(inplace=True, progress_bar=True)
    assert np.allclose(foo.points, warped.points)


def test_invalid_warp_scalar(sphere):
    sphere['cellscalars'] = np.random.random(sphere.n_cells)
    sphere.point_data.clear()
    with pytest.raises(TypeError):
        sphere.warp_by_scalar()


def test_invalid_warp_scalar_inplace(uniform):
    with pytest.raises(TypeError):
        uniform.warp_by_scalar(inplace=True, progress_bar=True)


def test_invalid_warp_vector(sphere):
    # bad vectors
    sphere.point_data['Normals'] = np.empty((sphere.n_points, 2))
    with pytest.raises(ValueError):
        sphere.warp_by_vector('Normals')

    # no vectors
    sphere.point_data.clear()
    with pytest.raises(ValueError):
        sphere.warp_by_vector()


def test_cell_data_to_point_data():
    data = examples.load_uniform()
    foo = data.cell_data_to_point_data(progress_bar=True)
    assert foo.n_arrays == 2
    assert len(foo.cell_data.keys()) == 0
    _ = data.ctp()


@skip_py2_nobind
def test_cell_data_to_point_data_composite(composite):
    # Now test composite data structures
    output = composite.cell_data_to_point_data(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_point_data_to_cell_data():
    data = examples.load_uniform()
    foo = data.point_data_to_cell_data(progress_bar=True)
    assert foo.n_arrays == 2
    assert len(foo.point_data.keys()) == 0
    _ = data.ptc()


@skip_py2_nobind
def test_point_data_to_cell_data_composite(composite):
    # Now test composite data structures
    output = composite.point_data_to_cell_data(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_triangulate():
    data = examples.load_uniform()
    tri = data.triangulate(progress_bar=True)
    assert isinstance(tri, pyvista.UnstructuredGrid)
    assert np.any(tri.cells)


@skip_py2_nobind
def test_triangulate_composite(composite):
    # Now test composite data structures
    output = composite.triangulate(progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_delaunay_3d():
    data = examples.load_uniform().threshold_percent(30, progress_bar=True)
    result = data.delaunay_3d()
    assert np.any(result.points)


def test_smooth(uniform):
    surf = uniform.extract_surface().clean()
    smoothed = surf.smooth()

    # expect mesh is smoothed, raising mean curvature since it is more "spherelike"
    assert smoothed.triangulate().curvature().mean() > surf.triangulate().curvature().mean()

    smooth_inplace = surf.smooth(inplace=True)
    assert np.allclose(surf.points, smoothed.points)
    assert np.allclose(smooth_inplace.points, smoothed.points)


def test_smooth_taubin(uniform):
    surf = uniform.extract_surface().clean()
    smoothed = surf.smooth_taubin()

    # expect mesh is smoothed, raising mean curvature since it is more "spherelike"
    assert smoothed.triangulate().curvature().mean() > surf.triangulate().curvature().mean()

    # while volume is maintained
    assert np.isclose(smoothed.volume, surf.volume, rtol=0.01)

    smooth_inplace = surf.smooth_taubin(inplace=True)
    assert np.allclose(surf.points, smoothed.points)
    assert np.allclose(smooth_inplace.points, smoothed.points)


def test_resample():
    mesh = pyvista.Sphere(center=(4.5, 4.5, 4.5), radius=4.5)
    data_to_probe = examples.load_uniform()
    result = mesh.sample(data_to_probe, progress_bar=True)
    name = 'Spatial Point Data'
    assert name in result.array_names
    assert isinstance(result, type(mesh))
    result = mesh.sample(data_to_probe, tolerance=1.0, progress_bar=True)
    name = 'Spatial Point Data'
    assert name in result.array_names
    assert isinstance(result, type(mesh))


locators = [None]
if pyvista.vtk_version_info >= (9,):
    locators.append(vtkStaticCellLocator())


@pytest.mark.parametrize('use_points', [True, False])
@pytest.mark.parametrize('categorical', [True, False])
@pytest.mark.parametrize('locator', locators)
def test_probe(categorical, use_points, locator):
    mesh = pyvista.Sphere(center=(4.5, 4.5, 4.5), radius=4.5)
    data_to_probe = examples.load_uniform()
    if use_points:
        dataset = np.array(mesh.points)
    else:
        dataset = mesh
    result = data_to_probe.probe(
        dataset, tolerance=1e-5, categorical=categorical, progress_bar=True, locator=locator
    )
    name = 'Spatial Point Data'
    assert name in result.array_names
    assert isinstance(result, type(mesh))
    result = mesh.sample(data_to_probe, tolerance=1.0, progress_bar=True)
    name = 'Spatial Point Data'
    assert name in result.array_names
    assert isinstance(result, type(mesh))


@pytest.mark.parametrize('integration_direction', ['forward', 'backward', 'both'])
def test_streamlines_dir(uniform_vec, integration_direction):
    stream = uniform_vec.streamlines(
        'vectors', integration_direction=integration_direction, progress_bar=True
    )
    assert all([stream.n_points, stream.n_cells])


@pytest.mark.parametrize('integrator_type', [2, 4, 45])
def test_streamlines_type(uniform_vec, integrator_type):
    stream = uniform_vec.streamlines('vectors', integrator_type=integrator_type, progress_bar=True)
    assert all([stream.n_points, stream.n_cells])


@pytest.mark.parametrize('interpolator_type', ['point', 'cell'])
def test_streamlines_cell_point(uniform_vec, interpolator_type):
    stream = uniform_vec.streamlines(
        'vectors', interpolator_type=interpolator_type, progress_bar=True
    )
    assert all([stream.n_points, stream.n_cells])


def test_streamlines_return_source(uniform_vec):
    stream, src = uniform_vec.streamlines(
        'vectors',
        return_source=True,
        pointa=(0.0, 0.0, 0.0),
        pointb=(1.1, 1.1, 0.1),
        progress_bar=True,
    )
    assert isinstance(src, pyvista.DataSet)
    assert all([stream.n_points, stream.n_cells, src.n_points])


def test_streamlines_start_position(uniform_vec):
    stream = uniform_vec.streamlines('vectors', start_position=(0.5, 0.0, 0.0), progress_bar=True)

    assert all([stream.n_points, stream.n_cells])


def test_streamlines_errors(uniform_vec):
    with pytest.raises(ValueError):
        uniform_vec.streamlines('vectors', integration_direction='not valid')

    with pytest.raises(ValueError):
        uniform_vec.streamlines('vectors', integrator_type=42)

    with pytest.raises(ValueError):
        uniform_vec.streamlines('vectors', interpolator_type='not valid')

    with pytest.raises(ValueError):
        uniform_vec.streamlines('vectors', step_unit='not valid')

    with pytest.raises(ValueError):
        uniform_vec.streamlines('vectors', pointa=(0, 0, 0))
    with pytest.raises(ValueError):
        uniform_vec.streamlines('vectors', pointb=(0, 0, 0))


def test_streamlines_from_source(uniform_vec):
    vertices = np.array([[0, 0, 0], [0.5, 0, 0], [0.5, 0.5, 0], [0, 0.5, 0]])
    source = pyvista.PolyData(vertices)
    stream = uniform_vec.streamlines_from_source(source, 'vectors', progress_bar=True)
    assert all([stream.n_points, stream.n_cells])

    source = pyvista.UniformGrid(dims=[5, 5, 5], spacing=[0.1, 0.1, 0.1], origin=[0, 0, 0])
    stream = uniform_vec.streamlines_from_source(source, 'vectors', progress_bar=True)
    assert all([stream.n_points, stream.n_cells])


def test_streamlines_from_source_structured_grids():
    x, y, z = np.meshgrid(np.arange(-10, 10, 0.5), np.arange(-10, 10, 0.5), np.arange(-10, 10, 0.5))
    mesh = pyvista.StructuredGrid(x, y, z)
    x2, y2, z2 = np.meshgrid(np.arange(-1, 1, 0.5), np.arange(-1, 1, 0.5), np.arange(-1, 1, 0.5))
    mesh2 = pyvista.StructuredGrid(x2, y2, z2)
    mesh["vectors"] = np.ones([mesh.n_points, 3])
    mesh.set_active_vectors("vectors")

    with pyvista.VtkErrorCatcher(raise_errors=True):
        stream = mesh.streamlines_from_source(mesh2)
    assert all([stream.n_points, stream.n_cells])


@pytest.mark.needs_vtk9
def mesh_2D_velocity():
    mesh = pyvista.Plane(i_resolution=100, j_resolution=100)
    velocity = np.zeros([mesh.n_points, 3])
    velocity[:, 0] = 1
    mesh["velocity"] = velocity
    mesh.set_active_vectors("velocity")
    return mesh


@pytest.mark.needs_vtk9
def test_streamlines_evenly_spaced_2D():
    mesh = mesh_2D_velocity()
    streams = mesh.streamlines_evenly_spaced_2D(progress_bar=True)
    assert all([streams.n_points, streams.n_cells])


@pytest.mark.needs_vtk9
def test_streamlines_evenly_spaced_2D_sep_dist_ratio():
    mesh = mesh_2D_velocity()
    streams = mesh.streamlines_evenly_spaced_2D(separating_distance_ratio=0.1, progress_bar=True)
    assert all([streams.n_points, streams.n_cells])


@pytest.mark.needs_vtk9
def test_streamlines_evenly_spaced_2D_start_position():
    mesh = mesh_2D_velocity()
    streams = mesh.streamlines_evenly_spaced_2D(start_position=(-0.1, 0.1, 0.0), progress_bar=True)
    assert all([streams.n_points, streams.n_cells])


@pytest.mark.needs_vtk9
def test_streamlines_evenly_spaced_2D_vectors():
    mesh = mesh_2D_velocity()
    mesh.set_active_vectors(None)
    streams = mesh.streamlines_evenly_spaced_2D(vectors="velocity", progress_bar=True)
    assert all([streams.n_points, streams.n_cells])


@pytest.mark.needs_vtk9
def test_streamlines_evenly_spaced_2D_integrator_type():
    mesh = mesh_2D_velocity()
    streams = mesh.streamlines_evenly_spaced_2D(integrator_type=4, progress_bar=True)
    assert all([streams.n_points, streams.n_cells])


@pytest.mark.needs_vtk9
def test_streamlines_evenly_spaced_2D_interpolator_type():
    mesh = mesh_2D_velocity()
    streams = mesh.streamlines_evenly_spaced_2D(interpolator_type='cell', progress_bar=True)
    assert all([streams.n_points, streams.n_cells])


def test_streamlines_evenly_spaced_2D_errors():
    mesh = mesh_2D_velocity()

    with pytest.raises(ValueError):
        mesh.streamlines_evenly_spaced_2D(integrator_type=45)

    with pytest.raises(ValueError):
        mesh.streamlines_evenly_spaced_2D(interpolator_type="not valid")

    with pytest.raises(ValueError):
        mesh.streamlines_evenly_spaced_2D(step_unit="not valid")


@pytest.mark.xfail
def test_streamlines_nonxy_plane():
    # streamlines_evenly_spaced_2D only works for xy plane datasets
    # test here so that fixes in vtk can be caught
    mesh = mesh_2D_velocity()
    mesh.translate((0, 0, 1), inplace=True)  # move to z=1, xy plane
    streams = mesh.streamlines_evenly_spaced_2D(progress_bar=True)
    assert all([streams.n_points, streams.n_cells])


def test_sample_over_line():
    """Test that we get a sampled line."""
    name = 'values'

    line = pyvista.Line([0, 0, 0], [0, 0, 10], 9)
    line[name] = np.linspace(0, 10, 10)

    sampled_line = line.sample_over_line([0, 0, 0.5], [0, 0, 1.5], 2, progress_bar=True)

    expected_result = np.array([0.5, 1, 1.5])
    assert np.allclose(sampled_line[name], expected_result)
    assert name in sampled_line.array_names  # is name in sampled result

    # test no resolution
    sphere = pyvista.Sphere(center=(4.5, 4.5, 4.5), radius=4.5)
    sampled_from_sphere = sphere.sample_over_line([3, 1, 1], [-3, -1, -1], progress_bar=True)
    assert sampled_from_sphere.n_points == sphere.n_cells + 1
    # is sampled result a polydata object
    assert isinstance(sampled_from_sphere, pyvista.PolyData)


@skip_no_mpl_figure
def test_plot_over_line(tmpdir):
    """This test requires matplotlib."""
    tmp_dir = tmpdir.mkdir("tmpdir")
    filename = str(tmp_dir.join('tmp.png'))
    mesh = examples.load_uniform()
    # Make two points to construct the line between
    a = [mesh.bounds[0], mesh.bounds[2], mesh.bounds[4]]
    b = [mesh.bounds[1], mesh.bounds[3], mesh.bounds[5]]
    mesh.plot_over_line(a, b, resolution=1000, show=False, progress_bar=True)
    # Test multicomponent
    mesh['foo'] = np.random.rand(mesh.n_cells, 3)
    mesh.plot_over_line(
        a,
        b,
        resolution=None,
        scalars='foo',
        title='My Stuff',
        ylabel='3 Values',
        show=False,
        fname=filename,
        progress_bar=True,
    )
    assert os.path.isfile(filename)
    # Should fail if scalar name does not exist
    with pytest.raises(KeyError):
        mesh.plot_over_line(
            a,
            b,
            resolution=None,
            scalars='invalid_array_name',
            title='My Stuff',
            ylabel='3 Values',
            show=False,
        )


def test_sample_over_multiple_lines():
    """Test that"""
    name = 'values'

    line = pyvista.Line([0, 0, 0], [0, 0, 10], 9)
    line[name] = np.linspace(0, 10, 10)

    sampled_multiple_lines = line.sample_over_multiple_lines(
        [[0, 0, 0.5], [0, 0, 1], [0, 0, 1.5]], progress_bar=True
    )

    expected_result = np.array([0.5, 1, 1.5])
    assert np.allclose(sampled_multiple_lines[name], expected_result)
    assert name in sampled_multiple_lines.array_names  # is name in sampled result


def test_sample_over_circular_arc():
    """Test that we get a circular arc."""

    name = 'values'

    uniform = examples.load_uniform()
    uniform[name] = uniform.points[:, 2]

    xmin = uniform.bounds[0]
    xmax = uniform.bounds[1]
    ymin = uniform.bounds[2]
    zmin = uniform.bounds[4]
    zmax = uniform.bounds[5]
    pointa = [xmin, ymin, zmax]
    pointb = [xmax, ymin, zmin]
    center = [xmin, ymin, zmin]
    sampled_arc = uniform.sample_over_circular_arc(pointa, pointb, center, 2, progress_bar=True)

    expected_result = zmin + (zmax - zmin) * np.sin([np.pi / 2.0, np.pi / 4.0, 0.0])
    assert np.allclose(sampled_arc[name], expected_result)
    assert name in sampled_arc.array_names  # is name in sampled result

    # test no resolution
    sphere = pyvista.Sphere(center=(4.5, 4.5, 4.5), radius=4.5)
    sampled_from_sphere = sphere.sample_over_circular_arc(
        [3, 1, 1], [-3, -1, -1], [0, 0, 0], progress_bar=True
    )
    assert sampled_from_sphere.n_points == sphere.n_cells + 1

    # is sampled result a polydata object
    assert isinstance(sampled_from_sphere, pyvista.PolyData)


def test_sample_over_circular_arc_normal():
    """Test that we get a circular arc_normal."""

    name = 'values'

    uniform = examples.load_uniform()
    uniform[name] = uniform.points[:, 2]

    xmin = uniform.bounds[0]
    ymin = uniform.bounds[2]
    ymax = uniform.bounds[3]
    zmin = uniform.bounds[4]
    zmax = uniform.bounds[5]
    normal = [xmin, ymax, zmin]
    polar = [xmin, ymin, zmax]
    angle = 90.0 * np.random.rand()
    resolution = np.random.randint(10000)
    center = [xmin, ymin, zmin]
    sampled_arc_normal = uniform.sample_over_circular_arc_normal(
        center, resolution=resolution, normal=normal, polar=polar, angle=angle, progress_bar=True
    )
    angles = np.linspace(np.pi / 2.0, np.pi / 2.0 - np.deg2rad(angle), resolution + 1)

    expected_result = zmin + (zmax - zmin) * np.sin(angles)
    assert np.allclose(sampled_arc_normal[name], expected_result)
    assert name in sampled_arc_normal.array_names  # is name in sampled result

    # test no resolution
    sphere = pyvista.Sphere(center=(4.5, 4.5, 4.5), radius=4.5)
    sampled_from_sphere = sphere.sample_over_circular_arc_normal(
        [0, 0, 0], polar=[3, 1, 1], angle=180, progress_bar=True
    )
    assert sampled_from_sphere.n_points == sphere.n_cells + 1

    # is sampled result a polydata object
    assert isinstance(sampled_from_sphere, pyvista.PolyData)


@skip_no_mpl_figure
def test_plot_over_circular_arc(tmpdir):
    """This test requires matplotlib."""
    mesh = examples.load_uniform()
    tmp_dir = tmpdir.mkdir("tmpdir")
    filename = str(tmp_dir.join('tmp.png'))

    # Make two points and center to construct the circular arc between
    a = [mesh.bounds[0], mesh.bounds[2], mesh.bounds[5]]
    b = [mesh.bounds[1], mesh.bounds[2], mesh.bounds[4]]
    center = [mesh.bounds[0], mesh.bounds[2], mesh.bounds[4]]
    mesh.plot_over_circular_arc(
        a, b, center, resolution=1000, show=False, fname=filename, progress_bar=True
    )
    assert os.path.isfile(filename)

    # Test multicomponent
    mesh['foo'] = np.random.rand(mesh.n_cells, 3)
    mesh.plot_over_circular_arc(
        a,
        b,
        center,
        resolution=None,
        scalars='foo',
        title='My Stuff',
        ylabel='3 Values',
        show=False,
        progress_bar=True,
    )

    # Should fail if scalar name does not exist
    with pytest.raises(KeyError):
        mesh.plot_over_circular_arc(
            a,
            b,
            center,
            resolution=None,
            scalars='invalid_array_name',
            title='My Stuff',
            ylabel='3 Values',
            show=False,
        )


@skip_no_mpl_figure
def test_plot_over_circular_arc_normal(tmpdir):
    """This test requires matplotlib."""
    mesh = examples.load_uniform()
    tmp_dir = tmpdir.mkdir("tmpdir")
    filename = str(tmp_dir.join('tmp.png'))

    # Make center and normal/polar vector to construct the circular arc between
    # normal = [mesh.bounds[0], mesh.bounds[2], mesh.bounds[5]]
    polar = [mesh.bounds[0], mesh.bounds[3], mesh.bounds[4]]
    angle = 90
    center = [mesh.bounds[0], mesh.bounds[2], mesh.bounds[4]]
    mesh.plot_over_circular_arc_normal(
        center, polar=polar, angle=angle, show=False, fname=filename, progress_bar=True
    )
    assert os.path.isfile(filename)

    # Test multicomponent
    mesh['foo'] = np.random.rand(mesh.n_cells, 3)
    mesh.plot_over_circular_arc_normal(
        center,
        polar=polar,
        angle=angle,
        resolution=None,
        scalars='foo',
        title='My Stuff',
        ylabel='3 Values',
        show=False,
        progress_bar=True,
    )

    # Should fail if scalar name does not exist
    with pytest.raises(KeyError):
        mesh.plot_over_circular_arc_normal(
            center,
            polar=polar,
            angle=angle,
            resolution=None,
            scalars='invalid_array_name',
            title='My Stuff',
            ylabel='3 Values',
            show=False,
        )


def test_slice_along_line():
    model = examples.load_uniform()
    n = 5
    x = y = z = np.linspace(model.bounds[0], model.bounds[1], num=n)
    points = np.c_[x, y, z]
    spline = pyvista.Spline(points, n)
    slc = model.slice_along_line(spline, progress_bar=True)
    assert slc.n_points > 0
    slc = model.slice_along_line(spline, contour=True, progress_bar=True)
    assert slc.n_points > 0
    # Now check a simple line
    a = [model.bounds[0], model.bounds[2], model.bounds[4]]
    b = [model.bounds[1], model.bounds[3], model.bounds[5]]
    line = pyvista.Line(a, b, resolution=10)
    slc = model.slice_along_line(line, progress_bar=True)
    assert slc.n_points > 0
    # Now check a bad input
    a = [model.bounds[0], model.bounds[2], model.bounds[4]]
    b = [model.bounds[1], model.bounds[2], model.bounds[5]]
    line2 = pyvista.Line(a, b, resolution=10)
    line = line2.cast_to_unstructured_grid().merge(line.cast_to_unstructured_grid())
    with pytest.raises(ValueError):
        slc = model.slice_along_line(line, progress_bar=True)

    with pytest.raises(TypeError):
        one_cell = model.extract_cells(0, progress_bar=True)
        model.slice_along_line(one_cell, progress_bar=True)


def extract_points_invalid(sphere):
    with pytest.raises(ValueError):
        sphere.extract_points('invalid')

    with pytest.raises(TypeError):
        sphere.extract_points(object)


def test_extract_points():
    # mesh points (4x4 regular grid)
    vertices = np.array(
        [
            [0, 0, 0],
            [1, 0, 0],
            [2, 0, 0],
            [3, 0, 0],
            [0, 1, 0],
            [1, 1, 0],
            [2, 1, 0],
            [3, 1, 0],
            [0, 2, 0],
            [1, 2, 0],
            [2, 2, 0],
            [3, 2, 0],
            [0, 3, 0],
            [1, 3, 0],
            [2, 3, 0],
            [3, 3, 0],
        ],
        dtype=np.float32,
    )
    # corresponding mesh faces
    faces = np.hstack(
        [
            [4, 0, 1, 5, 4],  # square
            [4, 1, 2, 6, 5],  # square
            [4, 2, 3, 7, 6],  # square
            [4, 4, 5, 9, 8],  # square
            [4, 5, 6, 10, 9],  # square
            [4, 6, 7, 11, 10],  # square
            [4, 8, 9, 13, 12],  # square
            [4, 9, 10, 14, 13],  # square
            [4, 10, 11, 15, 14],  # square
        ]
    )
    # create pyvista object
    surf = pyvista.PolyData(vertices, faces)
    # extract sub-surface with adjacent cells
    sub_surf_adj = surf.extract_points(np.array([0, 1, 4, 5]), progress_bar=True)
    # extract sub-surface without adjacent cells
    sub_surf = surf.extract_points(np.array([0, 1, 4, 5]), adjacent_cells=False, progress_bar=True)
    # extract sub-surface without cells
    sub_surf_nocells = surf.extract_points(
        np.array([0, 1, 4, 5]), include_cells=False, progress_bar=True
    )
    # check sub-surface size
    assert sub_surf.n_points == 4
    assert sub_surf.n_cells == 1
    assert sub_surf_adj.n_points == 9
    assert sub_surf_adj.n_cells == 4
    assert sub_surf_nocells.cells[0] == 1


@skip_py2_nobind
def test_slice_along_line_composite(composite):
    # Now test composite data structures
    a = [composite.bounds[0], composite.bounds[2], composite.bounds[4]]
    b = [composite.bounds[1], composite.bounds[3], composite.bounds[5]]
    line = pyvista.Line(a, b, resolution=10)
    output = composite.slice_along_line(line, progress_bar=True)
    assert output.n_blocks == composite.n_blocks


def test_interpolate():
    pdata = pyvista.PolyData()
    pdata.points = np.random.random((10, 3))
    pdata['scalars'] = np.random.random(10)
    surf = pyvista.Sphere(theta_resolution=10, phi_resolution=10)
    interp = surf.interpolate(pdata, radius=0.01, progress_bar=True)
    assert interp.n_points
    assert interp.n_arrays


def test_select_enclosed_points(uniform, hexbeam):
    surf = pyvista.Sphere(center=uniform.center, radius=uniform.length / 2.0)
    result = uniform.select_enclosed_points(surf, progress_bar=True)
    assert isinstance(result, type(uniform))
    assert 'SelectedPoints' in result.array_names
    assert result['SelectedPoints'].any()
    assert result.n_arrays == uniform.n_arrays + 1

    # Now check non-closed surface
    mesh = pyvista.Sphere(end_theta=270)
    surf = mesh.rotate_x(90, inplace=False)
    result = mesh.select_enclosed_points(surf, check_surface=False, progress_bar=True)
    assert isinstance(result, type(mesh))
    assert 'SelectedPoints' in result.array_names
    assert result.n_arrays == mesh.n_arrays + 1
    with pytest.raises(RuntimeError):
        result = mesh.select_enclosed_points(surf, check_surface=True, progress_bar=True)
    with pytest.raises(TypeError):
        result = mesh.select_enclosed_points(hexbeam, check_surface=True, progress_bar=True)


def test_decimate_boundary():
    mesh = examples.load_uniform()
    boundary = mesh.decimate_boundary(progress_bar=True)
    assert boundary.n_points


def test_extract_surface():
    # create a single quadratic hexahedral cell
    lin_pts = np.array(
        [
            [-1, -1, -1],  # node 0
            [1, -1, -1],  # node 1
            [1, 1, -1],  # node 2
            [-1, 1, -1],  # node 3
            [-1, -1, 1],  # node 4
            [1, -1, 1],  # node 5
            [1, 1, 1],  # node 6
            [-1, 1, 1],  # node 7
        ],
        np.double,
    )

    quad_pts = np.array(
        [
            (lin_pts[1] + lin_pts[0]) / 2,  # between point 0 and 1
            (lin_pts[1] + lin_pts[2]) / 2,  # between point 1 and 2
            (lin_pts[2] + lin_pts[3]) / 2,  # and so on...
            (lin_pts[3] + lin_pts[0]) / 2,
            (lin_pts[4] + lin_pts[5]) / 2,
            (lin_pts[5] + lin_pts[6]) / 2,
            (lin_pts[6] + lin_pts[7]) / 2,
            (lin_pts[7] + lin_pts[4]) / 2,
            (lin_pts[0] + lin_pts[4]) / 2,
            (lin_pts[1] + lin_pts[5]) / 2,
            (lin_pts[2] + lin_pts[6]) / 2,
            (lin_pts[3] + lin_pts[7]) / 2,
        ]
    )

    # introduce a minor variation to the location of the mid-side points
    quad_pts += np.random.random(quad_pts.shape) * 0.25
    pts = np.vstack((lin_pts, quad_pts))

    cells = np.hstack((20, np.arange(20))).astype(np.int64, copy=False)
    celltypes = np.array([VTK_QUADRATIC_HEXAHEDRON])
    if VTK9:
        grid = pyvista.UnstructuredGrid(cells, celltypes, pts)
    else:
        grid = pyvista.UnstructuredGrid(np.array([0]), cells, celltypes, pts)

    # expect each face to be divided 6 times since it has a midside node
    surf = grid.extract_surface(progress_bar=True)
    assert surf.n_faces == 36

    # expect each face to be divided several more times than the linear extraction
    surf_subdivided = grid.extract_surface(nonlinear_subdivision=5, progress_bar=True)
    assert surf_subdivided.n_faces > surf.n_faces

    # No subdivision, expect one face per cell
    surf_no_subdivide = grid.extract_surface(nonlinear_subdivision=0, progress_bar=True)
    assert surf_no_subdivide.n_faces == 6


def test_merge_general(uniform):
    thresh = uniform.threshold_percent([0.2, 0.5], progress_bar=True)  # unstructured grid
    con = uniform.contour()  # poly data
    merged = thresh + con
    assert isinstance(merged, pyvista.UnstructuredGrid)
    merged = con + thresh
    assert isinstance(merged, pyvista.UnstructuredGrid)
    # Pure PolyData inputs should yield poly data output
    merged = uniform.extract_surface() + con
    assert isinstance(merged, pyvista.PolyData)


def test_iadd_general(uniform, hexbeam, sphere):
    unstructured = hexbeam
    sphere_shifted = sphere.copy()
    sphere_shifted.points += [1, 1, 1]
    # successful case: poly += poly
    merged = sphere
    merged += sphere_shifted
    assert merged is sphere

    # successful case: unstructured += anything
    merged = unstructured
    merged += uniform
    assert merged is unstructured
    merged += unstructured
    assert merged is unstructured
    merged += sphere
    assert merged is unstructured

    # failing case: poly += non-poly
    merged = sphere
    with pytest.raises(TypeError):
        merged += uniform

    # failing case: uniform += anything
    merged = uniform
    with pytest.raises(TypeError):
        merged += uniform
    with pytest.raises(TypeError):
        merged += unstructured
    with pytest.raises(TypeError):
        merged += sphere


def test_compute_cell_quality():
    mesh = pyvista.ParametricEllipsoid().triangulate().decimate(0.8)
    qual = mesh.compute_cell_quality(progress_bar=True)
    assert 'CellQuality' in qual.array_names
    with pytest.raises(KeyError):
        qual = mesh.compute_cell_quality(quality_measure='foo', progress_bar=True)


def test_compute_derivatives():
    mesh = examples.load_random_hills()
    vector = np.zeros((mesh.n_points, 3))
    vector[:, 1] = np.ones(mesh.n_points)
    mesh['vector'] = vector
    derv = mesh.compute_derivative(
        scalars='vector',
        gradient=True,
        divergence=True,
        vorticity=True,
        qcriterion=True,
        progress_bar=True,
    )
    assert 'gradient' in derv.array_names
    assert np.shape(derv['gradient'])[0] == mesh.n_points
    assert np.shape(derv['gradient'])[1] == 9

    assert 'divergence' in derv.array_names
    assert np.shape(derv['divergence'])[0] == mesh.n_points
    assert len(np.shape(derv['divergence'])) == 1

    assert 'vorticity' in derv.array_names
    assert np.shape(derv['vorticity'])[0] == mesh.n_points
    assert np.shape(derv['vorticity'])[1] == 3

    assert 'qcriterion' in derv.array_names
    assert np.shape(derv['qcriterion'])[0] == mesh.n_points
    assert len(np.shape(derv['qcriterion'])) == 1

    derv = mesh.compute_derivative(
        scalars='vector',
        gradient='gradienttest',
        divergence='divergencetest',
        vorticity='vorticitytest',
        qcriterion='qcriteriontest',
        progress_bar=True,
    )
    assert 'gradienttest' in derv.array_names
    assert np.shape(derv['gradienttest'])[0] == mesh.n_points
    assert np.shape(derv['gradienttest'])[1] == 9

    assert 'divergencetest' in derv.array_names
    assert np.shape(derv['divergencetest'])[0] == mesh.n_points
    assert len(np.shape(derv['divergencetest'])) == 1

    assert 'vorticitytest' in derv.array_names
    assert np.shape(derv['vorticitytest'])[0] == mesh.n_points
    assert np.shape(derv['vorticitytest'])[1] == 3

    assert 'qcriteriontest' in derv.array_names
    assert np.shape(derv['qcriteriontest'])[0] == mesh.n_points
    assert len(np.shape(derv['qcriteriontest'])) == 1

    grad = mesh.compute_derivative(scalars='Elevation', gradient=True, progress_bar=True)
    assert 'gradient' in grad.array_names
    assert np.shape(grad['gradient'])[0] == mesh.n_points
    assert np.shape(grad['gradient'])[1] == 3

    grad = mesh.compute_derivative(
        scalars='Elevation', gradient=True, faster=True, progress_bar=True
    )
    assert 'gradient' in grad.array_names
    assert np.shape(grad['gradient'])[0] == mesh.n_points
    assert np.shape(grad['gradient'])[1] == 3

    grad = mesh.compute_derivative(scalars='vector', gradient=True, faster=True, progress_bar=True)
    assert 'gradient' in grad.array_names
    assert np.shape(grad['gradient'])[0] == mesh.n_points
    assert np.shape(grad['gradient'])[1] == 9

    with pytest.raises(ValueError):
        grad = mesh.compute_derivative(scalars='Elevation', gradient=False, progress_bar=True)

    with pytest.raises(TypeError):
        derv = mesh.compute_derivative(object)

    mesh.point_data.clear()
    with pytest.raises(MissingDataError):
        derv = mesh.compute_derivative()


def test_extract_subset():
    volume = examples.load_uniform()
    voi = volume.extract_subset([0, 3, 1, 4, 5, 7], progress_bar=True)
    assert isinstance(voi, pyvista.UniformGrid)
    # Test that we fix the confusing issue from extents in
    #   https://gitlab.kitware.com/vtk/vtk/-/issues/17938
    assert voi.origin == voi.bounds[::2]


def test_gaussian_smooth_output_type():
    volume = examples.load_uniform()
    volume_smooth = volume.gaussian_smooth()
    assert isinstance(volume_smooth, pyvista.UniformGrid)
    volume_smooth = volume.gaussian_smooth(scalars='Spatial Point Data')
    assert isinstance(volume_smooth, pyvista.UniformGrid)


def test_gaussian_smooth_constant_data():
    point_data = np.ones((10, 10, 10))
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume_smoothed = volume.gaussian_smooth()
    assert np.allclose(volume.point_data['point_data'], volume_smoothed.point_data['point_data'])


def test_gaussian_smooth_outlier():
    point_data = np.ones((10, 10, 10))
    point_data[4, 4, 4] = 100
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume_smoothed = volume.gaussian_smooth()
    assert volume_smoothed.get_data_range()[1] < volume.get_data_range()[1]


def test_gaussian_smooth_cell_data_specified():
    point_data = np.zeros((10, 10, 10))
    cell_data = np.zeros((9, 9, 9))
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume.cell_data['cell_data'] = cell_data.flatten(order='F')
    with pytest.raises(ValueError):
        volume.gaussian_smooth(scalars='cell_data')


def test_gaussian_smooth_cell_data_active():
    point_data = np.zeros((10, 10, 10))
    cell_data = np.zeros((9, 9, 9))
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume.cell_data['cell_data'] = cell_data.flatten(order='F')
    volume.set_active_scalars('cell_data')
    with pytest.raises(ValueError):
        volume.gaussian_smooth()


def test_median_smooth_output_type():
    volume = examples.load_uniform()
    volume_smooth = volume.median_smooth()
    assert isinstance(volume_smooth, pyvista.UniformGrid)
    volume_smooth = volume.median_smooth(scalars='Spatial Point Data')
    assert isinstance(volume_smooth, pyvista.UniformGrid)


def test_median_smooth_constant_data():
    point_data = np.ones((10, 10, 10))
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume_smoothed = volume.median_smooth()
    assert np.array_equal(volume.point_data['point_data'], volume_smoothed.point_data['point_data'])


def test_median_smooth_outlier():
    point_data = np.ones((10, 10, 10))
    point_data_outlier = point_data.copy()
    point_data_outlier[4, 4, 4] = 100
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume_outlier = pyvista.UniformGrid(dims=(10, 10, 10))
    volume_outlier.point_data['point_data'] = point_data_outlier.flatten(order='F')
    volume_outlier_smoothed = volume_outlier.median_smooth()
    assert np.array_equal(
        volume.point_data['point_data'], volume_outlier_smoothed.point_data['point_data']
    )


def test_image_dilate_erode_output_type():
    point_data = np.zeros((10, 10, 10))
    point_data[4, 4, 4] = 1
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume_dilate_erode = volume.image_dilate_erode()
    assert isinstance(volume_dilate_erode, pyvista.UniformGrid)
    volume_dilate_erode = volume.image_dilate_erode(scalars='point_data')
    assert isinstance(volume_dilate_erode, pyvista.UniformGrid)


def test_image_dilate_erode_dilation():
    point_data = np.zeros((10, 10, 10))
    point_data[4, 4, 4] = 1
    point_data_dilated = point_data.copy()
    point_data_dilated[3:6, 3:6, 4] = 1  # "activate" all voxels within diameter 3 around (4,4,4)
    point_data_dilated[3:6, 4, 3:6] = 1
    point_data_dilated[4, 3:6, 3:6] = 1
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume_dilated = volume.image_dilate_erode()  # default is binary dilation
    assert np.array_equal(
        volume_dilated.point_data['point_data'], point_data_dilated.flatten(order='F')
    )


def test_image_dilate_erode_erosion():
    point_data = np.zeros((10, 10, 10))
    point_data[4, 4, 4] = 1
    point_data_eroded = np.zeros((10, 10, 10))
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume_eroded = volume.image_dilate_erode(0, 1)  # binary erosion
    assert np.array_equal(
        volume_eroded.point_data['point_data'], point_data_eroded.flatten(order='F')
    )


def test_image_dilate_erode_cell_data_specified():
    point_data = np.zeros((10, 10, 10))
    cell_data = np.zeros((9, 9, 9))
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume.cell_data['cell_data'] = cell_data.flatten(order='F')
    with pytest.raises(ValueError):
        volume.image_dilate_erode(scalars='cell_data')


def test_image_dilate_erode_cell_data_active():
    point_data = np.zeros((10, 10, 10))
    cell_data = np.zeros((9, 9, 9))
    volume = pyvista.UniformGrid(dims=(10, 10, 10))
    volume.point_data['point_data'] = point_data.flatten(order='F')
    volume.cell_data['cell_data'] = cell_data.flatten(order='F')
    volume.set_active_scalars('cell_data')
    with pytest.raises(ValueError):
        volume.image_dilate_erode()


def test_image_threshold_output_type():
    threshold = 10  # 'random' value
    volume = examples.load_uniform()
    volume_thresholded = volume.image_threshold(threshold)
    assert isinstance(volume_thresholded, pyvista.UniformGrid)
    volume_thresholded = volume.image_threshold(threshold, scalars='Spatial Point Data')
    assert isinstance(volume_thresholded, pyvista.UniformGrid)


def test_image_threshold_wrong_threshold_length():
    threshold = (10, 10, 10)  # tuple with too many values
    volume = examples.load_uniform()
    with pytest.raises(ValueError):
        volume.image_threshold(threshold)


def test_image_threshold_wrong_threshold_type():
    threshold = {'min': 0, 'max': 10}  # dict thresh
    volume = examples.load_uniform()
    with pytest.raises(TypeError):
        volume.image_threshold(threshold)


@pytest.mark.parametrize('in_value', [1, None])
@pytest.mark.parametrize('out_value', [0, None])
def test_image_threshold_upper(in_value, out_value):
    threshold = 0  # 'random' value
    array_shape = (3, 3, 3)
    in_value_location = (1, 1, 1)
    point_data = np.ones(array_shape)
    in_value_mask = np.zeros(array_shape, dtype=bool)
    in_value_mask[in_value_location] = True
    point_data[in_value_mask] = 100  # the only 'in' value
    point_data[~in_value_mask] = -100  # out values
    volume = pyvista.UniformGrid(dims=array_shape)
    volume.point_data['point_data'] = point_data.flatten(order='F')
    point_data_thresholded = point_data.copy()
    if in_value is not None:
        point_data_thresholded[in_value_mask] = in_value
    if out_value is not None:
        point_data_thresholded[~in_value_mask] = out_value
    volume_thresholded = volume.image_threshold(threshold, in_value=in_value, out_value=out_value)
    assert np.array_equal(
        volume_thresholded.point_data['point_data'], point_data_thresholded.flatten(order='F')
    )


@pytest.mark.parametrize('in_value', [1, None])
@pytest.mark.parametrize('out_value', [0, None])
def test_image_threshold_between(in_value, out_value):
    threshold = [-10, 10]  # 'random' values
    array_shape = (3, 3, 3)
    in_value_location = (1, 1, 1)
    low_value_location = (1, 1, 2)
    point_data = np.ones(array_shape)
    in_value_mask = np.zeros(array_shape, dtype=bool)
    in_value_mask[in_value_location] = True
    point_data[in_value_mask] = 0  # the only 'in' value
    point_data[~in_value_mask] = 100  # out values
    point_data[low_value_location] = -100  # add a value below the threshold also
    volume = pyvista.UniformGrid(dims=array_shape)
    volume.point_data['point_data'] = point_data.flatten(order='F')
    point_data_thresholded = point_data.copy()
    if in_value is not None:
        point_data_thresholded[in_value_mask] = in_value
    if out_value is not None:
        point_data_thresholded[~in_value_mask] = out_value
    volume_thresholded = volume.image_threshold(threshold, in_value=in_value, out_value=out_value)
    assert np.array_equal(
        volume_thresholded.point_data['point_data'], point_data_thresholded.flatten(order='F')
    )


def test_extract_subset_structured():
    structured = examples.load_structured()
    voi = structured.extract_subset([0, 3, 1, 4, 0, 1])
    assert isinstance(voi, pyvista.StructuredGrid)
    assert voi.dimensions == (4, 4, 1)


@pytest.fixture
def structured_grids_split_coincident():
    """Two structured grids which are coincident along second axis (axis=1), and
    the grid from which they were extracted."""
    structured = examples.load_structured()
    point_data = (np.ones((80, 80)) * np.arange(0, 80)).ravel(order='F')
    cell_data = (np.ones((79, 79)) * np.arange(0, 79)).T.ravel(order='F')
    structured.point_data['point_data'] = point_data
    structured.cell_data['cell_data'] = cell_data
    voi_1 = structured.extract_subset([0, 80, 0, 40, 0, 1])
    voi_2 = structured.extract_subset([0, 80, 40, 80, 0, 1])
    return voi_1, voi_2, structured


@pytest.fixture
def structured_grids_split_disconnected():
    """Two structured grids which are disconnected."""
    structured = examples.load_structured()
    point_data = (np.ones((80, 80)) * np.arange(0, 80)).ravel(order='F')
    cell_data = (np.ones((79, 79)) * np.arange(0, 79)).T.ravel(order='F')
    structured.point_data['point_data'] = point_data
    structured.cell_data['cell_data'] = cell_data
    voi_1 = structured.extract_subset([0, 80, 0, 40, 0, 1])
    voi_2 = structured.extract_subset([0, 80, 45, 80, 0, 1])
    return voi_1, voi_2


def test_concatenate_structured(
    structured_grids_split_coincident, structured_grids_split_disconnected
):
    voi_1, voi_2, structured = structured_grids_split_coincident
    joined = voi_1.concatenate(voi_2, axis=1)
    assert structured.points == pytest.approx(joined.points)
    assert structured.volume == pytest.approx(joined.volume)
    assert structured.point_data['point_data'] == pytest.approx(joined.point_data['point_data'])
    assert structured.cell_data['cell_data'] == pytest.approx(joined.cell_data['cell_data'])


def test_concatenate_structured_bad_dimensions(structured_grids_split_coincident):
    voi_1, voi_2, structured = structured_grids_split_coincident

    # test invalid dimensions
    with pytest.raises(ValueError):
        voi_1.concatenate(voi_2, axis=0)

    with pytest.raises(ValueError):
        voi_1.concatenate(voi_2, axis=2)


def test_concatenate_structured_bad_inputs(structured_grids_split_coincident):
    voi_1, voi_2, structured = structured_grids_split_coincident
    with pytest.raises(RuntimeError):
        voi_1.concatenate(voi_2, axis=3)


def test_concatenate_structured_bad_point_data(structured_grids_split_coincident):
    voi_1, voi_2, structured = structured_grids_split_coincident
    voi_1['point_data'] = voi_1['point_data'] * 2.0
    with pytest.raises(RuntimeError):
        voi_1.concatenate(voi_2, axis=1)


def test_concatenate_structured_disconnected(structured_grids_split_disconnected):
    voi_1, voi_2 = structured_grids_split_disconnected
    with pytest.raises(RuntimeError):
        voi_1.concatenate(voi_2, axis=1)


def test_concatenate_structured_different_arrays(structured_grids_split_coincident):
    voi_1, voi_2, structured = structured_grids_split_coincident
    point_data = voi_1.point_data.pop('point_data')
    with pytest.raises(RuntimeError):
        voi_1.concatenate(voi_2, axis=1)

    voi_1.point_data['point_data'] = point_data
    voi_1.cell_data.remove('cell_data')
    with pytest.raises(RuntimeError):
        voi_1.concatenate(voi_2, axis=1)


def test_structured_add_non_grid():
    grid = examples.load_structured()
    merged = grid + examples.load_hexbeam()
    assert isinstance(merged, pyvista.UnstructuredGrid)


def test_poly_data_strip():
    mesh = examples.load_airplane()
    slc = mesh.slice(normal='z', origin=(0, 0, -10))
    stripped = slc.strip(progress_bar=True)
    assert stripped.n_cells == 1


def test_shrink():
    mesh = pyvista.Sphere()
    shrunk = mesh.shrink(shrink_factor=0.8, progress_bar=True)
    assert shrunk.n_cells == mesh.n_cells
    assert shrunk.area < mesh.area
    mesh = examples.load_uniform()
    shrunk = mesh.shrink(shrink_factor=0.8, progress_bar=True)
    assert shrunk.n_cells == mesh.n_cells
    assert shrunk.volume < mesh.volume


def test_tessellate():
    points = np.array(
        [
            [0.0, 0.0, 0.0],
            [2.0, 0.0, 0.0],
            [1.0, 2.0, 0.0],
            [1.0, 0.5, 0.0],
            [1.5, 1.5, 0.0],
            [0.5, 1.5, 0.0],
        ]
    )
    cells = np.array([6, 0, 1, 2, 3, 4, 5])
    cell_types = np.array([VTK_QUADRATIC_TRIANGLE])
    ugrid = pyvista.UnstructuredGrid(cells, cell_types, points)
    tessellated = ugrid.tessellate(progress_bar=True)
    assert tessellated.n_cells > ugrid.n_cells
    assert tessellated.n_points > ugrid.n_points
    assert ugrid.tessellate(max_n_subdivide=6).n_cells > tessellated.n_cells
    assert ugrid.tessellate(merge_points=False).n_points > tessellated.n_points
    with pytest.raises(TypeError):
        pdata = pyvista.PolyData()
        tessellated = pdata.tessellate(progress_bar=True)


@pytest.mark.parametrize('num_cell_arrays,num_point_data', itertools.product([0, 1, 2], [0, 1, 2]))
def test_transform_mesh(datasets, num_cell_arrays, num_point_data):
    # rotate about x-axis by 90 degrees
    for dataset in datasets:
        tf = pyvista.transformations.axis_angle_rotation((1, 0, 0), 90)

        for i in range(num_cell_arrays):
            dataset.cell_data[f'C{i}'] = np.random.rand(dataset.n_cells, 3)

        for i in range(num_point_data):
            dataset.point_data[f'P{i}'] = np.random.rand(dataset.n_points, 3)

        # deactivate any active vectors!
        # even if transform_all_input_vectors is False, vtkTransformfilter will
        # transform active vectors
        dataset.set_active_vectors(None)

        transformed = dataset.transform(tf, transform_all_input_vectors=False, inplace=False)

        assert dataset.points[:, 0] == pytest.approx(transformed.points[:, 0])
        assert dataset.points[:, 2] == pytest.approx(-transformed.points[:, 1])
        assert dataset.points[:, 1] == pytest.approx(transformed.points[:, 2])

        # ensure that none of the vector data is changed
        for name, array in dataset.point_data.items():
            assert transformed.point_data[name] == pytest.approx(array)

        for name, array in dataset.cell_data.items():
            assert transformed.cell_data[name] == pytest.approx(array)

        # verify that the cell connectivity is a deep copy
        if VTK9 and hasattr(dataset, '_connectivity_array'):
            transformed._connectivity_array[0] += 1
            assert not np.array_equal(dataset._connectivity_array, transformed._connectivity_array)
        if VTK9 and hasattr(dataset, 'cell_connectivity'):
            transformed.cell_connectivity[0] += 1
            assert not np.array_equal(dataset.cell_connectivity, transformed.cell_connectivity)


@pytest.mark.parametrize('num_cell_arrays,num_point_data', itertools.product([0, 1, 2], [0, 1, 2]))
def test_transform_mesh_and_vectors(datasets, num_cell_arrays, num_point_data):
    for dataset in datasets:
        # rotate about x-axis by 90 degrees
        tf = pyvista.transformations.axis_angle_rotation((1, 0, 0), 90)

        for i in range(num_cell_arrays):
            dataset.cell_data[f'C{i}'] = np.random.rand(dataset.n_cells, 3)

        for i in range(num_point_data):
            dataset.point_data[f'P{i}'] = np.random.rand(dataset.n_points, 3)

        # track original untransformed dataset
        orig_dataset = dataset.copy(deep=True)

        # handle
        f = pyvista._vtk.vtkTransformFilter()
        if not hasattr(f, 'SetTransformAllInputVectors'):
            with pytest.raises(VTKVersionError):
                transformed = dataset.transform(tf, transform_all_input_vectors=True, inplace=False)
            return

        transformed = dataset.transform(tf, transform_all_input_vectors=True, inplace=False)

        # verify that the dataset has not modified
        if num_cell_arrays:
            assert dataset.cell_data == orig_dataset.cell_data
        if num_point_data:
            assert dataset.point_data == orig_dataset.point_data

        assert dataset.points[:, 0] == pytest.approx(transformed.points[:, 0])
        assert dataset.points[:, 2] == pytest.approx(-transformed.points[:, 1])
        assert dataset.points[:, 1] == pytest.approx(transformed.points[:, 2])

        for i in range(num_cell_arrays):
            assert dataset.cell_data[f'C{i}'][:, 0] == pytest.approx(
                transformed.cell_data[f'C{i}'][:, 0]
            )
            assert dataset.cell_data[f'C{i}'][:, 2] == pytest.approx(
                -transformed.cell_data[f'C{i}'][:, 1]
            )
            assert dataset.cell_data[f'C{i}'][:, 1] == pytest.approx(
                transformed.cell_data[f'C{i}'][:, 2]
            )

        for i in range(num_point_data):
            assert dataset.point_data[f'P{i}'][:, 0] == pytest.approx(
                transformed.point_data[f'P{i}'][:, 0]
            )
            assert dataset.point_data[f'P{i}'][:, 2] == pytest.approx(
                -transformed.point_data[f'P{i}'][:, 1]
            )
            assert dataset.point_data[f'P{i}'][:, 1] == pytest.approx(
                transformed.point_data[f'P{i}'][:, 2]
            )


@pytest.mark.needs_vtk9
@pytest.mark.parametrize("num_cell_arrays,num_point_data", itertools.product([0, 1, 2], [0, 1, 2]))
def test_transform_int_vectors_warning(datasets, num_cell_arrays, num_point_data):
    for dataset in datasets:
        tf = pyvista.transformations.axis_angle_rotation((1, 0, 0), 90)
        dataset.clear_data()
        for i in range(num_cell_arrays):
            dataset.cell_data[f"C{i}"] = np.random.randint(
                np.iinfo(int).max, size=(dataset.n_cells, 3)
            )
        for i in range(num_point_data):
            dataset.point_data[f"P{i}"] = np.random.randint(
                np.iinfo(int).max, size=(dataset.n_points, 3)
            )
        if not (num_cell_arrays == 0 and num_point_data == 0):
            with pytest.warns(UserWarning, match="Integer"):
                _ = dataset.transform(tf, transform_all_input_vectors=True, inplace=False)


@pytest.mark.parametrize(
    'dataset',
    [
        examples.load_uniform(),  # UniformGrid
        examples.load_rectilinear(),  # RectilinearGrid
    ],
)
def test_transform_inplace_bad_types(dataset):
    # assert that transformations of these types throw the correct error
    tf = pyvista.transformations.axis_angle_rotation(
        (1, 0, 0), 90
    )  # rotate about x-axis by 90 degrees
    with pytest.raises(TypeError):
        dataset.transform(tf, inplace=True)


def test_reflect_mesh_about_point(datasets):
    for dataset in datasets:
        x_plane = 500
        reflected = dataset.reflect((1, 0, 0), point=(x_plane, 0, 0), progress_bar=True)
        assert reflected.n_cells == dataset.n_cells
        assert reflected.n_points == dataset.n_points
        assert np.allclose(x_plane - dataset.points[:, 0], reflected.points[:, 0] - x_plane)
        assert np.allclose(dataset.points[:, 1:], reflected.points[:, 1:])


@pytest.mark.needs_vtk9
def test_reflect_mesh_with_vectors(datasets):
    for dataset in datasets:
        if hasattr(dataset, 'compute_normals'):
            dataset.compute_normals(inplace=True, progress_bar=True)

        # add vector data to cell and point arrays
        dataset.cell_data['C'] = np.arange(dataset.n_cells)[:, np.newaxis] * np.array(
            [1, 2, 3], dtype=float
        ).reshape((1, 3))
        dataset.point_data['P'] = np.arange(dataset.n_points)[:, np.newaxis] * np.array(
            [1, 2, 3], dtype=float
        ).reshape((1, 3))

        reflected = dataset.reflect(
            (1, 0, 0), transform_all_input_vectors=True, inplace=False, progress_bar=True
        )

        # assert isinstance(reflected, type(dataset))
        assert reflected.n_cells == dataset.n_cells
        assert reflected.n_points == dataset.n_points
        assert np.allclose(dataset.points[:, 0], -reflected.points[:, 0])
        assert np.allclose(dataset.points[:, 1:], reflected.points[:, 1:])

        # assert normals are reflected
        if hasattr(dataset, 'compute_normals'):
            assert np.allclose(
                dataset.cell_data['Normals'][:, 0], -reflected.cell_data['Normals'][:, 0]
            )
            assert np.allclose(
                dataset.cell_data['Normals'][:, 1:], reflected.cell_data['Normals'][:, 1:]
            )
            assert np.allclose(
                dataset.point_data['Normals'][:, 0], -reflected.point_data['Normals'][:, 0]
            )
            assert np.allclose(
                dataset.point_data['Normals'][:, 1:], reflected.point_data['Normals'][:, 1:]
            )

        # assert other vector fields are reflected
        assert np.allclose(dataset.cell_data['C'][:, 0], -reflected.cell_data['C'][:, 0])
        assert np.allclose(dataset.cell_data['C'][:, 1:], reflected.cell_data['C'][:, 1:])
        assert np.allclose(dataset.point_data['P'][:, 0], -reflected.point_data['P'][:, 0])
        assert np.allclose(dataset.point_data['P'][:, 1:], reflected.point_data['P'][:, 1:])


@pytest.mark.parametrize(
    'dataset',
    [
        examples.load_hexbeam(),  # UnstructuredGrid
        examples.load_airplane(),  # PolyData
        examples.load_structured(),  # StructuredGrid
    ],
)
def test_reflect_inplace(dataset):
    orig = dataset.copy()
    dataset.reflect((1, 0, 0), inplace=True, progress_bar=True)
    assert dataset.n_cells == orig.n_cells
    assert dataset.n_points == orig.n_points
    assert np.allclose(dataset.points[:, 0], -orig.points[:, 0])
    assert np.allclose(dataset.points[:, 1:], orig.points[:, 1:])


@pytest.mark.parametrize(
    'dataset',
    [
        examples.load_uniform(),  # UniformGrid
        examples.load_rectilinear(),  # RectilinearGrid
    ],
)
def test_transform_inplace_bad_types_2(dataset):
    # assert that transformations of these types throw the correct error
    with pytest.raises(TypeError):
        dataset.reflect((1, 0, 0), inplace=True)


def test_extrude_rotate():
    resolution = 4
    line = pyvista.Line(pointa=(0, 0, 0), pointb=(1, 0, 0))

    with pytest.raises(ValueError):
        line.extrude_rotate(resolution=0, capping=True)

    poly = line.extrude_rotate(resolution=resolution, progress_bar=True, capping=True)
    assert poly.n_cells == line.n_points - 1
    assert poly.n_points == (resolution + 1) * line.n_points

    translation = 10.0
    dradius = 1.0
    poly = line.extrude_rotate(
        translation=translation,
        dradius=dradius,
        progress_bar=True,
        capping=True,
    )
    zmax = poly.bounds[5]
    assert zmax == translation
    xmax = poly.bounds[1]
    assert xmax == line.bounds[1] + dradius

    poly = line.extrude_rotate(angle=90.0, progress_bar=True, capping=True)
    xmin = poly.bounds[0]
    xmax = poly.bounds[1]
    ymin = poly.bounds[2]
    ymax = poly.bounds[3]
    assert (
        (xmin == line.bounds[0])
        and (xmax == line.bounds[1])
        and (ymin == line.bounds[0])
        and (ymax == line.bounds[1])
    )

    rotation_axis = (0, 1, 0)
    if not pyvista.vtk_version_info >= (9, 1, 0):
        with pytest.raises(VTKVersionError):
            poly = line.extrude_rotate(rotation_axis=rotation_axis, capping=True)
    else:
        poly = line.extrude_rotate(
            rotation_axis=rotation_axis, resolution=resolution, progress_bar=True, capping=True
        )
        assert poly.n_cells == line.n_points - 1
        assert poly.n_points == (resolution + 1) * line.n_points

    with pytest.raises(ValueError):
        line.extrude_rotate(rotation_axis=[1, 2], capping=True)


def test_extrude_rotate_inplace():
    resolution = 4
    poly = pyvista.Line(pointa=(0, 0, 0), pointb=(1, 0, 0))
    old_line = poly.copy()
    poly.extrude_rotate(resolution=resolution, inplace=True, progress_bar=True, capping=True)
    assert poly.n_cells == old_line.n_points - 1
    assert poly.n_points == (resolution + 1) * old_line.n_points


@pytest.mark.needs_vtk9
def test_extrude_trim():
    direction = (0, 0, 1)
    mesh = pyvista.Plane(
        center=(0, 0, 0), direction=direction, i_size=1, j_size=1, i_resolution=10, j_resolution=10
    )
    trim_surface = pyvista.Plane(
        center=(0, 0, 1), direction=direction, i_size=2, j_size=2, i_resolution=20, j_resolution=20
    )
    poly = mesh.extrude_trim(direction, trim_surface)
    assert np.isclose(poly.volume, 1.0)


@pytest.mark.needs_vtk9
@pytest.mark.parametrize('extrusion', ["boundary_edges", "all_edges"])
@pytest.mark.parametrize(
    'capping', ["intersection", "minimum_distance", "maximum_distance", "average_distance"]
)
def test_extrude_trim_strategy(extrusion, capping):
    direction = (0, 0, 1)
    mesh = pyvista.Plane(
        center=(0, 0, 0), direction=direction, i_size=1, j_size=1, i_resolution=10, j_resolution=10
    )
    trim_surface = pyvista.Plane(
        center=(0, 0, 1), direction=direction, i_size=2, j_size=2, i_resolution=20, j_resolution=20
    )
    poly = mesh.extrude_trim(direction, trim_surface, extrusion=extrusion, capping=capping)
    assert isinstance(poly, pyvista.PolyData)
    assert poly.n_cells
    assert poly.n_points


def test_extrude_trim_catch():
    direction = (0, 0, 1)
    mesh = pyvista.Plane()
    trim_surface = pyvista.Plane()
    with pytest.raises(ValueError):
        _ = mesh.extrude_trim(direction, trim_surface, extrusion="Invalid strategy")
    with pytest.raises(TypeError, match='Invalid type'):
        _ = mesh.extrude_trim(direction, trim_surface, extrusion=0)
    with pytest.raises(ValueError):
        _ = mesh.extrude_trim(direction, trim_surface, capping="Invalid strategy")
    with pytest.raises(TypeError, match='Invalid type'):
        _ = mesh.extrude_trim(direction, trim_surface, capping=0)
    with pytest.raises(TypeError):
        _ = mesh.extrude_trim('foobar', trim_surface)
    with pytest.raises(TypeError):
        _ = mesh.extrude_trim([1, 2], trim_surface)


@pytest.mark.needs_vtk9
def test_extrude_trim_inplace():
    direction = (0, 0, 1)
    mesh = pyvista.Plane(
        center=(0, 0, 0), direction=direction, i_size=1, j_size=1, i_resolution=10, j_resolution=10
    )
    trim_surface = pyvista.Plane(
        center=(0, 0, 1), direction=direction, i_size=2, j_size=2, i_resolution=20, j_resolution=20
    )
    mesh.extrude_trim(direction, trim_surface, inplace=True, progress_bar=True)
    assert np.isclose(mesh.volume, 1.0)


@pytest.mark.parametrize('inplace', [True, False])
def test_subdivide_adaptive(sphere, inplace):
    orig_n_faces = sphere.n_faces
    sub = sphere.subdivide_adaptive(0.01, 0.001, 100000, 2, inplace=inplace, progress_bar=True)
    assert sub.n_faces > orig_n_faces
    if inplace:
        assert sphere.n_faces == sub.n_faces


def test_invalid_subdivide_adaptive(cube):
    # check non-triangulated
    with pytest.raises(NotAllTrianglesError):
        cube.subdivide_adaptive()


@pytest.mark.needs_vtk9
def test_collision(sphere):
    moved_sphere = sphere.translate((0.5, 0, 0), inplace=False)
    output, n_collision = sphere.collision(moved_sphere)
    assert isinstance(output, pyvista.PolyData)
    assert n_collision > 40
    assert 'ContactCells' in output.field_data

    # test no collision
    moved_sphere.translate((1000, 0, 0), inplace=True)
    _, n_collision = sphere.collision(moved_sphere)
    assert not n_collision


@pytest.mark.needs_vtk9
def test_collision_solid_non_triangle(hexbeam):
    # test non-triangular mesh with a unstructured grid
    cube = pyvista.Cube()
    output, n_collision = cube.collision(hexbeam)
    assert isinstance(output, pyvista.PolyData)
    assert n_collision > 40
    assert 'ContactCells' in output.field_data
    assert output.is_all_triangles


def test_reconstruct_surface_poly(sphere):
    pc = pyvista.wrap(sphere.points)
    surf = pc.reconstruct_surface(nbr_sz=10, sample_spacing=50)
    assert surf.is_all_triangles


def test_is_manifold(sphere, plane):
    assert sphere.is_manifold
    assert not plane.is_manifold


def test_reconstruct_surface_unstructured():
    mesh = examples.load_hexbeam().reconstruct_surface()
    assert isinstance(mesh, pyvista.PolyData)
    assert mesh.n_points


@pytest.mark.needs_vtk9
def test_integrate_data_datasets(datasets):
    """Test multiple dataset types."""
    for dataset in datasets:
        integrated = dataset.integrate_data()
        if "Area" in integrated.array_names:
            assert integrated["Area"] > 0
        elif "Volume" in integrated.array_names:
            assert integrated["Volume"] > 0
        else:
            raise ValueError("Unexpected integration")


@pytest.mark.needs_vtk9
def test_integrate_data():
    """Test specific case."""
    # sphere with radius = 0.5, area = pi
    # increase resolution to increase precision
    sphere = pyvista.Sphere(theta_resolution=100, phi_resolution=100)
    sphere.cell_data["cdata"] = 2 * np.ones(sphere.n_cells)
    sphere.point_data["pdata"] = 3 * np.ones(sphere.n_points)

    integrated = sphere.integrate_data()
    assert np.isclose(integrated["Area"], np.pi, rtol=1e-3)
    assert np.isclose(integrated["cdata"], 2 * np.pi, rtol=1e-3)
    assert np.isclose(integrated["pdata"], 3 * np.pi, rtol=1e-3)


def test_subdivide_tetra(tetbeam):
    grid = tetbeam.subdivide_tetra()
    assert grid.n_cells == tetbeam.n_cells * 12


# tests/test_geometric_objects.py

import numpy as np
import pytest

import pyvista


def test_cylinder():
    surf = pyvista.Cylinder([0, 10, 0], [1, 1, 1], 1, 5)
    assert np.any(surf.points)
    assert np.any(surf.faces)


def test_cylinder_structured():
    cyl = pyvista.CylinderStructured()
    assert np.any(cyl.points)
    assert np.any(cyl.n_cells)


@pytest.mark.parametrize('scale', [None, 2.0, 4, 'auto'])
def test_arrow(scale):
    surf = pyvista.Arrow([0, 0, 0], [1, 1, 1], scale=scale)
    assert np.any(surf.points)
    assert np.any(surf.faces)


def test_arrow_raises_error():
    with pytest.raises(TypeError):
        pyvista.Arrow([0, 0, 0], [1, 1, 1], scale='badarg')


def test_sphere():
    surf = pyvista.Sphere()
    assert np.any(surf.points)
    assert np.any(surf.faces)


def test_plane():
    surf = pyvista.Plane()
    assert np.any(surf.points)
    assert np.any(surf.faces)


def test_line():
    pointa = (0, 0, 0)
    pointb = (10, 1.0, 3)

    line = pyvista.Line(pointa, pointb)
    assert line.n_points == 2
    assert line.n_cells == 1
    line = pyvista.Line(pointa, pointb, 10)
    assert line.n_points == 11
    assert line.n_cells == 1

    with pytest.raises(ValueError):
        pyvista.Line(pointa, pointb, -1)

    with pytest.raises(TypeError):
        pyvista.Line(pointa, pointb, 0.1)  # from vtk

    with pytest.raises(TypeError):
        pyvista.Line((0, 0), pointb)

    with pytest.raises(TypeError):
        pyvista.Line(pointa, (10, 1.0))


def test_multiple_lines():
    points = np.array([[0, 0, 0], [1, 1, 0], [2, 2, 2], [3, 3, 0]])
    multiple_lines = pyvista.MultipleLines(points=points)
    assert multiple_lines.n_points == 4
    assert multiple_lines.n_cells == 1

    points = np.array([[0, 0, 0], [1, 1 * np.sqrt(3), 0], [2, 0, 0], [3, 3 * np.sqrt(3), 0]])
    multiple_lines = pyvista.MultipleLines(points=points)

    with pytest.raises(ValueError):
        pyvista.MultipleLines(points[:, :1])

    with pytest.raises(ValueError):
        pyvista.MultipleLines(points[0, :])


def test_tube():
    pointa = (0, 0, 0)
    pointb = (10, 1.0, 3)

    tube = pyvista.Tube(n_sides=3)
    assert tube.n_points == 6
    assert tube.n_cells == 3
    tube = pyvista.Tube(pointa, pointb, 10)
    assert tube.n_points == 165
    assert tube.n_cells == 15

    with pytest.raises(ValueError):
        pyvista.Tube(pointa, pointb, -1)

    with pytest.raises(TypeError):
        pyvista.Tube(pointa, pointb, 0.1)  # from vtk

    with pytest.raises(TypeError):
        pyvista.Tube((0, 0), pointb)

    with pytest.raises(TypeError):
        pyvista.Tube(pointa, (10, 1.0))


def test_cube():
    cube = pyvista.Cube()
    assert np.any(cube.points)
    assert np.any(cube.faces)
    bounds = (1.0, 3.0, 5.0, 6.0, 7.0, 8.0)
    cube = pyvista.Cube(bounds=bounds)
    assert np.any(cube.points)
    assert np.any(cube.faces)
    assert np.allclose(cube.bounds, bounds)


def test_cone():
    cone = pyvista.Cone()
    assert np.any(cone.points)
    assert np.any(cone.faces)


def test_box():
    geom = pyvista.Box()
    assert np.any(geom.points)

    bounds = [-10.0, 10.0, 10.0, 20.0, -5.0, 5.0]
    level = 3
    quads = True
    mesh1 = pyvista.Box(bounds, level, quads)
    assert mesh1.n_cells == (level + 1) * (level + 1) * 6
    assert np.allclose(mesh1.bounds, bounds)

    quads = False
    mesh2 = pyvista.Box(bounds, level, quads)
    assert mesh2.n_cells == mesh1.n_cells * 2


def test_polygon():
    geom = pyvista.Polygon()
    assert np.any(geom.points)

    geom1 = pyvista.Polygon(fill=True)
    assert geom1.n_cells == 2
    geom2 = pyvista.Polygon(fill=False)
    assert geom2.n_cells == 1


def test_disc():
    geom = pyvista.Disc()
    assert np.any(geom.points)

    normal = np.array([1.2, 3.4, 5.6])
    unit_normal = normal / np.linalg.norm(normal)
    geom = pyvista.Disc(normal=unit_normal)

    normals = geom.compute_normals()['Normals']
    assert np.allclose(np.dot(normals, unit_normal), 1)

    center = (1.2, 3.4, 5.6)
    geom = pyvista.Disc(center=center)

    assert np.allclose(
        geom.bounds, pyvista.Disc().bounds + np.array([1.2, 1.2, 3.4, 3.4, 5.6, 5.6])
    )


def test_superquadric():
    geom = pyvista.Superquadric()
    assert np.any(geom.points)


# def test_supertoroid():
#     geom = pyvista.SuperToroid()
#     assert np.any(geom.points)


# def test_ellipsoid():
#     geom = pyvista.Ellipsoid()
#     assert np.any(geom.points)


def test_text_3d():
    mesh = pyvista.Text3D("foo")
    assert mesh.n_points
    assert mesh.n_cells


def test_wavelet():
    mesh = pyvista.Wavelet()
    assert mesh.n_points
    assert mesh.n_cells


def test_circular_arc():
    pointa = [-1, 0, 0]
    pointb = [0, 1, 0]
    center = [0, 0, 0]
    resolution = 100

    mesh = pyvista.CircularArc(pointa, pointb, center, resolution)
    assert mesh.n_points == resolution + 1
    assert mesh.n_cells == 1
    distance = np.arange(0.0, 1.0 + 0.01, 0.01) * np.pi / 2.0
    assert np.allclose(mesh['Distance'], distance)

    # pointa and pointb are not equidistant from center
    with pytest.raises(ValueError):
        mesh = pyvista.CircularArc([-1, 0, 0], [-0.99, 0.001, 0], [0, 0, 0], 100)


def test_circular_arc_from_normal():
    center = [0, 0, 0]
    normal = [0, 0, 1]
    polar = [-2.0, 0, 0]
    angle = 90
    resolution = 100

    mesh = pyvista.CircularArcFromNormal(center, resolution, normal, polar, angle)
    assert mesh.n_points == resolution + 1
    assert mesh.n_cells == 1
    distance = np.arange(0.0, 1.0 + 0.01, 0.01) * np.pi
    assert np.allclose(mesh['Distance'], distance)


def test_pyramid():
    pointa = [1.0, 1.0, 1.0]
    pointb = [-1.0, 1.0, 1.0]
    pointc = [-1.0, -1.0, 1.0]
    pointd = [1.0, -1.0, 1.0]
    pointe = [0.0, 0.0, 0.0]
    points = np.array([pointa, pointb, pointc, pointd, pointe])

    mesh = pyvista.Pyramid(points)
    assert mesh.n_points
    assert mesh.n_cells
    assert np.allclose(mesh.points, points)

    # test pyramid with default points
    mesh = pyvista.Pyramid()
    assert isinstance(mesh, pyvista.UnstructuredGrid)


def test_triangle():
    pointa = [1.0, 1.0, 1.0]
    pointb = [-1.0, 1.0, 1.0]
    pointc = [-1.0, -1.0, 1.0]
    points = np.array([pointa, pointb, pointc])

    mesh = pyvista.Triangle(points)
    assert mesh.n_points
    assert mesh.n_cells
    assert np.allclose(mesh.points, points)


def test_rectangle():
    pointa = [1.0, 1.0, 1.0]
    pointb = [-1.0, 1.0, 1.0]
    pointc = [-1.0, -1.0, 1.0]
    pointd = [1.0, -1.0, 1.0]
    points = np.array([pointa, pointb, pointc, pointd])

    mesh = pyvista.Rectangle(points)
    assert mesh.n_points
    assert mesh.n_cells
    assert np.allclose(mesh.points, points)


def test_circle():
    radius = 1.0

    mesh = pyvista.Circle(radius)
    assert mesh.n_points
    assert mesh.n_cells
    diameter = np.max(mesh.points[:, 0]) - np.min(mesh.points[:, 0])
    assert np.isclose(diameter, radius * 2.0, rtol=1e-3)


@pytest.mark.parametrize(
    'kind_str, kind_int, n_vertices, n_faces',
    zip(
        ['tetrahedron', 'cube', 'octahedron', 'icosahedron', 'dodecahedron'],
        range(5),
        [4, 8, 6, 12, 20],
        [4, 6, 8, 20, 12],
    ),
)
def test_platonic_solids(kind_str, kind_int, n_vertices, n_faces):
    # verify integer mapping
    solid_from_str = pyvista.PlatonicSolid(kind_str)
    solid_from_int = pyvista.PlatonicSolid(kind_int)
    assert solid_from_str == solid_from_int

    # verify type of solid
    assert solid_from_str.n_points == n_vertices
    assert solid_from_str.n_faces == n_faces


def test_platonic_invalids():
    with pytest.raises(ValueError):
        pyvista.PlatonicSolid(kind='invalid')
    with pytest.raises(ValueError):
        pyvista.PlatonicSolid(kind=42)
    with pytest.raises(ValueError):
        pyvista.PlatonicSolid(kind=[])


def test_tetrahedron():
    radius = 1.7
    center = (1, -2, 3)
    solid = pyvista.Tetrahedron(radius=radius, center=center)
    assert solid.n_points == 4
    assert solid.n_faces == 4
    assert np.allclose(solid.center, center)

    doubled_solid = pyvista.Tetrahedron(radius=2 * radius, center=center)
    assert np.isclose(doubled_solid.length, 2 * solid.length)


def test_octahedron():
    radius = 1.7
    center = (1, -2, 3)
    solid = pyvista.Octahedron(radius=radius, center=center)
    assert solid.n_points == 6
    assert solid.n_faces == 8
    assert np.allclose(solid.center, center)

    doubled_solid = pyvista.Octahedron(radius=2 * radius, center=center)
    assert np.isclose(doubled_solid.length, 2 * solid.length)


def test_dodecahedron():
    radius = 1.7
    center = (1, -2, 3)
    solid = pyvista.Dodecahedron(radius=radius, center=center)
    assert solid.n_points == 20
    assert solid.n_faces == 12
    assert np.allclose(solid.center, center)

    doubled_solid = pyvista.Dodecahedron(radius=2 * radius, center=center)
    assert np.isclose(doubled_solid.length, 2 * solid.length)


def test_icosahedron():
    radius = 1.7
    center = (1, -2, 3)
    solid = pyvista.Icosahedron(radius=radius, center=center)
    assert solid.n_points == 12
    assert solid.n_faces == 20
    assert np.allclose(solid.center, center)

    doubled_solid = pyvista.Icosahedron(radius=2 * radius, center=center)
    assert np.isclose(doubled_solid.length, 2 * solid.length)


# tests/test_grid.py

import os
import pathlib
import weakref

import numpy as np
import pytest
import vtk

import pyvista
from pyvista import examples
from pyvista._vtk import VTK9
from pyvista.core.errors import VTKVersionError
from pyvista.errors import AmbiguousDataError, MissingDataError
from pyvista.plotting import system_supports_plotting
from pyvista.utilities.misc import PyVistaDeprecationWarning

test_path = os.path.dirname(os.path.abspath(__file__))

# must be manually set until pytest adds parametrize with fixture feature
HEXBEAM_CELLS_BOOL = np.ones(40, np.bool_)  # matches hexbeam.n_cells == 40
STRUCTGRID_CELLS_BOOL = np.ones(729, np.bool_)  # struct_grid.n_cells == 729
STRUCTGRID_POINTS_BOOL = np.ones(1000, np.bool_)  # struct_grid.n_points == 1000

pointsetmark = pytest.mark.skipif(
    pyvista.vtk_version_info < (9, 1, 0), reason="Requires VTK>=9.1.0 for a concrete PointSet class"
)


def test_volume(hexbeam):
    assert hexbeam.volume > 0.0


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_struct_example():
    # create and plot structured grid
    grid = examples.load_structured()
    grid.plot(off_screen=True)  # basic plot
    grid.plot_curvature(off_screen=True)


def test_init_from_polydata(sphere):
    unstruct_grid = pyvista.UnstructuredGrid(sphere)
    assert unstruct_grid.n_points == sphere.n_points
    assert unstruct_grid.n_cells == sphere.n_cells
    assert np.all(unstruct_grid.celltypes == 5)


def test_init_from_structured(struct_grid):
    unstruct_grid = pyvista.UnstructuredGrid(struct_grid)
    assert unstruct_grid.points.shape[0] == struct_grid.x.size
    assert np.all(unstruct_grid.celltypes == 12)


def test_init_from_unstructured(hexbeam):
    grid = pyvista.UnstructuredGrid(hexbeam, deep=True)
    grid.points += 1
    assert not np.any(grid.points == hexbeam.points)

    grid = pyvista.UnstructuredGrid(hexbeam)
    grid.points += 1
    assert np.array_equal(grid.points, hexbeam.points)


def test_init_from_numpy_arrays():
    offset = np.array([0, 9])
    cells = [[8, 0, 1, 2, 3, 4, 5, 6, 7], [8, 8, 9, 10, 11, 12, 13, 14, 15]]
    cells = np.array(cells).ravel()
    cell_type = np.array([vtk.VTK_HEXAHEDRON, vtk.VTK_HEXAHEDRON])
    cell1 = np.array(
        [
            [0, 0, 0],
            [1, 0, 0],
            [1, 1, 0],
            [0, 1, 0],
            [0, 0, 1],
            [1, 0, 1],
            [1, 1, 1],
            [0, 1, 1],
        ],
        dtype=np.float32,
    )

    cell2 = np.array(
        [
            [0, 0, 2],
            [1, 0, 2],
            [1, 1, 2],
            [0, 1, 2],
            [0, 0, 3],
            [1, 0, 3],
            [1, 1, 3],
            [0, 1, 3],
        ],
        dtype=np.float32,
    )

    points = np.vstack((cell1, cell2))
    if VTK9:
        grid = pyvista.UnstructuredGrid(cells, cell_type, points)
    else:
        grid = pyvista.UnstructuredGrid(offset, cells, cell_type, points)

    assert grid.number_of_points == 16
    assert grid.number_of_cells == 2


def test_init_bad_input():
    with pytest.raises(TypeError):
        pyvista.UnstructuredGrid(np.array(1))

    with pytest.raises(TypeError):
        pyvista.UnstructuredGrid(np.array(1), np.array(1), np.array(1), 'woa')


def create_hex_example():
    cells = np.array([8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15])
    cell_type = np.array([vtk.VTK_HEXAHEDRON, vtk.VTK_HEXAHEDRON], np.int32)

    cell1 = np.array(
        [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]],
        dtype=np.float32,
    )

    cell2 = np.array(
        [[0, 0, 2], [1, 0, 2], [1, 1, 2], [0, 1, 2], [0, 0, 3], [1, 0, 3], [1, 1, 3], [0, 1, 3]],
        dtype=np.float32,
    )

    points = np.vstack((cell1, cell2))
    offset = np.array([0, 9], np.int8)

    return offset, cells, cell_type, points


# Try both with and without an offset array
@pytest.mark.parametrize('specify_offset', [False, True])
def test_init_from_arrays(specify_offset):
    offset, cells, cell_type, points = create_hex_example()

    if VTK9:
        grid = pyvista.UnstructuredGrid(cells, cell_type, points, deep=False)
    else:
        if specify_offset:
            grid = pyvista.UnstructuredGrid(offset, cells, cell_type, points, deep=False)
        else:
            grid = pyvista.UnstructuredGrid(cells, cell_type, points, deep=False)

        assert np.allclose(grid.offset, offset)

    assert grid.n_cells == 2
    assert np.allclose(cells, grid.cells)

    if VTK9:
        assert np.allclose(grid.cell_connectivity, np.arange(16))
    else:
        with pytest.raises(VTKVersionError):
            grid.cell_connectivity


@pytest.mark.parametrize('multiple_cell_types', [False, True])
@pytest.mark.parametrize('flat_cells', [False, True])
def test_init_from_dict(multiple_cell_types, flat_cells):
    # Try mixed construction
    vtk8_offsets, vtk_cell_format, cell_type, points = create_hex_example()

    vtk9_offsets = np.array([0, 8, 16])
    cells_hex = np.array([[0, 1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14, 15]])
    input_cells_dict = {vtk.VTK_HEXAHEDRON: cells_hex}

    if multiple_cell_types:
        cells_quad = np.array([[16, 17, 18, 19]])

        cell3 = np.array([[0, 0, -1], [1, 0, -1], [1, 1, -1], [0, 1, -1]])

        points = np.vstack((points, cell3))
        input_cells_dict[vtk.VTK_QUAD] = cells_quad

        # Update expected vtk cell arrays
        vtk_cell_format = np.concatenate([vtk_cell_format, [4], np.squeeze(cells_quad)])
        vtk8_offsets = np.concatenate([vtk8_offsets, [18]])
        vtk9_offsets = np.concatenate([vtk9_offsets, [20]])
        cell_type = np.concatenate([cell_type, [vtk.VTK_QUAD]])

    if flat_cells:
        input_cells_dict = {k: v.reshape([-1]) for k, v in input_cells_dict.items()}

    grid = pyvista.UnstructuredGrid(input_cells_dict, points, deep=False)

    if VTK9:
        assert np.all(grid.offset == vtk9_offsets)
    else:
        assert np.all(grid.offset == vtk8_offsets)

    assert grid.n_cells == (3 if multiple_cell_types else 2)
    assert np.all(grid.cells == vtk_cell_format)

    if VTK9:
        assert np.allclose(
            grid.cell_connectivity, (np.arange(20) if multiple_cell_types else np.arange(16))
        )
    else:
        with pytest.raises(VTKVersionError):
            grid.cell_connectivity

    # Now fetch the arrays
    output_cells_dict = grid.cells_dict

    assert np.all(
        output_cells_dict[vtk.VTK_HEXAHEDRON].reshape([-1])
        == input_cells_dict[vtk.VTK_HEXAHEDRON].reshape([-1])
    )

    if multiple_cell_types:
        assert np.all(
            output_cells_dict[vtk.VTK_QUAD].reshape([-1])
            == input_cells_dict[vtk.VTK_QUAD].reshape([-1])
        )

    # Test for some errors
    # Invalid index (<0)
    input_cells_dict[vtk.VTK_HEXAHEDRON] -= 1

    with pytest.raises(ValueError):
        pyvista.UnstructuredGrid(input_cells_dict, points, deep=False)

    # Restore
    input_cells_dict[vtk.VTK_HEXAHEDRON] += 1

    # Invalid index (>= nr_points)
    input_cells_dict[vtk.VTK_HEXAHEDRON].flat[0] = points.shape[0]

    with pytest.raises(ValueError):
        pyvista.UnstructuredGrid(input_cells_dict, points, deep=False)

    input_cells_dict[vtk.VTK_HEXAHEDRON] -= 1

    # Incorrect size
    with pytest.raises(ValueError):
        pyvista.UnstructuredGrid(
            {vtk.VTK_HEXAHEDRON: cells_hex.reshape([-1])[:-1]}, points, deep=False
        )

    # Unknown cell type
    with pytest.raises(ValueError):
        pyvista.UnstructuredGrid({255: cells_hex}, points, deep=False)

    # Dynamic sizes cell type
    with pytest.raises(ValueError):
        pyvista.UnstructuredGrid({vtk.VTK_POLYGON: cells_hex.reshape([-1])}, points, deep=False)

    # Non-integer arrays
    with pytest.raises(ValueError):
        pyvista.UnstructuredGrid(
            {vtk.VTK_HEXAHEDRON: cells_hex.reshape([-1])[:-1].astype(np.float32)}, points
        )

    # Invalid point dimensions
    with pytest.raises(ValueError):
        pyvista.UnstructuredGrid(input_cells_dict, points[..., :-1])


def test_cells_dict_hexbeam_file():
    grid = pyvista.UnstructuredGrid(examples.hexbeamfile)
    cells = np.delete(grid.cells, np.arange(0, grid.cells.size, 9)).reshape([-1, 8])

    assert np.all(grid.cells_dict[vtk.VTK_HEXAHEDRON] == cells)


def test_cells_dict_variable_length():
    cells_poly = np.concatenate([[5], np.arange(5)])
    cells_types = np.array([vtk.VTK_POLYGON])
    points = np.random.normal(size=(5, 3))
    grid = pyvista.UnstructuredGrid(cells_poly, cells_types, points)

    # Dynamic sizes cell types are currently unsupported
    with pytest.raises(ValueError):
        grid.cells_dict

    grid.celltypes[:] = 255
    # Unknown cell types
    with pytest.raises(ValueError):
        grid.cells_dict


def test_cells_dict_empty_grid():
    grid = pyvista.UnstructuredGrid()
    assert grid.cells_dict is None


def test_cells_dict_alternating_cells():
    cells = np.concatenate([[4], [1, 2, 3, 4], [3], [0, 1, 2], [4], [0, 1, 5, 6]])
    cells_types = np.array([vtk.VTK_QUAD, vtk.VTK_TRIANGLE, vtk.VTK_QUAD])
    points = np.random.normal(size=(3 + 2 * 2, 3))
    grid = pyvista.UnstructuredGrid(cells, cells_types, points)

    cells_dict = grid.cells_dict

    if VTK9:
        assert np.all(grid.offset == np.array([0, 4, 7, 11]))
    else:
        assert np.all(grid.offset == np.array([0, 5, 9]))

    assert np.all(cells_dict[vtk.VTK_QUAD] == np.array([cells[1:5], cells[-4:]]))
    assert np.all(cells_dict[vtk.VTK_TRIANGLE] == [0, 1, 2])


def test_destructor():
    ugrid = examples.load_hexbeam()
    ref = weakref.ref(ugrid)
    del ugrid
    assert ref() is None


def test_surface_indices(hexbeam):
    surf = hexbeam.extract_surface()
    surf_ind = surf.point_data['vtkOriginalPointIds']
    assert np.allclose(surf_ind, hexbeam.surface_indices())


def test_extract_feature_edges(hexbeam):
    edges = hexbeam.extract_feature_edges(90, progress_bar=True)
    assert edges.n_points

    edges = hexbeam.extract_feature_edges(180, progress_bar=True)
    assert not edges.n_points


def test_triangulate_inplace(hexbeam):
    hexbeam.triangulate(inplace=True)
    assert (hexbeam.celltypes == vtk.VTK_TETRA).all()


@pytest.mark.parametrize('binary', [True, False])
@pytest.mark.parametrize('extension', pyvista.pointset.UnstructuredGrid._WRITERS)
def test_save(extension, binary, tmpdir, hexbeam):
    filename = str(tmpdir.mkdir("tmpdir").join(f'tmp.{extension}'))
    hexbeam.save(filename, binary)

    grid = pyvista.UnstructuredGrid(filename)
    assert grid.cells.shape == hexbeam.cells.shape
    assert grid.points.shape == hexbeam.points.shape

    grid = pyvista.read(filename)
    assert grid.cells.shape == hexbeam.cells.shape
    assert grid.points.shape == hexbeam.points.shape
    assert isinstance(grid, pyvista.UnstructuredGrid)


def test_pathlib_read_write(tmpdir, hexbeam):
    path = pathlib.Path(str(tmpdir.mkdir("tmpdir").join('tmp.vtk')))
    assert not path.is_file()
    hexbeam.save(path)
    assert path.is_file()

    grid = pyvista.UnstructuredGrid(path)
    assert grid.cells.shape == hexbeam.cells.shape
    assert grid.points.shape == hexbeam.points.shape

    grid = pyvista.read(path)
    assert grid.cells.shape == hexbeam.cells.shape
    assert grid.points.shape == hexbeam.points.shape
    assert isinstance(grid, pyvista.UnstructuredGrid)


def test_init_bad_filename():
    filename = os.path.join(test_path, 'test_grid.py')
    with pytest.raises(IOError):
        pyvista.UnstructuredGrid(filename)

    with pytest.raises(FileNotFoundError):
        pyvista.UnstructuredGrid('not a file')


def test_save_bad_extension():
    with pytest.raises(FileNotFoundError):
        pyvista.UnstructuredGrid('file.abc')


def test_linear_copy(hexbeam):
    # need a grid with quadratic cells
    lgrid = hexbeam.linear_copy()
    assert np.all(lgrid.celltypes < 20)


def test_linear_copy_surf_elem():
    cells = np.array([8, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 9, 10, 11, 12, 13], np.int32)
    celltypes = np.array([vtk.VTK_QUADRATIC_QUAD, vtk.VTK_QUADRATIC_TRIANGLE], np.uint8)

    cell0 = [
        [0.0, 0.0, 0.0],
        [1.0, 0.0, 0.0],
        [1.0, 1.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.5, 0.1, 0.0],
        [1.1, 0.5, 0.0],
        [0.5, 0.9, 0.0],
        [0.1, 0.5, 0.0],
    ]

    cell1 = [
        [0.0, 0.0, 1.0],
        [1.0, 0.0, 1.0],
        [0.5, 0.5, 1.0],
        [0.5, 0.0, 1.3],
        [0.7, 0.7, 1.3],
        [0.1, 0.1, 1.3],
    ]

    points = np.vstack((cell0, cell1))
    if VTK9:
        grid = pyvista.UnstructuredGrid(cells, celltypes, points, deep=False)
    else:
        offset = np.array([0, 9])
        grid = pyvista.UnstructuredGrid(offset, cells, celltypes, points, deep=False)

    lgrid = grid.linear_copy()

    qfilter = vtk.vtkMeshQuality()
    qfilter.SetInputData(lgrid)
    qfilter.Update()
    qual = pyvista.wrap(qfilter.GetOutput())['Quality']
    assert np.allclose(qual, [1, 1.4], atol=0.01)


def test_extract_cells(hexbeam):
    ind = [1, 2, 3]
    part_beam = hexbeam.extract_cells(ind)
    assert part_beam.n_cells == len(ind)
    assert part_beam.n_points < hexbeam.n_points
    assert np.allclose(part_beam.cell_data['vtkOriginalCellIds'], ind)

    mask = np.zeros(hexbeam.n_cells, np.bool_)
    mask[ind] = True
    part_beam = hexbeam.extract_cells(mask)
    assert part_beam.n_cells == len(ind)
    assert part_beam.n_points < hexbeam.n_points
    assert np.allclose(part_beam.cell_data['vtkOriginalCellIds'], ind)

    ind = np.vstack(([1, 2], [4, 5]))[:, 0]
    part_beam = hexbeam.extract_cells(ind)


def test_merge(hexbeam):
    grid = hexbeam.copy()
    grid.points[:, 0] += 1
    unmerged = grid.merge(hexbeam, inplace=False, merge_points=False)

    grid.merge(hexbeam, inplace=True, merge_points=True)
    assert grid.n_points > hexbeam.n_points
    assert grid.n_points < unmerged.n_points


def test_merge_not_main(hexbeam):
    grid = hexbeam.copy()
    grid.points[:, 0] += 1
    unmerged = grid.merge(hexbeam, inplace=False, merge_points=False, main_has_priority=False)

    grid.merge(hexbeam, inplace=True, merge_points=True)
    assert grid.n_points > hexbeam.n_points
    assert grid.n_points < unmerged.n_points


def test_merge_list(hexbeam):
    grid_a = hexbeam.copy()
    grid_a.points[:, 0] += 1

    grid_b = hexbeam.copy()
    grid_b.points[:, 1] += 1

    grid_a.merge([hexbeam, grid_b], inplace=True, merge_points=True)
    assert grid_a.n_points > hexbeam.n_points


def test_merge_invalid(hexbeam, sphere):
    with pytest.raises(TypeError):
        sphere.merge([hexbeam], inplace=True)


def test_init_structured_raise():
    with pytest.raises(TypeError, match="Invalid parameters"):
        pyvista.StructuredGrid(['a', 'b', 'c'])
    with pytest.raises(ValueError, match="Too many args"):
        pyvista.StructuredGrid([0, 1], [0, 1], [0, 1], [0, 1])


def test_init_structured(struct_grid):
    xrng = np.arange(-10, 10, 2, dtype=np.float32)
    yrng = np.arange(-10, 10, 2, dtype=np.float32)
    zrng = np.arange(-10, 10, 2, dtype=np.float32)
    x, y, z = np.meshgrid(xrng, yrng, zrng)
    grid = pyvista.StructuredGrid(x, y, z)
    assert np.allclose(struct_grid.x, x)
    assert np.allclose(struct_grid.y, y)
    assert np.allclose(struct_grid.z, z)

    grid_a = pyvista.StructuredGrid(grid, deep=True)
    grid_a.points += 1
    assert not np.any(grid_a.points == grid.points)

    grid_a = pyvista.StructuredGrid(grid)
    grid_a.points += 1
    assert np.array_equal(grid_a.points, grid.points)


@pytest.fixture
def structured_points():
    x = np.arange(-10, 10, 0.25)
    y = np.arange(-10, 10, 0.25)
    x, y = np.meshgrid(x, y)
    r = np.sqrt(x**2 + y**2)
    z = np.sin(r)
    source = np.empty((x.size, 3), x.dtype)
    source[:, 0] = x.ravel('F')
    source[:, 1] = y.ravel('F')
    source[:, 2] = z.ravel('F')
    return source, (*x.shape, 1)


def test_no_copy_polydata_init():
    source = np.random.rand(100, 3)
    mesh = pyvista.PolyData(source)
    pts = mesh.points
    pts /= 2
    assert np.array_equal(mesh.points, pts)
    assert np.may_share_memory(mesh.points, pts)
    assert np.array_equal(mesh.points, source)
    assert np.may_share_memory(mesh.points, source)


def test_no_copy_polydata_points_setter():
    source = np.random.rand(100, 3)
    mesh = pyvista.PolyData()
    mesh.points = source
    pts = mesh.points
    pts /= 2
    assert np.array_equal(mesh.points, pts)
    assert np.may_share_memory(mesh.points, pts)
    assert np.array_equal(mesh.points, source)
    assert np.may_share_memory(mesh.points, source)


def test_no_copy_structured_mesh_init(structured_points):
    source, dims = structured_points
    mesh = pyvista.StructuredGrid(source)
    mesh.dimensions = dims
    pts = mesh.points
    pts /= 2
    assert np.array_equal(mesh.points, pts)
    assert np.may_share_memory(mesh.points, pts)
    assert np.array_equal(mesh.points, source)
    assert np.may_share_memory(mesh.points, source)


def test_no_copy_structured_mesh_points_setter(structured_points):
    source, dims = structured_points
    mesh = pyvista.StructuredGrid()
    mesh.points = source
    mesh.dimensions = dims
    pts = mesh.points
    pts /= 2
    assert np.array_equal(mesh.points, pts)
    assert np.may_share_memory(mesh.points, pts)
    assert np.array_equal(mesh.points, source)
    assert np.may_share_memory(mesh.points, source)


@pointsetmark
def test_no_copy_pointset_init():
    source = np.random.rand(100, 3)
    mesh = pyvista.PointSet(source)
    pts = mesh.points
    pts /= 2
    assert np.array_equal(mesh.points, pts)
    assert np.may_share_memory(mesh.points, pts)
    assert np.array_equal(mesh.points, source)
    assert np.may_share_memory(mesh.points, source)


@pointsetmark
def test_no_copy_pointset_points_setter():
    source = np.random.rand(100, 3)
    mesh = pyvista.PointSet()
    mesh.points = source
    pts = mesh.points
    pts /= 2
    assert np.array_equal(mesh.points, pts)
    assert np.may_share_memory(mesh.points, pts)
    assert np.array_equal(mesh.points, source)
    assert np.may_share_memory(mesh.points, source)


def test_no_copy_unstructured_grid_points_setter():
    source = np.random.rand(100, 3)
    mesh = pyvista.UnstructuredGrid()
    mesh.points = source
    pts = mesh.points
    pts /= 2
    assert np.array_equal(mesh.points, pts)
    assert np.may_share_memory(mesh.points, pts)
    assert np.array_equal(mesh.points, source)
    assert np.may_share_memory(mesh.points, source)


def test_no_copy_rectilinear_grid():
    xrng = np.arange(-10, 10, 2, dtype=float)
    yrng = np.arange(-10, 10, 5, dtype=float)
    zrng = np.arange(-10, 10, 1, dtype=float)
    mesh = pyvista.RectilinearGrid(xrng, yrng, zrng)
    x = mesh.x
    x /= 2
    assert np.array_equal(mesh.x, x)
    assert np.may_share_memory(mesh.x, x)
    assert np.array_equal(mesh.x, xrng)
    assert np.may_share_memory(mesh.x, xrng)
    y = mesh.y
    y /= 2
    assert np.array_equal(mesh.y, y)
    assert np.may_share_memory(mesh.y, y)
    assert np.array_equal(mesh.y, yrng)
    assert np.may_share_memory(mesh.y, yrng)
    z = mesh.z
    z /= 2
    assert np.array_equal(mesh.z, z)
    assert np.may_share_memory(mesh.z, z)
    assert np.array_equal(mesh.z, zrng)
    assert np.may_share_memory(mesh.z, zrng)


def test_grid_repr(struct_grid):
    str_ = str(struct_grid)
    assert 'StructuredGrid' in str_
    assert f'N Points:\t{struct_grid.n_points}\n' in str_

    repr_ = repr(struct_grid)
    assert 'StructuredGrid' in repr_
    assert f'N Points:\t{struct_grid.n_points}\n' in repr_


def test_slice_structured(struct_grid):
    sliced = struct_grid[1, :, 1:3]  # three different kinds of slices
    assert sliced.dimensions == (1, struct_grid.dimensions[1], 2)

    # check that points are in the right place
    assert struct_grid.x[1, :, 1:3].ravel() == pytest.approx(sliced.x.ravel())
    assert struct_grid.y[1, :, 1:3].ravel() == pytest.approx(sliced.y.ravel())
    assert struct_grid.z[1, :, 1:3].ravel() == pytest.approx(sliced.z.ravel())

    with pytest.raises(RuntimeError):
        # fancy indexing error
        struct_grid[[1, 2, 3], :, 1:3]

    with pytest.raises(RuntimeError):
        # incorrect number of dims error
        struct_grid[:, :]


def test_invalid_init_structured():
    xrng = np.arange(-10, 10, 2)
    yrng = np.arange(-10, 10, 2)
    zrng = np.arange(-10, 10, 2)
    x, y, z = np.meshgrid(xrng, yrng, zrng)
    z = z[:, :, :2]
    with pytest.raises(ValueError):
        pyvista.StructuredGrid(x, y, z)


@pytest.mark.parametrize('binary', [True, False])
@pytest.mark.parametrize('extension', pyvista.pointset.StructuredGrid._WRITERS)
def test_save_structured(extension, binary, tmpdir, struct_grid):
    filename = str(tmpdir.mkdir("tmpdir").join(f'tmp.{extension}'))
    struct_grid.save(filename, binary)

    grid = pyvista.StructuredGrid(filename)
    assert grid.x.shape == struct_grid.y.shape
    assert grid.n_cells
    assert grid.points.shape == struct_grid.points.shape

    grid = pyvista.read(filename)
    assert grid.x.shape == struct_grid.y.shape
    assert grid.n_cells
    assert grid.points.shape == struct_grid.points.shape
    assert isinstance(grid, pyvista.StructuredGrid)


def test_load_structured_bad_filename():
    with pytest.raises(FileNotFoundError):
        pyvista.StructuredGrid('not a file')

    filename = os.path.join(test_path, 'test_grid.py')
    with pytest.raises(IOError):
        pyvista.StructuredGrid(filename)


def test_instantiate_by_filename():
    ex = examples

    # actual mapping of example file to datatype
    fname_to_right_type = {
        ex.antfile: pyvista.PolyData,
        ex.planefile: pyvista.PolyData,
        ex.hexbeamfile: pyvista.UnstructuredGrid,
        ex.spherefile: pyvista.PolyData,
        ex.uniformfile: pyvista.UniformGrid,
        ex.rectfile: pyvista.RectilinearGrid,
    }

    # a few combinations of wrong type
    fname_to_wrong_type = {
        ex.antfile: pyvista.UnstructuredGrid,  # actual data is PolyData
        ex.planefile: pyvista.StructuredGrid,  # actual data is PolyData
        ex.rectfile: pyvista.UnstructuredGrid,  # actual data is StructuredGrid
    }

    # load the files into the right types
    for fname, right_type in fname_to_right_type.items():
        data = right_type(fname)
        assert data.n_points > 0

    # load the files into the wrong types
    for fname, wrong_type in fname_to_wrong_type.items():
        with pytest.raises(ValueError):
            data = wrong_type(fname)


def test_create_rectilinear_grid_from_specs():
    # 3D example
    xrng = np.arange(-10, 10, 2)
    yrng = np.arange(-10, 10, 5)
    zrng = np.arange(-10, 10, 1)
    grid = pyvista.RectilinearGrid(xrng)
    assert grid.n_cells == 9
    assert grid.n_points == 10
    grid = pyvista.RectilinearGrid(xrng, yrng)
    assert grid.n_cells == 9 * 3
    assert grid.n_points == 10 * 4
    grid = pyvista.RectilinearGrid(xrng, yrng, zrng)
    assert grid.n_cells == 9 * 3 * 19
    assert grid.n_points == 10 * 4 * 20
    assert grid.bounds == (-10.0, 8.0, -10.0, 5.0, -10.0, 9.0)
    # 2D example
    cell_spacings = np.array([1.0, 1.0, 2.0, 2.0, 5.0, 10.0])
    x_coordinates = np.cumsum(cell_spacings)
    y_coordinates = np.cumsum(cell_spacings)
    grid = pyvista.RectilinearGrid(x_coordinates, y_coordinates)
    assert grid.n_cells == 5 * 5
    assert grid.n_points == 6 * 6
    assert grid.bounds == (1.0, 21.0, 1.0, 21.0, 0.0, 0.0)


def test_create_rectilinear_after_init():
    x = np.array([0, 1, 2])
    y = np.array([0, 5, 8])
    z = np.array([3, 2, 1])
    grid = pyvista.RectilinearGrid()
    grid.x = x
    assert grid.dimensions == (3, 1, 1)
    grid.y = y
    assert grid.dimensions == (3, 3, 1)
    grid.z = z
    assert grid.dimensions == (3, 3, 3)
    assert np.allclose(grid.x, x)
    assert np.allclose(grid.y, y)
    assert np.allclose(grid.z, z)


def test_create_rectilinear_grid_from_file():
    grid = examples.load_rectilinear()
    assert grid.n_cells == 16146
    assert grid.n_points == 18144
    assert grid.bounds == (-350.0, 1350.0, -400.0, 1350.0, -850.0, 0.0)
    assert grid.n_arrays == 1


def test_read_rectilinear_grid_from_file():
    grid = pyvista.read(examples.rectfile)
    assert grid.n_cells == 16146
    assert grid.n_points == 18144
    assert grid.bounds == (-350.0, 1350.0, -400.0, 1350.0, -850.0, 0.0)
    assert grid.n_arrays == 1


def test_read_rectilinear_grid_from_pathlib():
    grid = pyvista.RectilinearGrid(pathlib.Path(examples.rectfile))
    assert grid.n_cells == 16146
    assert grid.n_points == 18144
    assert grid.bounds == (-350.0, 1350.0, -400.0, 1350.0, -850.0, 0.0)
    assert grid.n_arrays == 1


def test_raise_rectilinear_grid_non_unique():
    rng_uniq = np.arange(4.0)
    rng_dupe = np.array([0, 1, 2, 2], dtype=float)
    with pytest.raises(ValueError, match="Array contains duplicate values"):
        pyvista.RectilinearGrid(rng_dupe, check_duplicates=True)
    with pytest.raises(ValueError, match="Array contains duplicate values"):
        pyvista.RectilinearGrid(rng_uniq, rng_dupe, check_duplicates=True)
    with pytest.raises(ValueError, match="Array contains duplicate values"):
        pyvista.RectilinearGrid(rng_uniq, rng_uniq, rng_dupe, check_duplicates=True)


def test_cast_rectilinear_grid():
    grid = pyvista.read(examples.rectfile)
    structured = grid.cast_to_structured_grid()
    assert isinstance(structured, pyvista.StructuredGrid)
    assert structured.n_points == grid.n_points
    assert structured.n_cells == grid.n_cells
    assert np.allclose(structured.points, grid.points)
    for k, v in grid.point_data.items():
        assert np.allclose(structured.point_data[k], v)
    for k, v in grid.cell_data.items():
        assert np.allclose(structured.cell_data[k], v)


def test_create_uniform_grid_from_specs():
    # empty
    grid = pyvista.UniformGrid()

    # create UniformGrid
    dims = (10, 10, 10)
    grid = pyvista.UniformGrid(dims=dims)  # Using default spacing and origin
    assert grid.dimensions == dims
    assert grid.extent == (0, 9, 0, 9, 0, 9)
    assert grid.origin == (0.0, 0.0, 0.0)
    assert grid.spacing == (1.0, 1.0, 1.0)

    # Using default origin
    spacing = (2, 1, 5)
    grid = pyvista.UniformGrid(dims=dims, spacing=spacing)
    assert grid.dimensions == dims
    assert grid.origin == (0.0, 0.0, 0.0)
    assert grid.spacing == spacing
    origin = (10, 35, 50)

    # Everything is specified
    grid = pyvista.UniformGrid(dims=dims, spacing=spacing, origin=origin)
    assert grid.dimensions == dims
    assert grid.origin == origin
    assert grid.spacing == spacing

    # ensure negative spacing is not allowed
    with pytest.raises(ValueError, match="Spacing must be non-negative"):
        grid = pyvista.UniformGrid(dims=dims, spacing=(-1, 1, 1))

    # all args (deprecated)
    with pytest.warns(
        PyVistaDeprecationWarning, match="Behavior of pyvista.UniformGrid has changed"
    ):
        grid = pyvista.UniformGrid(dims, origin, spacing)
        assert grid.dimensions == dims
        assert grid.origin == origin
        assert grid.spacing == spacing

    # just dims (deprecated)
    with pytest.warns(
        PyVistaDeprecationWarning, match="Behavior of pyvista.UniformGrid has changed"
    ):
        grid = pyvista.UniformGrid(dims)
        assert grid.dimensions == dims

    # uniform grid from a uniform grid
    grid = pyvista.UniformGrid(dims=dims, spacing=spacing, origin=origin)
    grid_from_grid = pyvista.UniformGrid(grid)
    assert grid == grid_from_grid

    # and is a copy
    grid.origin = (0, 0, 0)
    assert grid != grid_from_grid


def test_uniform_grid_invald_args():
    with pytest.warns(
        PyVistaDeprecationWarning, match="Behavior of pyvista.UniformGrid has changed"
    ):
        pyvista.UniformGrid((1, 1, 1))

    with pytest.raises(TypeError):
        pyvista.UniformGrid(1)


def test_uniform_setters():
    grid = pyvista.UniformGrid()
    grid.dimensions = (10, 10, 10)
    assert grid.GetDimensions() == (10, 10, 10)
    assert grid.dimensions == (10, 10, 10)
    grid.spacing = (5, 2, 1)
    assert grid.GetSpacing() == (5, 2, 1)
    assert grid.spacing == (5, 2, 1)
    grid.origin = (6, 27.7, 19.8)
    assert grid.GetOrigin() == (6, 27.7, 19.8)
    assert grid.origin == (6, 27.7, 19.8)


def test_create_uniform_grid_from_file():
    grid = examples.load_uniform()
    assert grid.n_cells == 729
    assert grid.n_points == 1000
    assert grid.bounds == (0.0, 9.0, 0.0, 9.0, 0.0, 9.0)
    assert grid.n_arrays == 2
    assert grid.dimensions == (10, 10, 10)


def test_read_uniform_grid_from_file():
    grid = pyvista.read(examples.uniformfile)
    assert grid.n_cells == 729
    assert grid.n_points == 1000
    assert grid.bounds == (0.0, 9.0, 0.0, 9.0, 0.0, 9.0)
    assert grid.n_arrays == 2
    assert grid.dimensions == (10, 10, 10)


def test_read_uniform_grid_from_pathlib():
    grid = pyvista.UniformGrid(pathlib.Path(examples.uniformfile))
    assert grid.n_cells == 729
    assert grid.n_points == 1000
    assert grid.bounds == (0.0, 9.0, 0.0, 9.0, 0.0, 9.0)
    assert grid.n_arrays == 2
    assert grid.dimensions == (10, 10, 10)


def test_cast_uniform_to_structured():
    grid = examples.load_uniform()
    structured = grid.cast_to_structured_grid()
    assert structured.n_points == grid.n_points
    assert structured.n_arrays == grid.n_arrays
    assert structured.bounds == grid.bounds


def test_cast_uniform_to_rectilinear():
    grid = examples.load_uniform()
    rectilinear = grid.cast_to_rectilinear_grid()
    assert rectilinear.n_points == grid.n_points
    assert rectilinear.n_arrays == grid.n_arrays
    assert rectilinear.bounds == grid.bounds


def test_uniform_grid_to_tetrahedra():
    grid = pyvista.UniformGrid(dims=(2, 2, 2))
    ugrid = grid.to_tetrahedra()
    assert ugrid.n_cells == 5


def test_fft_and_rfft(noise_2d):
    grid = pyvista.UniformGrid(dims=(10, 10, 1))
    with pytest.raises(MissingDataError, match='FFT filter requires point scalars'):
        grid.fft()

    grid['cell_data'] = np.arange(grid.n_cells)
    with pytest.raises(MissingDataError, match='FFT filter requires point scalars'):
        grid.fft()

    name = noise_2d.active_scalars_name
    noise_fft = noise_2d.fft()
    assert noise_fft[name].dtype == np.complex128

    full_pass = noise_2d.fft().rfft()
    assert full_pass[name].dtype == np.complex128

    # expect FFT and and RFFT to transform from time --> freq --> time domain
    assert np.allclose(noise_2d['scalars'], full_pass[name].real)
    assert np.allclose(full_pass[name].imag, 0)

    output_scalars_name = 'out_scalars'
    # also, disable active scalars to check if it will be automatically set
    noise_2d.active_scalars_name = None
    noise_fft = noise_2d.fft(output_scalars_name=output_scalars_name)
    assert output_scalars_name in noise_fft.point_data

    noise_fft = noise_2d.fft()
    noise_fft_inactive_scalars = noise_fft.copy()
    noise_fft_inactive_scalars.active_scalars_name = None
    full_pass = noise_fft_inactive_scalars.rfft()
    assert np.allclose(full_pass.active_scalars, noise_fft.rfft().active_scalars)


def test_fft_low_pass(noise_2d):
    name = noise_2d.active_scalars_name
    noise_no_scalars = noise_2d.copy()
    noise_no_scalars.clear_data()
    with pytest.raises(MissingDataError, match='FFT filters require point scalars'):
        noise_no_scalars.low_pass(1, 1, 1)

    noise_too_many_scalars = noise_no_scalars.copy()
    noise_too_many_scalars.point_data.set_array(np.arange(noise_2d.n_points), 'a')
    noise_too_many_scalars.point_data.set_array(np.arange(noise_2d.n_points), 'b')
    with pytest.raises(AmbiguousDataError, match='There are multiple point scalars available'):
        noise_too_many_scalars.low_pass(1, 1, 1)

    with pytest.raises(ValueError, match='must be complex data'):
        noise_2d.low_pass(1, 1, 1)

    out_zeros = noise_2d.fft().low_pass(0, 0, 0)
    assert np.allclose(out_zeros[name][1:], 0)

    out = noise_2d.fft().low_pass(1, 1, 1)
    assert not np.allclose(out[name][1:], 0)


def test_fft_high_pass(noise_2d):
    name = noise_2d.active_scalars_name
    out_zeros = noise_2d.fft().high_pass(100000, 100000, 100000)
    assert np.allclose(out_zeros[name], 0)

    out = noise_2d.fft().high_pass(10, 10, 10)
    assert not np.allclose(out[name], 0)


@pytest.mark.parametrize('binary', [True, False])
@pytest.mark.parametrize('extension', ['.vtk', '.vtr'])
def test_save_rectilinear(extension, binary, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join(f'tmp.{extension}'))
    ogrid = examples.load_rectilinear()
    ogrid.save(filename, binary)
    grid = pyvista.RectilinearGrid(filename)
    assert grid.n_cells == ogrid.n_cells
    assert np.allclose(grid.x, ogrid.x)
    assert np.allclose(grid.y, ogrid.y)
    assert np.allclose(grid.z, ogrid.z)
    assert grid.dimensions == ogrid.dimensions
    grid = pyvista.read(filename)
    assert isinstance(grid, pyvista.RectilinearGrid)
    assert grid.n_cells == ogrid.n_cells
    assert np.allclose(grid.x, ogrid.x)
    assert np.allclose(grid.y, ogrid.y)
    assert np.allclose(grid.z, ogrid.z)
    assert grid.dimensions == ogrid.dimensions


@pytest.mark.parametrize('binary', [True, False])
@pytest.mark.parametrize('extension', ['.vtk', '.vti'])
def test_save_uniform(extension, binary, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join(f'tmp.{extension}'))
    ogrid = examples.load_uniform()
    ogrid.save(filename, binary)
    grid = pyvista.UniformGrid(filename)
    assert grid.n_cells == ogrid.n_cells
    assert grid.origin == ogrid.origin
    assert grid.spacing == ogrid.spacing
    assert grid.dimensions == ogrid.dimensions
    grid = pyvista.read(filename)
    assert isinstance(grid, pyvista.UniformGrid)
    assert grid.n_cells == ogrid.n_cells
    assert grid.origin == ogrid.origin
    assert grid.spacing == ogrid.spacing
    assert grid.dimensions == ogrid.dimensions


def test_grid_points():
    """Test the points methods on UniformGrid and RectilinearGrid"""
    # test creation of 2d grids
    x = y = range(3)
    z = [0]
    xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
    points = np.c_[xx.ravel(order='F'), yy.ravel(order='F'), zz.ravel(order='F')]
    grid = pyvista.UniformGrid()
    with pytest.raises(AttributeError):
        grid.points = points
    grid.origin = (0.0, 0.0, 0.0)
    grid.dimensions = (3, 3, 1)
    grid.spacing = (1, 1, 1)
    assert grid.n_points == 9
    assert grid.n_cells == 4
    assert np.allclose(grid.points, points)

    points = np.array(
        [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]]
    )
    grid = pyvista.UniformGrid()
    grid.dimensions = [2, 2, 2]
    grid.spacing = [1, 1, 1]
    grid.origin = [0.0, 0.0, 0.0]
    assert np.allclose(np.unique(grid.points, axis=0), np.unique(points, axis=0))
    opts = np.c_[grid.x, grid.y, grid.z]
    assert np.allclose(np.unique(opts, axis=0), np.unique(points, axis=0))

    # Now test rectilinear grid
    grid = pyvista.RectilinearGrid()
    with pytest.raises(AttributeError):
        grid.points = points
    x, y, z = np.array([0, 1, 3]), np.array([0, 2.5, 5]), np.array([0, 1])
    xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
    grid.x = x
    grid.y = y
    grid.z = z
    assert grid.dimensions == (3, 3, 2)
    assert np.allclose(grid.meshgrid, (xx, yy, zz))
    assert np.allclose(
        grid.points, np.c_[xx.ravel(order='F'), yy.ravel(order='F'), zz.ravel(order='F')]
    )


def test_grid_extract_selection_points(struct_grid):
    grid = pyvista.UnstructuredGrid(struct_grid)
    sub_grid = grid.extract_points([0])
    assert sub_grid.n_cells == 1

    sub_grid = grid.extract_points(range(100))
    assert sub_grid.n_cells > 1


def test_gaussian_smooth(hexbeam):
    uniform = examples.load_uniform()
    active = uniform.active_scalars_name
    values = uniform.active_scalars

    uniform = uniform.gaussian_smooth(scalars=active)
    assert uniform.active_scalars_name == active
    assert uniform.active_scalars.shape == values.shape
    assert not np.all(uniform.active_scalars == values)
    values = uniform.active_scalars

    uniform = uniform.gaussian_smooth(radius_factor=5, std_dev=1.3)
    assert uniform.active_scalars_name == active
    assert uniform.active_scalars.shape == values.shape
    assert not np.all(uniform.active_scalars == values)


@pytest.mark.parametrize('ind', [range(10), np.arange(10), HEXBEAM_CELLS_BOOL])
def test_remove_cells(ind, hexbeam):
    grid_copy = hexbeam.remove_cells(ind)
    assert grid_copy.n_cells < hexbeam.n_cells


@pytest.mark.parametrize('ind', [range(10), np.arange(10), HEXBEAM_CELLS_BOOL])
def test_remove_cells_not_inplace(ind, hexbeam):
    grid_copy = hexbeam.copy()  # copy to protect
    grid_w_removed = grid_copy.remove_cells(ind)
    assert grid_w_removed.n_cells < hexbeam.n_cells
    assert grid_copy.n_cells == hexbeam.n_cells


def test_remove_cells_invalid(hexbeam):
    grid_copy = hexbeam.copy()
    with pytest.raises(ValueError):
        grid_copy.remove_cells(np.ones(10, np.bool_), inplace=True)


@pytest.mark.parametrize('ind', [range(10), np.arange(10), STRUCTGRID_CELLS_BOOL])
def test_hide_cells(ind, struct_grid):
    struct_grid.hide_cells(ind, inplace=True)
    assert struct_grid.HasAnyBlankCells()

    out = struct_grid.hide_cells(ind, inplace=False)
    assert id(out) != id(struct_grid)
    assert out.HasAnyBlankCells()

    with pytest.raises(ValueError, match='Boolean array size must match'):
        struct_grid.hide_cells(np.ones(10, dtype=np.bool), inplace=True)


@pytest.mark.parametrize('ind', [range(10), np.arange(10), STRUCTGRID_POINTS_BOOL])
def test_hide_points(ind, struct_grid):
    struct_grid.hide_points(ind)
    assert struct_grid.HasAnyBlankPoints()

    with pytest.raises(ValueError, match='Boolean array size must match'):
        struct_grid.hide_points(np.ones(10, dtype=np.bool))


def test_set_extent():
    uni_grid = pyvista.UniformGrid(dims=[10, 10, 10])
    with pytest.raises(ValueError):
        uni_grid.extent = [0, 1]

    extent = [0, 1, 0, 1, 0, 1]
    uni_grid.extent = extent
    assert np.array_equal(uni_grid.extent, extent)


@pytest.mark.needs_vtk9
def test_UnstructuredGrid_cast_to_explicit_structured_grid():
    grid = examples.load_explicit_structured()
    grid = grid.hide_cells(range(80, 120))
    grid = grid.cast_to_unstructured_grid()
    grid = grid.cast_to_explicit_structured_grid()
    assert grid.n_cells == 120
    assert grid.n_points == 210
    assert grid.bounds == (0.0, 80.0, 0.0, 50.0, 0.0, 6.0)
    assert 'BLOCK_I' in grid.cell_data
    assert 'BLOCK_J' in grid.cell_data
    assert 'BLOCK_K' in grid.cell_data
    assert 'vtkGhostType' in grid.cell_data
    assert np.count_nonzero(grid.cell_data['vtkGhostType']) == 40


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_init():
    grid = examples.load_explicit_structured()
    assert isinstance(grid, pyvista.ExplicitStructuredGrid)
    assert grid.n_cells == 120
    assert grid.n_points == 210
    assert grid.bounds == (0.0, 80.0, 0.0, 50.0, 0.0, 6.0)
    assert repr(grid) == str(grid)
    assert 'N Cells' in str(grid)
    assert 'N Points' in str(grid)
    assert 'N Arrays' in str(grid)


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_cast_to_unstructured_grid():
    block_i = np.fromstring(
        '''
        0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0
        1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1
        2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2
        3 0 1 2 3 0 1 2 3
        ''',
        sep=' ',
        dtype=int,
    )

    block_j = np.fromstring(
        '''
        0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4
        4 4 4 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 0 0 0 0 1 1 1 1 2 2 2 2 3 3
        3 3 4 4 4 4 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 0 0 0 0 1 1 1 1 2 2 2
        2 3 3 3 3 4 4 4 4
        ''',
        sep=' ',
        dtype=int,
    )

    block_k = np.fromstring(
        '''
        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
        1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3
        3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5
        5 5 5 5 5 5 5 5 5
        ''',
        sep=' ',
        dtype=int,
    )

    grid = examples.load_explicit_structured()
    grid = grid.cast_to_unstructured_grid()
    assert isinstance(grid, pyvista.UnstructuredGrid)
    assert 'BLOCK_I' in grid.cell_data
    assert 'BLOCK_J' in grid.cell_data
    assert 'BLOCK_K' in grid.cell_data
    assert np.array_equal(grid.cell_data['BLOCK_I'], block_i)
    assert np.array_equal(grid.cell_data['BLOCK_J'], block_j)
    assert np.array_equal(grid.cell_data['BLOCK_K'], block_k)


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_save():
    grid = examples.load_explicit_structured()
    grid = grid.hide_cells(range(80, 120))
    grid.save('grid.vtu')
    grid = pyvista.ExplicitStructuredGrid('grid.vtu')
    assert grid.n_cells == 120
    assert grid.n_points == 210
    assert grid.bounds == (0.0, 80.0, 0.0, 50.0, 0.0, 6.0)
    assert np.count_nonzero(grid.cell_data['vtkGhostType']) == 40
    os.remove('grid.vtu')


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_hide_cells():
    ghost = np.asarray(
        '''
     0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
     0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
     0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
     0  0  0  0  0  0  0  0 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32
    32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32
    '''.split(),
        dtype=np.uint8,
    )

    grid = examples.load_explicit_structured()

    copy = grid.hide_cells(range(80, 120))
    assert isinstance(copy, pyvista.ExplicitStructuredGrid)
    assert 'vtkGhostType' in copy.cell_data
    assert 'vtkGhostType' not in grid.cell_data
    assert np.array_equal(copy.cell_data['vtkGhostType'], ghost)

    out = grid.hide_cells(range(80, 120), inplace=True)
    assert out is grid
    assert 'vtkGhostType' in grid.cell_data
    assert np.array_equal(grid.cell_data['vtkGhostType'], ghost)


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_show_cells():
    grid = examples.load_explicit_structured()
    grid.hide_cells(range(80, 120), inplace=True)

    copy = grid.show_cells()
    assert isinstance(copy, pyvista.ExplicitStructuredGrid)
    assert 'vtkGhostType' in copy.cell_data
    assert np.count_nonzero(copy.cell_data['vtkGhostType']) == 0
    assert np.count_nonzero(grid.cell_data['vtkGhostType']) == 40

    out = grid.show_cells(inplace=True)
    assert out is grid
    assert np.count_nonzero(grid.cell_data['vtkGhostType']) == 0


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_dimensions():
    grid = examples.load_explicit_structured()
    assert isinstance(grid.dimensions, tuple)
    assert isinstance(grid.dimensions[0], int)
    assert len(grid.dimensions) == 3
    assert grid.dimensions == (5, 6, 7)


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_visible_bounds():
    grid = examples.load_explicit_structured()
    grid = grid.hide_cells(range(80, 120))
    assert isinstance(grid.visible_bounds, tuple)
    assert all(isinstance(x, float) for x in grid.visible_bounds)
    assert len(grid.visible_bounds) == 6
    assert grid.visible_bounds == (0.0, 80.0, 0.0, 50.0, 0.0, 4.0)


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_cell_id():
    grid = examples.load_explicit_structured()

    ind = grid.cell_id((3, 4, 0))
    assert np.issubdtype(ind, np.integer)
    assert ind == 19

    ind = grid.cell_id([(3, 4, 0), (3, 2, 1), (1, 0, 2), (2, 3, 2)])
    assert isinstance(ind, np.ndarray)
    assert np.issubdtype(ind.dtype, np.integer)
    assert np.array_equal(ind, [19, 31, 41, 54])


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_cell_coords():
    grid = examples.load_explicit_structured()

    coords = grid.cell_coords(19)
    assert isinstance(coords, tuple)
    assert all(np.issubdtype(c, np.integer) for c in coords)
    assert coords == (3, 4, 0)

    coords = grid.cell_coords((19, 31, 41, 54))
    assert isinstance(coords, np.ndarray)
    assert np.issubdtype(coords.dtype, np.integer)
    assert np.array_equal(coords, [(3, 4, 0), (3, 2, 1), (1, 0, 2), (2, 3, 2)])


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_neighbors():
    grid = examples.load_explicit_structured()

    indices = grid.neighbors(0, rel='topological')
    assert isinstance(indices, list)
    assert all(np.issubdtype(ind, np.integer) for ind in indices)
    assert indices == [1, 4, 20]

    indices = grid.neighbors(0, rel='connectivity')
    assert isinstance(indices, list)
    assert all(np.issubdtype(ind, np.integer) for ind in indices)
    assert indices == [1, 4, 20]

    indices = grid.neighbors(0, rel='geometric')
    assert isinstance(indices, list)
    assert all(np.issubdtype(ind, np.integer) for ind in indices)
    assert indices == [1, 4, 20]


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_compute_connectivity():
    connectivity = np.asarray(
        '''
    42 43 43 41 46 47 47 45 46 47 47 45 46 47 47 45 38 39 39 37 58 59 59 57
    62 63 63 61 62 63 63 61 62 63 63 61 54 55 55 53 58 59 59 57 62 63 63 61
    62 63 63 61 62 63 63 61 54 55 55 53 58 59 59 57 62 63 63 61 62 63 63 61
    62 63 63 61 54 55 55 53 58 59 59 57 62 63 63 61 62 63 63 61 62 63 63 61
    54 55 55 53 26 27 27 25 30 31 31 29 30 31 31 29 30 31 31 29 22 23 23 21
    '''.split(),
        dtype=int,
    )

    grid = examples.load_explicit_structured()
    assert 'ConnectivityFlags' not in grid.cell_data

    copy = grid.compute_connectivity()
    assert isinstance(copy, pyvista.ExplicitStructuredGrid)
    assert 'ConnectivityFlags' in copy.cell_data
    assert 'ConnectivityFlags' not in grid.cell_data
    assert np.array_equal(copy.cell_data['ConnectivityFlags'], connectivity)

    out = grid.compute_connectivity(inplace=True)
    assert out is grid
    assert 'ConnectivityFlags' in grid.cell_data
    assert np.array_equal(grid.cell_data['ConnectivityFlags'], connectivity)


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_compute_connections():
    connections = np.asarray(
        '''
    3 4 4 3 4 5 5 4 4 5 5 4 4 5 5 4 3 4 4 3 4 5 5 4 5 6 6 5 5 6 6 5 5 6 6 5 4
    5 5 4 4 5 5 4 5 6 6 5 5 6 6 5 5 6 6 5 4 5 5 4 4 5 5 4 5 6 6 5 5 6 6 5 5 6
    6 5 4 5 5 4 4 5 5 4 5 6 6 5 5 6 6 5 5 6 6 5 4 5 5 4 3 4 4 3 4 5 5 4 4 5 5
    4 4 5 5 4 3 4 4 3
    '''.split(),
        dtype=int,
    )

    grid = examples.load_explicit_structured()
    assert 'number_of_connections' not in grid.cell_data

    copy = grid.compute_connections()
    assert isinstance(copy, pyvista.ExplicitStructuredGrid)
    assert 'number_of_connections' in copy.cell_data
    assert 'number_of_connections' not in grid.cell_data
    assert np.array_equal(copy.cell_data['number_of_connections'], connections)

    grid.compute_connections(inplace=True)
    assert 'number_of_connections' in grid.cell_data
    assert np.array_equal(grid.cell_data['number_of_connections'], connections)


@pytest.mark.needs_vtk9
def test_ExplicitStructuredGrid_raise_init():
    with pytest.raises(ValueError, match="Too many args"):
        pyvista.ExplicitStructuredGrid(1, 2, True)


def test_copy_no_copy_wrap_object(datasets):
    for dataset in datasets:
        # different dataset types have different copy behavior for points
        # use point data which is common
        dataset["data"] = np.ones(dataset.n_points)
        new_dataset = type(dataset)(dataset)
        new_dataset["data"] += 1
        assert np.array_equal(new_dataset["data"], dataset["data"])

    for dataset in datasets:
        # different dataset types have different copy behavior for points
        # use point data which is common
        dataset["data"] = np.ones(dataset.n_points)
        new_dataset = type(dataset)(dataset, deep=True)
        new_dataset["data"] += 1
        assert not np.any(new_dataset["data"] == dataset["data"])


@pytest.mark.needs_vtk9
def test_copy_no_copy_wrap_object_vtk9(datasets_vtk9):
    for dataset in datasets_vtk9:
        # different dataset tyoes have different copy behavior for points
        # use point data which is common
        dataset["data"] = np.ones(dataset.n_points)
        new_dataset = type(dataset)(dataset)
        new_dataset["data"] += 1
        assert np.array_equal(new_dataset["data"], dataset["data"])

    for dataset in datasets_vtk9:
        # different dataset tyoes have different copy behavior for points
        # use point data which is common
        dataset["data"] = np.ones(dataset.n_points)
        new_dataset = type(dataset)(dataset, deep=True)
        new_dataset["data"] += 1
        assert not np.any(new_dataset["data"] == dataset["data"])


# tests/test_helpers.py

import os

from PIL import Image
import numpy as np
import pytest
import trimesh
import vtk

import pyvista
from pyvista import _vtk
from pyvista.errors import AmbiguousDataError, MissingDataError


def test_wrap_none():
    # check against the "None" edge case
    assert pyvista.wrap(None) is None


def test_wrap_pyvista_ndarray(sphere):
    pd = pyvista.wrap(sphere.points)
    assert isinstance(pd, pyvista.PolyData)


# NOTE: It's not necessary to test all data types here, several of the
# most used ones.  We're just checking that we can wrap VTK data types.
@pytest.mark.parametrize(
    'dtypes',
    [
        (np.float64, _vtk.vtkDoubleArray),
        (np.float32, _vtk.vtkFloatArray),
        (np.int64, _vtk.vtkTypeInt64Array),
        (np.int32, _vtk.vtkTypeInt32Array),
        (np.int8, _vtk.vtkSignedCharArray),
        (np.uint8, _vtk.vtkUnsignedCharArray),
    ],
)
def test_wrap_pyvista_ndarray_vtk(dtypes):
    np_dtype, vtk_class = dtypes
    np_array = np.array([[0, 10, 20], [-10, -200, 0], [0.5, 0.667, 0]], dtype=np_dtype)

    vtk_array = vtk_class()
    vtk_array.SetNumberOfComponents(3)
    vtk_array.SetNumberOfValues(9)
    for i in range(9):
        vtk_array.SetValue(i, np_array.flat[i])

    wrapped = pyvista.wrap(vtk_array)
    assert np.allclose(wrapped, np_array)
    assert wrapped.dtype == np_array.dtype


def test_wrap_trimesh():
    points = [[0, 0, 0], [0, 0, 1], [0, 1, 0]]
    faces = [[0, 1, 2]]
    tmesh = trimesh.Trimesh(points, faces=faces, process=False)
    mesh = pyvista.wrap(tmesh)
    assert isinstance(mesh, pyvista.PolyData)

    assert np.allclose(tmesh.vertices, mesh.points)
    assert np.allclose(tmesh.faces, mesh.faces[1:])


def test_make_tri_mesh(sphere):
    with pytest.raises(ValueError):
        pyvista.make_tri_mesh(sphere.points, sphere.faces)

    with pytest.raises(ValueError):
        pyvista.make_tri_mesh(sphere.points[:, :1], sphere.faces)

    faces = sphere.faces.reshape(-1, 4)[:, 1:]
    mesh = pyvista.make_tri_mesh(sphere.points, faces)

    assert np.allclose(sphere.points, mesh.points)
    assert np.allclose(sphere.faces, mesh.faces)


def test_wrappers():
    vtk_data = vtk.vtkPolyData()
    pv_data = pyvista.wrap(vtk_data)
    assert isinstance(pv_data, pyvista.PolyData)

    class Foo(pyvista.PolyData):
        """A user defined subclass of pyvista.PolyData."""

        pass

    default_wrappers = pyvista._wrappers.copy()
    # Use try...finally to set and reset _wrappers
    try:
        pyvista._wrappers['vtkPolyData'] = Foo

        pv_data = pyvista.wrap(vtk_data)
        assert isinstance(pv_data, Foo)

        tri_data = pv_data.delaunay_2d()

        assert isinstance(tri_data, Foo)

        uniform_grid = pyvista.UniformGrid()
        surface = uniform_grid.extract_surface()

        assert isinstance(surface, Foo)

        surface.delaunay_2d(inplace=True)
        assert isinstance(surface, Foo)

        sphere = pyvista.Sphere()
        assert isinstance(sphere, Foo)

        circle = pyvista.Circle()
        assert isinstance(circle, Foo)

    finally:
        pyvista._wrappers = default_wrappers  # always reset back to default


def test_inheritance_no_wrappers():
    class Foo(pyvista.PolyData):
        pass

    # inplace operations do not change type
    mesh = Foo(pyvista.Sphere())
    mesh.decimate(0.5, inplace=True)
    assert isinstance(mesh, Foo)

    # without using _wrappers, we need to explicitly handle inheritance
    mesh = Foo(pyvista.Sphere())
    new_mesh = mesh.decimate(0.5)
    assert isinstance(new_mesh, pyvista.PolyData)
    foo_new_mesh = Foo(new_mesh)
    assert isinstance(foo_new_mesh, Foo)


def test_skybox(tmpdir):
    path = str(tmpdir.mkdir("tmpdir"))
    sets = ['posx', 'negx', 'posy', 'negy', 'posz', 'negz']
    filenames = []
    for suffix in sets:
        image = Image.new('RGB', (10, 10))
        filename = os.path.join(path, suffix + '.jpg')
        image.save(filename)
        filenames.append(filename)

    skybox = pyvista.cubemap(path)
    assert isinstance(skybox, pyvista.Texture)

    with pytest.raises(FileNotFoundError, match='Unable to locate'):
        pyvista.cubemap('')

    skybox = pyvista.cubemap_from_filenames(filenames)
    assert isinstance(skybox, pyvista.Texture)

    with pytest.raises(ValueError, match='must contain 6 paths'):
        pyvista.cubemap_from_filenames(image_paths=['/path'])


def test_numpy_to_texture():
    tex_im = np.ones((1024, 1024, 3), dtype=np.float64) * 255
    with pytest.warns(UserWarning, match='np.uint8'):
        tex = pyvista.numpy_to_texture(tex_im)
    assert isinstance(tex, pyvista.Texture)
    assert tex.to_array().dtype == np.uint8


def test_array_association():
    # TODO: cover vtkTable/ROW association case
    mesh = pyvista.PolyData()
    FieldAssociation = pyvista.FieldAssociation

    # single match cases
    mesh.point_data['p'] = []
    mesh.cell_data['c'] = []
    mesh.field_data['f'] = ['foo']
    for preference in 'point', 'cell', 'field':
        assoc = mesh.get_array_association('p', preference=preference)
        assert assoc == FieldAssociation.POINT
        assoc = mesh.get_array_association('c', preference=preference)
        assert assoc == FieldAssociation.CELL
        assoc = mesh.get_array_association('f', preference=preference)
        assert assoc == FieldAssociation.NONE

    # multiple match case
    mesh.point_data['common'] = []
    mesh.cell_data['common'] = []
    mesh.field_data['common'] = ['foo']
    assoc = mesh.get_array_association('common', preference='point')
    assert assoc == FieldAssociation.POINT
    assoc = mesh.get_array_association('common', preference='cell')
    assert assoc == FieldAssociation.CELL
    assoc = mesh.get_array_association('common', preference='field')
    assert assoc == FieldAssociation.NONE

    # regression test against overly suggestive preference
    mesh.clear_cell_data()  # point and field left
    assoc = mesh.get_array_association('common', 'cell')
    assert assoc != FieldAssociation.CELL

    # missing cases
    mesh.clear_data()
    with pytest.raises(KeyError, match='not present in this dataset.'):
        assoc = mesh.get_array_association('missing')
    assoc = pyvista.get_array_association(mesh, 'missing', err=False)
    assert assoc == FieldAssociation.NONE

    with pytest.raises(ValueError, match='not supported.'):
        mesh.get_array_association('name', preference='row')


def test_set_default_active_vectors():
    mesh = pyvista.Sphere()
    mesh.clear_data()  # make sure we have a clean mesh with no arrays to start

    assert mesh.active_vectors_name is None

    # Point data vectors
    mesh["vec_point"] = np.ones((mesh.n_points, 3))
    pyvista.set_default_active_vectors(mesh)
    assert mesh.active_vectors_name == "vec_point"
    mesh.clear_data()

    # Cell data vectors
    mesh["vec_cell"] = np.ones((mesh.n_cells, 3))
    pyvista.set_default_active_vectors(mesh)
    assert mesh.active_vectors_name == "vec_cell"
    mesh.clear_data()

    # Raises if no data is present
    with pytest.raises(MissingDataError):
        pyvista.set_default_active_vectors(mesh)
    assert mesh.active_vectors_name is None

    # Raises if no vector-like data is present
    mesh["scalar_data"] = np.ones((mesh.n_points, 1))
    with pytest.raises(MissingDataError):
        pyvista.set_default_active_vectors(mesh)
    assert mesh.active_vectors_name is None
    mesh.clear_data()

    # Raises if multiple vector-like data is present
    mesh["vec_data1"] = np.ones((mesh.n_points, 3))
    mesh["vec_data2"] = np.ones((mesh.n_points, 3))
    with pytest.raises(AmbiguousDataError):
        pyvista.set_default_active_vectors(mesh)
    assert mesh.active_vectors_name is None
    mesh.clear_data()

    # Raises if multiple vector-like data in cell and point
    mesh["vec_data1"] = np.ones((mesh.n_points, 3))
    mesh["vec_data2"] = np.ones((mesh.n_cells, 3))
    with pytest.raises(AmbiguousDataError):
        pyvista.set_default_active_vectors(mesh)
    assert mesh.active_vectors_name is None

    # Raises if multiple vector-like data with same name
    mesh["vec_data"] = np.ones((mesh.n_points, 3))
    mesh["vec_data"] = np.ones((mesh.n_cells, 3))
    with pytest.raises(AmbiguousDataError):
        pyvista.set_default_active_vectors(mesh)
    assert mesh.active_vectors_name is None


def test_set_default_active_scalarrs():
    mesh = pyvista.Sphere()
    mesh.clear_data()  # make sure we have a clean mesh with no arrays to start

    assert mesh.active_scalars_name is None

    # Point data scalars
    mesh["scalar_point"] = np.ones(mesh.n_points)
    mesh.set_active_scalars(None)
    pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name == "scalar_point"
    mesh.clear_data()

    # Cell data scalars
    mesh["scalar_cell"] = np.ones(mesh.n_cells)
    mesh.set_active_scalars(None)
    pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name == "scalar_cell"
    mesh.clear_data()

    # Point data scalars multidimensional
    mesh["scalar_point"] = np.ones((mesh.n_points, 3))
    mesh.set_active_scalars(None)
    pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name == "scalar_point"
    mesh.clear_data()

    # Cell data scalars multidimensional
    mesh["scalar_cell"] = np.ones((mesh.n_cells, 3))
    mesh.set_active_scalars(None)
    pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name == "scalar_cell"
    mesh.clear_data()

    # Raises if no data is present
    with pytest.raises(MissingDataError):
        pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name is None

    # Raises if multiple scalar-like data is present
    mesh["scalar_data1"] = np.ones(mesh.n_points)
    mesh["scalar_data2"] = np.ones(mesh.n_points)
    mesh.set_active_scalars(None)
    with pytest.raises(AmbiguousDataError):
        pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name is None
    mesh.clear_data()

    # Raises if multiple scalar-like data in cell and point
    mesh["scalar_data1"] = np.ones(mesh.n_points)
    mesh["scalar_data2"] = np.ones(mesh.n_cells)
    mesh.set_active_scalars(None)
    with pytest.raises(AmbiguousDataError):
        pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name is None

    # Raises if multiple scalar-like data with same name
    mesh["scalar_data"] = np.ones(mesh.n_points)
    mesh["scalar_data"] = np.ones(mesh.n_cells)
    mesh.set_active_scalars(None)
    with pytest.raises(AmbiguousDataError):
        pyvista.set_default_active_scalars(mesh)
    assert mesh.active_scalars_name is None


def test_vtk_points_deep_shallow():

    points = np.array([[0.0, 0.0, 0.0]])
    vtk_points = pyvista.vtk_points(points, deep=False)

    assert vtk_points.GetNumberOfPoints() == 1
    assert np.array_equal(vtk_points.GetPoint(0), points[0])

    # test shallow copy
    vtk_points.SetPoint(0, [1.0, 1.0, 1.0])

    assert np.array_equal(vtk_points.GetPoint(0), points[0])
    assert np.array_equal(vtk_points.GetPoint(0), [1.0, 1.0, 1.0])

    # test deep copy

    points = np.array([[0.0, 0.0, 0.0]])
    vtk_points = pyvista.vtk_points(points, deep=True)

    vtk_points.SetPoint(0, [1.0, 1.0, 1.0])

    assert not np.array_equal(vtk_points.GetPoint(0), points[0])
    assert np.array_equal(points[0], [0.0, 0.0, 0.0])

    # TODO: test force_float


# tests/test_imaging.py

import os

import numpy as np
from numpy.testing import assert_allclose
import pytest

import pyvista as pv


def test_perlin_noise():
    amp = 10
    freq = (3, 2, 1)
    phase = (0, 0, 0)
    perlin = pv.perlin_noise(amp, freq, phase)

    assert_allclose(perlin.GetAmplitude(), amp)
    assert_allclose(perlin.GetFrequency(), freq)
    assert_allclose(perlin.GetPhase(), phase)


@pytest.mark.parametrize(
    'dtype',
    [
        np.float64,
        np.float32,
        np.int64,
        np.uint64,
        np.int32,
        np.uint32,
        np.int16,
        np.uint16,
        np.int8,
        np.uint8,
    ],
)
def test_sample_function(dtype):
    perlin = pv.perlin_noise(0.1, (1, 1, 1), (0, 0, 0))
    bounds = (0, 2, 0, 1, -4, 4)
    dim = (5, 10, 20)
    scalar_arr_name = 'my_scalars'

    if os.name == 'nt' and dtype in [np.int64, np.uint64]:
        with pytest.raises(ValueError):
            pv.sample_function(perlin, output_type=dtype)
    else:
        mesh = pv.sample_function(
            perlin,
            bounds,
            dim,
            compute_normals=False,
            output_type=dtype,
            scalar_arr_name=scalar_arr_name,
        )

        assert_allclose(mesh.dimensions, dim)
        assert_allclose(mesh.bounds, bounds)
        assert mesh[scalar_arr_name].dtype == dtype


# tests/test_init.py

import os
import sys

import pytest
import vtk

import pyvista

developer_note = """
vtk has been directly imported in vtk>=9
Please see:
https://github.com/pyvista/pyvista/pull/1163
"""


@pytest.mark.skipif(not pyvista._vtk.VTK9, reason='``vtk`` can be loaded directly on vtk<9')
def test_vtk_not_loaded():
    """This test verifies that the vtk module isn't loaded when using vtk>=9

    We use ``os.system`` because we need to test the import of pyvista
    outside of the pytest unit test framework as pytest loads vtk.

    """
    exe_str = "import pyvista; import sys; assert 'vtk' not in sys.modules"

    # anything other than 0 indicates an error
    assert not os.system(f'{sys.executable} -c "{exe_str}"'), developer_note


# validate all lazy loads
lazy_readers = [
    'vtkGL2PSExporter',
    'vtkFacetReader',
    'vtkPDataSetReader',
    'vtkMultiBlockPLOT3DReader',
    'vtkPlot3DMetaReader',
]

if pyvista._vtk.VTK9:
    lazy_readers.append('vtkSegYReader')


@pytest.mark.parametrize("cls_", lazy_readers)
def test_lazy_loads(cls_):
    lazy_class = getattr(pyvista._vtk, 'lazy_' + cls_)()
    actual_class = getattr(vtk, cls_)()

    # can't use isinstance here because these are classes
    assert type(lazy_class) == type(actual_class)


# tests/test_lights.py

import numpy as np
import pytest
import vtk

import pyvista

# pyvista attr -- value -- vtk name triples:
configuration = [
    ('light_type', pyvista.Light.CAMERA_LIGHT, 'SetLightType'),  # resets transformation!
    ('position', (1, 1, 1), 'SetPosition'),
    ('focal_point', (2, 2, 2), 'SetFocalPoint'),
    ('ambient_color', (1.0, 0.0, 0.0), 'SetAmbientColor'),
    ('diffuse_color', (0.0, 1.0, 0.0), 'SetDiffuseColor'),
    ('specular_color', (0.0, 0.0, 1.0), 'SetSpecularColor'),
    ('intensity', 0.5, 'SetIntensity'),
    ('on', False, 'SetSwitch'),
    ('positional', True, 'SetPositional'),
    ('exponent', 1.5, 'SetExponent'),
    ('cone_angle', 45, 'SetConeAngle'),
    ('attenuation_values', (3, 2, 1), 'SetAttenuationValues'),
    ('transform_matrix', np.arange(4 * 4).reshape(4, 4), 'SetTransformMatrix'),
    ('shadow_attenuation', 0.5, 'SetShadowAttenuation'),
]


def test_init():
    position = (1, 1, 1)
    focal_point = (2, 2, 2)
    color = (0.5, 0.5, 0.5)
    light_type = 'headlight'
    cone_angle = 15
    intensity = 2
    exponent = 1.5
    positional = True
    show_actor = False
    shadow_attenuation = 0.5
    light = pyvista.Light(
        position=position,
        focal_point=focal_point,
        color=color,
        light_type=light_type,
        cone_angle=cone_angle,
        intensity=intensity,
        exponent=exponent,
        show_actor=show_actor,
        positional=positional,
        shadow_attenuation=shadow_attenuation,
    )
    assert isinstance(light, pyvista.Light)
    assert light.position == position
    assert light.focal_point == focal_point
    assert light.ambient_color == color
    assert light.diffuse_color == color
    assert light.specular_color == color
    assert light.light_type == light.HEADLIGHT
    assert light.cone_angle == cone_angle
    assert light.intensity == intensity
    assert light.exponent == exponent
    assert light.positional == positional
    assert light.actor.GetVisibility() == show_actor
    assert light.shadow_attenuation == shadow_attenuation

    # check repr too
    assert repr(light) is not None


def test_eq():
    light = pyvista.Light()
    other = pyvista.Light()
    for light_now in light, other:
        for name, value, _ in configuration:
            setattr(light_now, name, value)

    assert light == other

    # check that changing anything will break equality
    for name, value, _ in configuration:
        original_value = getattr(other, name)
        restore_transform = False
        if value is pyvista.Light.CAMERA_LIGHT:
            changed_value = pyvista.Light.HEADLIGHT
            restore_transform = True
        elif isinstance(value, bool):
            changed_value = not value
        elif isinstance(value, (int, float)):
            changed_value = 0
        elif isinstance(value, tuple):
            changed_value = (0.5, 0.5, 0.5)
        else:
            # transform_matrix; value is an ndarray
            changed_value = -value
        setattr(other, name, changed_value)
        assert light != other
        setattr(other, name, original_value)
        if restore_transform:
            # setting the light type cleared the transform
            other.transform_matrix = light.transform_matrix

    # sanity check that we managed to restore the original state
    assert light == other

    # check None vs transform_matrix case
    other.transform_matrix = None
    assert light != other


def test_copy():
    light = pyvista.Light()
    for name, value, _ in configuration:
        setattr(light, name, value)

    deep = light.copy()
    assert deep == light
    assert deep.transform_matrix is not light.transform_matrix
    shallow = light.copy(deep=False)
    assert shallow == light
    assert shallow.transform_matrix is light.transform_matrix


def test_colors():
    light = pyvista.Light()

    color = (0.0, 1.0, 0.0)
    light.diffuse_color = color
    assert light.diffuse_color == color
    color = (0.0, 0.0, 1.0)
    light.specular_color = color
    assert light.specular_color == color
    color = (1.0, 0.0, 0.0)
    light.ambient_color = color
    assert light.ambient_color == color

    # test whether strings raise but don't test the result
    for valid in 'white', 'r', '#c0ffee':
        light.diffuse_color = valid
        light.specular_color = valid
        light.ambient_color = valid
    with pytest.raises(ValueError):
        light.diffuse_color = 'invalid'
    with pytest.raises(ValueError):
        light.specular_color = 'invalid'
    with pytest.raises(ValueError):
        light.ambient_color = 'invalid'


def test_positioning():
    light = pyvista.Light()

    position = (1, 1, 1)
    light.position = position
    assert light.position == position
    # with no transformation matrix this is also the world position
    assert light.world_position == position

    focal_point = (2, 2, 2)
    light.focal_point = focal_point
    assert light.focal_point == focal_point
    # with no transformation matrix this is also the world focal point
    assert light.world_focal_point == focal_point

    elev, azim = (30, 60)
    expected_position = (np.sqrt(3) / 2 * 1 / 2, np.sqrt(3) / 2 * np.sqrt(3) / 2, 1 / 2)
    light.positional = True
    light.set_direction_angle(elev, azim)
    assert not light.positional
    assert light.focal_point == (0, 0, 0)
    assert np.allclose(light.position, expected_position)

    with pytest.raises(AttributeError):
        light.world_position = position
    with pytest.raises(AttributeError):
        light.world_focal_point = focal_point


def test_transforms():
    position = (1, 2, 3)
    focal_point = (4, 5, 6)
    light = pyvista.Light(position=position)
    light.focal_point = focal_point

    trans_array = np.arange(4 * 4).reshape(4, 4)
    trans_matrix = pyvista.vtkmatrix_from_array(trans_array)

    assert light.transform_matrix is None
    light.transform_matrix = trans_array
    assert isinstance(light.transform_matrix, vtk.vtkMatrix4x4)
    array = pyvista.array_from_vtkmatrix(light.transform_matrix)
    assert np.array_equal(array, trans_array)
    light.transform_matrix = trans_matrix
    matrix = light.transform_matrix
    assert all(
        matrix.GetElement(i, j) == trans_matrix.GetElement(i, j) for i in range(4) for j in range(4)
    )

    linear_trans = trans_array[:-1, :-1]
    shift = trans_array[:-1, -1]
    assert light.position == position
    assert np.allclose(light.world_position, linear_trans @ position + shift)
    assert light.focal_point == focal_point
    assert np.allclose(light.world_focal_point, linear_trans @ focal_point + shift)

    with pytest.raises(ValueError):
        light.transform_matrix = 'invalid'


def test_intensity():
    light = pyvista.Light()

    intensity = 0.5
    light.intensity = intensity
    assert light.intensity == intensity


def test_switch_state():
    light = pyvista.Light()

    light.switch_on()
    assert light.on
    light.switch_off()
    assert not light.on
    light.on = False
    assert not light.on


def test_positional():
    light = pyvista.Light()

    # default is directional light
    assert not light.positional
    light.positional = True
    assert light.positional
    light.positional = False
    assert not light.positional


def test_shape():
    light = pyvista.Light()

    exponent = 1.5
    light.exponent = exponent
    assert light.exponent == exponent

    cone_angle = 45
    light.cone_angle = cone_angle
    assert light.cone_angle == cone_angle

    attenuation_values = (3, 2, 1)
    light.attenuation_values = attenuation_values
    assert light.attenuation_values == attenuation_values


@pytest.mark.parametrize(
    'int_code,enum_code',
    [
        (1, pyvista.Light.HEADLIGHT),
        (2, pyvista.Light.CAMERA_LIGHT),
        (3, pyvista.Light.SCENE_LIGHT),
    ],
)
def test_type_properties(int_code, enum_code):
    light = pyvista.Light()

    # test that the int and enum codes match up
    assert int_code == enum_code

    # test that both codes work
    light.light_type = int_code
    assert light.light_type == int_code
    light.light_type = enum_code
    assert light.light_type == enum_code


def test_type_setters():
    light = pyvista.Light()

    light.set_headlight()
    assert light.is_headlight
    light.set_camera_light()
    assert light.is_camera_light
    light.set_scene_light()
    assert light.is_scene_light


def test_type_invalid():
    with pytest.raises(TypeError):
        light = pyvista.Light(light_type=['invalid'])
    with pytest.raises(ValueError):
        light = pyvista.Light(light_type='invalid')

    light = pyvista.Light()

    with pytest.raises(TypeError):
        light.light_type = ['invalid']


def test_from_vtk():
    vtk_light = vtk.vtkLight()

    # set the vtk light
    for _, value, vtkname in configuration:
        vtk_setter = getattr(vtk_light, vtkname)
        if isinstance(value, np.ndarray):
            # we can't pass the array to vtkLight directly
            value = pyvista.vtkmatrix_from_array(value)
        vtk_setter(value)
    light = pyvista.Light.from_vtk(vtk_light)
    for pvname, value, _ in configuration:
        if isinstance(value, np.ndarray):
            trans_arr = pyvista.array_from_vtkmatrix(getattr(light, pvname))
            assert np.array_equal(trans_arr, value)
        else:
            assert getattr(light, pvname) == value

    # invalid case
    with pytest.raises(TypeError):
        pyvista.Light.from_vtk('invalid')
    with pytest.raises(TypeError):
        pyvista.Light('invalid')


def test_add_vtk_light():
    pl = pyvista.Plotter(lighting=None)
    pl.add_light(vtk.vtkLight())
    assert len(pl.renderer.lights) == 1


def test_actors():
    light = pyvista.Light()
    actor = light.actor

    # test showing
    assert not actor.GetVisibility()
    light.show_actor()
    assert not actor.GetVisibility()
    light.positional = True
    light.show_actor()
    assert actor.GetVisibility()

    # test hiding
    light.positional = False
    assert not actor.GetVisibility()
    light.positional = True
    light.show_actor()
    assert actor.GetVisibility()
    light.hide_actor()
    assert not actor.GetVisibility()
    light.show_actor()
    light.cone_angle = 90
    assert not actor.GetVisibility()


# tests/test_mapper.py

import pytest
import vtk

import pyvista as pv
from pyvista.plotting.mapper import DataSetMapper


@pytest.fixture()
def dataset_mapper(sphere):
    pl = pv.Plotter()
    pl.add_mesh(sphere)
    return pl.mapper


def test_init(sphere):
    mapper = DataSetMapper()
    mapper.dataset = sphere
    assert mapper.dataset is sphere


def test_scalar_range(dataset_mapper):
    assert isinstance(dataset_mapper.scalar_range, tuple)
    rng = (0, 2)
    dataset_mapper.scalar_range = rng
    assert dataset_mapper.scalar_range == rng


def test_lookup_table(dataset_mapper):
    assert isinstance(dataset_mapper.lookup_table, vtk.vtkLookupTable)

    table = vtk.vtkLookupTable()

    dataset_mapper.lookup_table = table
    assert dataset_mapper.lookup_table is table


def test_interpolate_before_map(dataset_mapper):
    assert isinstance(dataset_mapper.interpolate_before_map, bool)
    dataset_mapper.interpolate_before_map = True
    assert dataset_mapper.interpolate_before_map is True


def test_color_mode(dataset_mapper):
    assert isinstance(dataset_mapper.color_mode, str)

    dataset_mapper.color_mode = 'direct'
    assert dataset_mapper.color_mode == 'direct'

    dataset_mapper.color_mode = 'map'
    assert dataset_mapper.color_mode == 'map'


def test_set_scalars(dataset_mapper):
    scalars = dataset_mapper.dataset.points[:, 2]
    n_colors = 128
    dataset_mapper.set_scalars(scalars, 'z', n_colors=n_colors)
    assert dataset_mapper.lookup_table.GetNumberOfTableValues() == n_colors


# tests/test_meshio.py

import pathlib

import meshio
import numpy as np
import pytest

import pyvista
from pyvista import examples

beam = pyvista.UnstructuredGrid(examples.hexbeamfile)
airplane = examples.load_airplane().cast_to_unstructured_grid()
uniform = examples.load_uniform().cast_to_unstructured_grid()
mesh2d = meshio.Mesh(
    points=[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
    cells=[("triangle", [[0, 1, 2], [1, 3, 2]])],
)


@pytest.mark.parametrize("mesh_in", [beam, airplane, uniform, mesh2d])
def test_meshio(mesh_in, tmpdir):
    if isinstance(mesh_in, meshio.Mesh):
        mesh_in = pyvista.from_meshio(mesh_in)

    # Save and read reference mesh using meshio
    filename = tmpdir.mkdir("tmpdir").join("test_mesh.vtk")
    pyvista.save_meshio(filename, mesh_in)
    mesh = pyvista.read_meshio(filename)

    # Assert mesh is still the same
    assert np.allclose(mesh_in.points, mesh.points)
    if (mesh_in.celltypes == 11).all():
        cells = mesh_in.cells.reshape((mesh_in.n_cells, 9))[:, [0, 1, 2, 4, 3, 5, 6, 8, 7]].ravel()
        assert np.allclose(cells, mesh.cells)
    else:
        assert np.allclose(mesh_in.cells, mesh.cells)
    for k, v in mesh_in.point_data.items():
        assert np.allclose(v, mesh.point_data[k.replace(" ", "_")])
    for k, v in mesh_in.cell_data.items():
        assert np.allclose(v, mesh.cell_data[k.replace(" ", "_")])


def test_pathlib_read_write(tmpdir, sphere):
    path = pathlib.Path(str(tmpdir.mkdir("tmpdir").join('tmp.vtk')))
    pyvista.save_meshio(path, sphere)
    assert path.is_file()

    mesh = pyvista.read_meshio(path)
    assert isinstance(mesh, pyvista.UnstructuredGrid)
    assert mesh.points.shape == sphere.points.shape


def test_file_format():
    from meshio._exceptions import ReadError, WriteError

    with pytest.raises(ReadError):
        _ = pyvista.read_meshio(examples.hexbeamfile, file_format="bar")

    with pytest.raises((KeyError, WriteError)):
        pyvista.save_meshio("foo.bar", beam, file_format="bar")

    with pytest.raises((KeyError, WriteError)):
        pyvista.save_meshio("foo.npy", beam, file_format="npy")


# tests/test_meta.py

"""Meta-tests for the test suite.

Module for tests that test the test setup itself, and in general
anything that's beyond testing actual library code.

"""


def test_mpl_backend():
    """Check if the backend is correctly set for testing."""
    # only fail if matplotlib is otherwise available
    try:
        import matplotlib
    except ImportError:
        return

    assert matplotlib.get_backend() == 'agg'


# tests/test_objects.py

"""
Tests for non-spatially referenced objects
"""
import numpy as np
import pytest
import vtk

import pyvista
from pyvista import examples

try:
    import pandas as pd
except ImportError:
    pd = None


def test_table_init(tmpdir):
    """Save some delimited text to a file and read it"""
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.csv'))
    nr, nc = 50, 3
    arrays = np.random.rand(nr, nc)

    # Create from 2D array
    table = pyvista.Table(arrays)
    assert table.n_rows == nr
    assert table.n_columns == nc
    assert table.n_arrays == nc

    assert len(table.row_arrays) == nc
    for i in range(nc):
        assert np.allclose(arrays[:, i], table[i])

    with pytest.raises(ValueError):
        pyvista.Table(np.random.rand(100, 2, 3))

    # Create from 1D array
    table = pyvista.Table(arrays[:, 0])
    assert table.n_rows == nr
    assert table.n_columns == 1

    assert len(table.row_arrays) == 1
    assert np.allclose(arrays[:, 0], table[0])

    # create from dictionary
    array_dict = {}
    for i in range(nc):
        array_dict[f'foo{i}'] = arrays[:, i].copy()
    table = pyvista.Table(array_dict)
    assert table.n_rows == nr
    assert table.n_columns == nc

    assert len(table.row_arrays) == nc
    for i in range(nc):
        assert np.allclose(arrays[:, i], table[f'foo{i}'])

    dataset = examples.load_hexbeam()
    array_dict = dict(dataset.point_data)
    table = pyvista.Table(array_dict)
    assert table.n_rows == dataset.n_points
    assert table.n_columns == len(array_dict)

    assert len(table.row_arrays) == len(array_dict)
    for name in table.keys():
        assert np.allclose(dataset[name], table[name])

    # Create from vtkTable object
    h = '\t'.join([f'a{i}' for i in range(nc)])
    np.savetxt(filename, arrays, delimiter='\t', header=h, comments='')

    reader = vtk.vtkDelimitedTextReader()
    reader.SetFileName(filename)
    reader.DetectNumericColumnsOn()
    reader.SetFieldDelimiterCharacters('\t')
    reader.SetHaveHeaders(True)
    reader.Update()

    # Test init
    table = pyvista.Table(reader.GetOutput(), deep=True)
    assert isinstance(table, vtk.vtkTable)
    assert isinstance(table, pyvista.Table)

    table = pyvista.Table(reader.GetOutput(), deep=False)
    assert isinstance(table, vtk.vtkTable)
    assert isinstance(table, pyvista.Table)

    # Test wrap
    table = pyvista.wrap(reader.GetOutput())
    assert isinstance(table, vtk.vtkTable)
    assert isinstance(table, pyvista.Table)

    assert table.n_rows == nr
    assert table.n_columns == nc

    assert len(table.row_arrays) == nc
    for i in range(nc):
        assert np.allclose(arrays[:, i], table[i])

    with pytest.raises(TypeError):
        pyvista.Table("foo")

    return


def test_table_row_arrays():
    nr, nc = 50, 3
    arrays = np.random.rand(nr, nc)
    table = pyvista.Table()
    for i in range(nc):
        table[f'foo{i}'] = arrays[:, i]
    assert table.n_columns == nc
    assert table.n_rows == nr
    for i in range(nc):
        assert np.allclose(table[f'foo{i}'], arrays[:, i])
    # Multi component
    table = pyvista.Table()
    table['multi'] = arrays
    assert table.n_columns == 1
    assert table.n_rows == nr
    assert np.allclose(table[0], arrays)
    assert np.allclose(table['multi'], arrays)
    del table['multi']
    assert table.n_columns == 0

    dataset = examples.load_hexbeam()
    array_dict = dataset.point_data
    # Test dict methods
    table = pyvista.Table()
    table.update(array_dict)
    assert table.n_rows == dataset.n_points
    assert table.n_columns == len(array_dict)

    assert len(table.row_arrays) == len(array_dict)
    for name in table.keys():
        assert np.allclose(dataset[name], table[name])

    for i, array in enumerate(table.values()):
        name = table.keys()[i]
        assert np.allclose(dataset[name], array)

    for name, array in table.items():
        assert np.allclose(dataset[name], array)

    n = table.n_arrays
    array = table.pop(table.keys()[0])
    assert isinstance(array, np.ndarray)
    assert table.n_arrays == n - 1
    array = table.get(table.keys()[0])
    assert isinstance(array, np.ndarray)
    assert table.n_arrays == n - 1

    del table[table.keys()[0]]
    assert table.n_arrays == n - 2

    return


def test_table_row_np_bool():
    n = 50
    table = pyvista.Table()
    bool_arr = np.zeros(n, np.bool_)
    table.row_arrays['bool_arr'] = bool_arr
    bool_arr[:] = True
    assert table.row_arrays['bool_arr'].all()
    assert table._row_array('bool_arr').all()
    assert table._row_array('bool_arr').dtype == np.bool_


def test_table_row_uint8():
    n = 50
    table = pyvista.Table()
    arr = np.zeros(n, np.uint8)
    table.row_arrays['arr'] = arr
    arr[:] = np.arange(n)
    assert np.allclose(table.row_arrays['arr'], np.arange(n))


def test_table_repr():
    nr, nc = 50, 3
    arrays = np.random.rand(nr, nc)
    table = pyvista.Table(arrays)
    text = table._repr_html_()
    assert isinstance(text, str)
    text = table.__repr__()
    assert isinstance(text, str)
    text = table.__str__()
    assert isinstance(text, str)


@pytest.mark.skipif(pd is None, reason="Requires Pandas")
def test_table_pandas():
    nr, nc = 50, 3
    arrays = np.random.rand(nr, nc)
    df = pd.DataFrame()
    for i in range(nc):
        df[f'foo{i}'] = arrays[:, i].copy()
    table = pyvista.Table(df)
    assert table.n_rows == nr
    assert table.n_columns == nc
    for i in range(nc):
        assert np.allclose(table.row_arrays[f'foo{i}'], arrays[:, i])
    assert df.equals(table.to_pandas())


def test_table_iter():
    nr, nc = 50, 3
    arrays = np.random.rand(nr, nc)
    table = pyvista.Table(arrays)
    for i, array in enumerate(table):
        assert np.allclose(array, arrays[:, i])


def test_texture():
    texture = pyvista.Texture(examples.mapfile)
    assert texture is not None
    image = texture.to_image()
    assert isinstance(image, pyvista.UniformGrid)
    arr = texture.to_array()
    assert isinstance(arr, np.ndarray)
    assert arr.shape[0] * arr.shape[1] == image.n_points
    texture.flip(0)
    texture.flip(1)
    texture = pyvista.Texture(examples.load_globe_texture())
    assert texture is not None


def test_skybox():
    texture = examples.load_globe_texture()
    texture.cube_map = False
    assert texture.cube_map is False

    texture.cube_map = True
    assert texture.cube_map is True

    skybox = texture.to_skybox()
    assert isinstance(skybox, vtk.vtkOpenGLSkybox)


# tests/test_parametric_geometry.py

import numpy as np
import pytest

import pyvista


def test_spline():
    theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
    z = np.linspace(-2, 2, 100)
    r = z**2 + 1
    x = r * np.sin(theta)
    y = r * np.cos(theta)

    points = np.column_stack((x, y, z))
    spline = pyvista.Spline(points, 1000)
    assert spline.n_points == 1000


def test_kochanek_spline():
    theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
    z = np.linspace(-2, 2, 100)
    r = z**2 + 1
    x = r * np.sin(theta)
    y = r * np.cos(theta)

    tension = np.random.random(3)
    bias = np.random.random(3)
    continuity = np.random.random(3)

    n_points = 1000
    points = np.column_stack((x, y, z))
    kochanek_spline = pyvista.KochanekSpline(points, tension, bias, continuity, n_points)
    assert kochanek_spline.n_points == n_points

    # test default
    kochanek_spline = pyvista.KochanekSpline(points, n_points=n_points)
    assert kochanek_spline.n_points == n_points

    # test invalid
    with pytest.raises(ValueError, match='tension'):
        kochanek_spline = pyvista.KochanekSpline(points, [-2, 0, 0], bias, continuity, n_points)
    with pytest.raises(ValueError, match='bias'):
        kochanek_spline = pyvista.KochanekSpline(points, tension, [-2, 0, 0], continuity, n_points)
    with pytest.raises(ValueError, match='continuity'):
        kochanek_spline = pyvista.KochanekSpline(points, tension, bias, [-2, 0, 0], n_points)


def test_ParametricBohemianDome():
    geom = pyvista.ParametricBohemianDome(direction=[0, 0, 1])
    assert geom.n_points


def test_ParametricBour():
    geom = pyvista.ParametricBour()
    assert geom.n_points


def test_ParametricBoy():
    geom = pyvista.ParametricBoy()
    assert geom.n_points


def test_ParametricCatalanMinimal():
    geom = pyvista.ParametricCatalanMinimal()
    assert geom.n_points


def test_ParametricConicSpiral():
    geom = pyvista.ParametricConicSpiral()
    assert geom.n_points


def test_ParametricCrossCap():
    geom = pyvista.ParametricCrossCap()
    assert geom.n_points


def test_ParametricDini():
    geom = pyvista.ParametricDini()
    assert geom.n_points


def test_ParametricEllipsoid():
    geom = pyvista.ParametricEllipsoid()
    assert geom.n_points


def test_ParametricEnneper():
    geom = pyvista.ParametricEnneper()
    assert geom.n_points


def test_ParametricFigure8Klein():
    geom = pyvista.ParametricFigure8Klein()
    assert geom.n_points


def test_ParametricHenneberg():
    geom = pyvista.ParametricHenneberg()
    assert geom.n_points


def test_ParametricKlein():
    geom = pyvista.ParametricKlein()
    assert geom.n_points


def test_ParametricKuen():
    geom = pyvista.ParametricKuen()
    assert geom.n_points


def test_ParametricMobius():
    geom = pyvista.ParametricMobius()
    assert geom.n_points


def test_ParametricPluckerConoid():
    geom = pyvista.ParametricPluckerConoid()
    assert geom.n_points


def test_ParametricPseudosphere():
    geom = pyvista.ParametricPseudosphere()
    assert geom.n_points


def test_ParametricRandomHills():
    geom = pyvista.ParametricRandomHills()
    assert geom.n_points


def test_ParametricRoman():
    geom = pyvista.ParametricRoman()
    assert geom.n_points


def test_ParametricSuperEllipsoid():
    geom = pyvista.ParametricSuperEllipsoid()
    assert geom.n_points


def test_ParametricSuperToroid():
    geom = pyvista.ParametricSuperToroid()
    assert geom.n_points


def test_ParametricTorus():
    geom = pyvista.ParametricTorus()
    assert geom.n_points


# tests/test_picking.py

import pytest

import pyvista
from pyvista.plotting import system_supports_plotting

NO_PLOTTING = not system_supports_plotting()

# skip all tests if unable to render
if not system_supports_plotting():
    pytestmark = pytest.mark.skip


@pytest.mark.needs_vtk9
def test_cell_picking():
    with pytest.raises(AttributeError, match="mesh"):
        plotter = pyvista.Plotter()
        plotter.enable_cell_picking(mesh=None)

    sphere = pyvista.Sphere()
    for through in (False, True):
        plotter = pyvista.Plotter(
            window_size=(100, 100),
        )

        def callback(*args, **kwargs):
            pass

        plotter.enable_cell_picking(
            mesh=sphere,
            start=True,
            show=True,
            callback=callback,
            through=through,
        )
        plotter.add_mesh(sphere)
        plotter.show(auto_close=False)  # must start renderer first

        # simulate the pick
        renderer = plotter.renderer
        picker = plotter.iren.get_picker()
        picker.Pick(50, 50, 0, renderer)

        # pick nothing
        picker.Pick(0, 0, 0, renderer)

        plotter.get_pick_position()
        plotter.close()

    # multiblock
    plotter = pyvista.Plotter()
    multi = pyvista.MultiBlock([sphere])
    plotter.add_mesh(multi)
    plotter.enable_cell_picking()
    plotter.close()


@pytest.mark.parametrize('left_clicking', [False, True])
def test_enable_mesh_picking(sphere, left_clicking):
    picked = []

    def callback(picked_mesh):
        picked.append(picked_mesh)

    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.enable_mesh_picking(callback=callback, left_clicking=left_clicking)
    pl.show(auto_close=False)

    width, height = pl.window_size

    # clicking is to "activate" the renderer
    pl.iren._mouse_left_button_press(width // 2, height // 2)
    pl.iren._mouse_left_button_release(width, height)
    pl.iren._mouse_move(width // 2, height // 2)
    if not left_clicking:
        pl.iren._simulate_keypress('p')

    assert sphere in picked
    assert pl.picked_mesh == sphere

    # invalid selection
    pl.iren._mouse_left_button_press(0, 0)
    pl.iren._mouse_left_button_release(0, 0)
    pl.iren._mouse_move(0, 0)
    if not left_clicking:
        pl.iren._simulate_keypress('p')

    assert pl.picked_mesh is None


@pytest.mark.parametrize('left_clicking', [False, True])
def test_enable_surface_picking(sphere, left_clicking):
    picked = []

    def callback(point):
        picked.append(point)

    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.enable_surface_picking(callback=callback, left_clicking=left_clicking)
    pl.show(auto_close=False)

    width, height = pl.window_size

    # clicking is to "activate" the renderer
    pl.iren._mouse_left_button_press(width // 2, height // 2)
    pl.iren._mouse_left_button_release(width, height)
    pl.iren._mouse_move(width // 2, height // 2)
    if not left_clicking:
        pl.iren._simulate_keypress('p')

    assert len(picked)
    assert pl.picked_point is not None

    # invalid selection
    pl.iren._mouse_left_button_press(0, 0)
    pl.iren._mouse_left_button_release(0, 0)
    pl.iren._mouse_move(0, 0)
    if not left_clicking:
        pl.iren._simulate_keypress('p')

    assert pl.picked_point is None


@pytest.mark.parametrize('left_clicking', [False, True])
def test_disable_picking(sphere, left_clicking):

    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.enable_surface_picking(left_clicking=left_clicking)
    pl.disable_picking()
    pl.show(auto_close=False)

    width, height = pl.window_size

    # clicking is to "activate" the renderer
    pl.iren._mouse_left_button_press(width // 2, height // 2)
    pl.iren._mouse_left_button_release(width, height)
    pl.iren._mouse_move(width // 2, height // 2)
    if not left_clicking:
        pl.iren._simulate_keypress('p')

    assert pl.picked_point is None

    # ensure it can safely be called twice
    pl.disable_picking()
    assert pl._picking_text not in pl.renderer.actors


def test_enable_cell_picking_interactive():

    n_cells = []

    def callback(picked_cells):
        n_cells.append(picked_cells.n_cells)

    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.Sphere())
    pl.enable_cell_picking(callback=callback)
    pl.show(auto_close=False, interactive=False)

    width, height = pl.window_size

    # simulate "r" keypress
    pl.iren._simulate_keypress('r')
    pl.iren._mouse_left_button_press(width // 2, height // 2)
    pl.iren._mouse_left_button_release(width, height)

    assert n_cells[0]


def test_enable_cell_picking_interactive_two_ren_win():

    n_cells = []

    def callback(picked_cells):
        n_cells.append(picked_cells.n_cells)

    pl = pyvista.Plotter(shape=(1, 2))
    pl.add_mesh(pyvista.Sphere())
    pl.enable_cell_picking(callback=callback)
    pl.show(auto_close=False, interactive=False)

    width, height = pl.window_size

    # simulate "r" keypress
    pl.iren._simulate_keypress('r')

    # select just the left-hand side
    pl.iren._mouse_left_button_press(width // 4, height // 2)
    pl.iren._mouse_left_button_release(width // 2, height)

    assert n_cells[0]


@pytest.mark.needs_vtk9
@pytest.mark.parametrize('left_clicking', [False, True])
def test_point_picking(left_clicking):
    sphere = pyvista.Sphere()
    for use_mesh in (False, True):
        plotter = pyvista.Plotter(
            window_size=(100, 100),
        )
        plotter.add_mesh(sphere)
        plotter.enable_point_picking(
            show_message=True,
            use_mesh=use_mesh,
            left_clicking=left_clicking,
            callback=lambda: None,
        )
        # must show to activate the interactive renderer (for left_clicking)
        plotter.show(auto_close=False)

        # simulate the pick
        width, height = plotter.window_size
        if left_clicking:
            plotter.iren._mouse_left_button_press(width // 2, height // 2)
            plotter.iren._mouse_left_button_release(width, height)
            plotter.iren._mouse_move(width // 2, height // 2)
        else:
            renderer = plotter.renderer
            picker = plotter.iren.get_picker()
            picker.Pick(width // 2, height // 2, 0, renderer)
        plotter.close()


@pytest.mark.needs_vtk9
def test_point_picking_window_not_pickable():

    plotter = pyvista.Plotter(
        window_size=(100, 100),
    )

    # bottom left corner, pickable
    sphere = pyvista.Sphere()
    sphere.translate([-100, -100, 0], inplace=True)
    plotter.add_mesh(sphere, pickable=True)

    # top right corner, not pickable
    unpickable_sphere = pyvista.Sphere()
    unpickable_sphere.translate([100, 100, 0], inplace=True)
    plotter.add_mesh(unpickable_sphere, pickable=False)

    plotter.view_xy()
    plotter.enable_point_picking(
        pickable_window=False,
        tolerance=0.2,
    )

    # simulate the pick
    renderer = plotter.renderer
    picker = plotter.iren.get_picker()

    successful_pick = picker.Pick(0, 0, 0, renderer)
    assert successful_pick

    successful_pick = picker.Pick(100, 100, 0, renderer)
    assert not successful_pick

    plotter.close()


@pytest.mark.needs_vtk9
def test_path_picking():
    sphere = pyvista.Sphere()
    plotter = pyvista.Plotter(
        window_size=(100, 100),
    )
    plotter.add_mesh(sphere)
    plotter.enable_path_picking(
        show_message=True,
        callback=lambda: None,
    )
    # simulate the pick
    renderer = plotter.renderer
    picker = plotter.iren.get_picker()
    picker.Pick(50, 50, 0, renderer)
    # pick nothing
    picker.Pick(0, 0, 0, renderer)
    # 'c' to clear
    clear_callback = plotter.iren._key_press_event_callbacks['c']
    clear_callback[0]()
    plotter.close()


@pytest.mark.needs_vtk9
def test_geodesic_picking():
    sphere = pyvista.Sphere()
    plotter = pyvista.Plotter(
        window_size=(100, 100),
    )
    plotter.add_mesh(sphere)
    plotter.enable_geodesic_picking(
        show_message=True,
        callback=lambda: None,
        show_path=True,
        keep_order=True,
    )
    plotter.show(auto_close=False)

    # simulate the pick
    renderer = plotter.renderer
    picker = plotter.iren.get_picker()
    picker.Pick(50, 50, 0, renderer)
    picker.Pick(45, 45, 0, renderer)
    # pick nothing
    picker.Pick(0, 0, 0, renderer)
    # 'c' to clear
    clear_callback = plotter.iren._key_press_event_callbacks['c']
    clear_callback[0]()
    plotter.close()


@pytest.mark.needs_vtk9
def test_horizon_picking():
    sphere = pyvista.Sphere()
    plotter = pyvista.Plotter(
        window_size=(100, 100),
    )
    plotter.add_mesh(sphere)
    plotter.enable_horizon_picking(
        show_message=True,
        callback=lambda: None,
        show_horizon=True,
    )
    # simulate the pick
    renderer = plotter.renderer
    picker = plotter.iren.get_picker()
    # at least 3 picks
    picker.Pick(50, 50, 0, renderer)
    picker.Pick(49, 50, 0, renderer)
    picker.Pick(48, 50, 0, renderer)
    # pick nothing
    picker.Pick(0, 0, 0, renderer)
    # 'c' to clear
    clear_callback = plotter.iren._key_press_event_callbacks['c']
    clear_callback[0]()
    plotter.close()


def test_enable_fly_to_right_click(sphere):

    point = []

    def callback(click_point):
        point.append(click_point)

    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.enable_fly_to_right_click(callback=callback)
    pl.show(auto_close=False)
    width, height = pl.window_size
    cpos_before = pl.camera_position
    pl.iren._mouse_right_button_press(width // 2, height // 2)

    # ensure callback was called and camera position changes due to "fly"
    assert cpos_before != pl.camera_position
    assert point


def test_enable_fly_to_right_click_multi_render(sphere):
    """Same as enable as fly_to_right_click except with two renders for coverage"""
    point = []

    def callback(click_point):
        point.append(click_point)

    pl = pyvista.Plotter(shape=(1, 2))
    pl.add_mesh(sphere)
    pl.enable_fly_to_right_click(callback=callback)
    pl.show(auto_close=False)
    width, height = pl.window_size
    cpos_before = pl.camera_position
    pl.iren._mouse_right_button_press(width // 4, height // 2)
    # ensure callback was called and camera position changes due to "fly"
    assert cpos_before != pl.camera_position
    assert point


def test_block_picking(multiblock_poly):
    """Test we can pick a block."""

    pl = pyvista.Plotter()
    width, height = pl.window_size
    actor, mapper = pl.add_composite(multiblock_poly)

    picked_blocks = []

    def turn_blue(index, dataset):
        mapper.block_attr[index].color = 'blue'
        picked_blocks.append(index)

    pl.enable_block_picking(callback=turn_blue)
    pl.show(auto_close=False)

    # click in the corner
    assert not picked_blocks
    pl.iren._mouse_left_button_press(0, 0)
    pl.iren._mouse_left_button_release(0, 0)
    assert not picked_blocks

    # click directly in the middle
    pl.iren._mouse_left_button_press(width // 2, height // 2)
    pl.iren._mouse_left_button_release(width // 2, height // 2)
    assert mapper.block_attr[2].color

    assert pl.picked_block_index == picked_blocks[0]


# tests/test_plotter.py

"""Test any plotting that does not require rendering.

All other tests requiring rendering should to in
./plotting/test_plotting.py

"""
import numpy as np
import pytest

import pyvista
from pyvista.plotting import _plotting


def test_plotter_image():
    plotter = pyvista.Plotter()
    with pytest.raises(AttributeError, match="not yet been set up"):
        plotter.image


def test_enable_hidden_line_removal():
    plotter = pyvista.Plotter(shape=(1, 2))
    plotter.enable_hidden_line_removal(False)
    assert plotter.renderers[0].GetUseHiddenLineRemoval()
    assert not plotter.renderers[1].GetUseHiddenLineRemoval()

    plotter.enable_hidden_line_removal(True)
    assert plotter.renderers[1].GetUseHiddenLineRemoval()


def test_disable_hidden_line_removal():
    plotter = pyvista.Plotter(shape=(1, 2))
    plotter.enable_hidden_line_removal(True)

    plotter.disable_hidden_line_removal(False)
    assert not plotter.renderers[0].GetUseHiddenLineRemoval()
    assert plotter.renderers[1].GetUseHiddenLineRemoval()

    plotter.disable_hidden_line_removal(True)
    assert not plotter.renderers[1].GetUseHiddenLineRemoval()


def test_pickable_actors():

    plotter = pyvista.Plotter()
    sphere = plotter.add_mesh(pyvista.Sphere(), pickable=True)
    cube = plotter.add_mesh(pyvista.Cube(), pickable=False)

    pickable = plotter.pickable_actors
    assert sphere in pickable
    assert cube not in pickable

    plotter.pickable_actors = cube
    pickable = plotter.pickable_actors
    assert sphere not in pickable
    assert cube in pickable

    plotter.pickable_actors = [sphere, cube]
    pickable = plotter.pickable_actors
    assert sphere in pickable
    assert cube in pickable

    plotter.pickable_actors = None
    pickable = plotter.pickable_actors
    assert sphere not in pickable
    assert cube not in pickable

    with pytest.raises(TypeError, match="Expected a vtkActor instance or "):
        plotter.pickable_actors = [0, 10]


def test_prepare_smooth_shading_texture(globe):
    """Test edge cases for smooth shading"""
    mesh, scalars = _plotting.prepare_smooth_shading(globe, None, True, True, False, None)
    assert scalars is None
    assert "Normals" in mesh.point_data
    assert "Texture Coordinates" in mesh.point_data


def test_prepare_smooth_shading_not_poly(hexbeam):
    """Test edge cases for smooth shading"""
    scalars_name = "sample_point_scalars"
    scalars = hexbeam.point_data[scalars_name]
    mesh, scalars = _plotting.prepare_smooth_shading(hexbeam, scalars, False, True, True, None)

    assert "Normals" in mesh.point_data

    expected_mesh = hexbeam.extract_surface().compute_normals(
        cell_normals=False,
        split_vertices=True,
    )

    assert np.allclose(mesh[scalars_name], expected_mesh[scalars_name])


def test_get_datasets(sphere, hexbeam):
    """Test pyvista.Plotter._datasets."""
    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.add_mesh(hexbeam)
    datasets = pl._datasets
    assert len(datasets) == 2
    assert sphere in datasets and hexbeam in datasets


def test_remove_scalars_single(sphere, hexbeam):
    """Ensure no scalars are added when plotting datasets if copy_mesh=True."""
    # test single component scalars
    sphere.clear_data()
    hexbeam.clear_data()
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=range(sphere.n_points), copy_mesh=True)
    pl.add_mesh(hexbeam, scalars=range(hexbeam.n_cells), copy_mesh=True)

    # arrays will be added to the mesh
    pl.mesh.n_arrays == 1

    # but not the original data
    assert sphere.n_arrays == 0
    assert hexbeam.n_arrays == 0

    pl.close()


def test_active_scalars_remain(sphere, hexbeam):
    """Ensure active scalars remain active despite plotting different scalars when copy_mesh=True."""
    point_data_name = "point_data"
    cell_data_name = "cell_data"
    sphere[point_data_name] = np.random.random(sphere.n_points)
    hexbeam[cell_data_name] = np.random.random(hexbeam.n_cells)
    assert sphere.point_data.active_scalars_name == point_data_name
    assert hexbeam.cell_data.active_scalars_name == cell_data_name

    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalars=range(sphere.n_points), copy_mesh=True)
    pl.add_mesh(hexbeam, scalars=range(hexbeam.n_cells), copy_mesh=True)
    pl.close()

    assert sphere.point_data.active_scalars_name == point_data_name
    assert hexbeam.cell_data.active_scalars_name == cell_data_name


def test_no_added_with_scalar_bar(sphere):
    point_data_name = "data"
    sphere[point_data_name] = np.random.random(sphere.n_points)
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, scalar_bar_args={"title": "some_title"})
    assert sphere.n_arrays == 1


def test_plotter_remains_shallow():
    sphere = pyvista.Sphere()
    sphere.point_data['numbers'] = np.arange(sphere.n_points)
    sphere2 = sphere.copy(deep=False)

    sphere['numbers'] *= -1  # sphere2 'numbers' are also modified

    assert np.array_equal(sphere['numbers'], sphere2['numbers'])
    assert np.shares_memory(sphere['numbers'], sphere2['numbers'])

    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere, scalars=None)

    sphere[
        'numbers'
    ] *= -1  # sphere2 'numbers' are also modified after adding to Plotter.  (See  issue #2461)

    assert np.array_equal(sphere['numbers'], sphere2['numbers'])
    assert np.shares_memory(sphere['numbers'], sphere2['numbers'])


def test_add_multiple(sphere):
    point_data_name = 'data'
    sphere[point_data_name] = np.random.random(sphere.n_points)
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, copy_mesh=True)
    pl.add_mesh(sphere, scalars=np.arange(sphere.n_points), copy_mesh=True)
    pl.add_mesh(sphere, scalars=np.arange(sphere.n_cells), copy_mesh=True)
    pl.add_mesh(sphere, scalars='data', copy_mesh=True)
    pl.show()
    assert sphere.n_arrays == 1


def test_deep_clean(cube):
    pl = pyvista.Plotter()
    cube_orig = cube.copy()
    pl.add_mesh(cube)
    pl.deep_clean()
    assert pl.mesh is None
    assert pl.mapper is None
    assert pl.volume is None
    assert pl.textActor is None
    assert cube == cube_orig


def test_disable_depth_of_field(sphere):
    pl = pyvista.Plotter()
    pl.enable_depth_of_field()
    assert pl.renderer.GetPass() is not None
    pl.disable_depth_of_field()
    assert pl.renderer.GetPass() is None


def test_remove_blurring(sphere):
    pl = pyvista.Plotter()
    pl.add_blurring()
    assert pl.renderer.GetPass() is not None
    pl.remove_blurring()
    assert pl.renderer.GetPass() is None


def test_anti_aliasing_multiplot(sphere):
    pl = pyvista.Plotter(shape=(1, 2))
    pl.enable_anti_aliasing('fxaa', all_renderers=False)
    assert pl.renderers[0].GetUseFXAA()
    assert not pl.renderers[1].GetUseFXAA()

    pl.enable_anti_aliasing('fxaa', all_renderers=True)
    assert pl.renderers[1].GetUseFXAA()

    pl.disable_anti_aliasing(all_renderers=False)
    assert not pl.renderers[0].GetUseFXAA()
    assert pl.renderers[1].GetUseFXAA()

    pl.disable_anti_aliasing(all_renderers=True)
    assert not pl.renderers[0].GetUseFXAA()
    assert not pl.renderers[1].GetUseFXAA()


def test_anti_aliasing_invalid():
    pl = pyvista.Plotter()
    with pytest.raises(ValueError, match='Should be either "fxaa" or "ssaa"'):
        pl.renderer.enable_anti_aliasing('invalid')


# tests/test_pointset.py

"""Test pyvista.PointSet"""

import numpy as np
import pytest
import vtk

import pyvista

# skip all tests if concrete pointset unavailable
pytestmark = pytest.mark.skipif(
    pyvista.vtk_version_info < (9, 1, 0), reason="Requires VTK>=9.1.0 for a concrete PointSet class"
)


def test_pointset_basic():
    # create empty pointset
    pset = pyvista.PointSet()
    assert pset.n_points == 0
    assert pset.n_cells == 0
    assert 'PointSet' in str(pset)
    assert 'PointSet' in repr(pset)


def test_pointset_from_vtk():
    vtk_pset = vtk.vtkPointSet()

    np_points = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
    points = pyvista.vtk_points(np_points, deep=False)
    vtk_pset.SetPoints(points)

    pset = pyvista.PointSet(vtk_pset, deep=False)
    assert pset.n_points == 2

    # test that data is shallow copied

    np_points[:] = [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]
    assert np.array_equal(np_points, pset.points)

    # test that data is deep copied

    np_points = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
    point_copy = np_points.copy()
    points = pyvista.vtk_points(np_points, deep=False)
    vtk_pset.SetPoints(points)

    pset = pyvista.PointSet(vtk_pset, deep=True)

    np_points[:] = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
    assert not np.array_equal(np_points, pset.points)
    assert np.array_equal(pset.points, point_copy)


def test_pointset_wrap():
    vtk_pset = vtk.vtkPointSet()
    np_points = np.array([[0.0, 0.0, 0.0]])
    points = pyvista.vtk_points(np_points, deep=False)
    vtk_pset.SetPoints(points)

    pset = pyvista.wrap(vtk_pset)
    assert type(pset) is pyvista.PointSet

    # test that wrapping is shallow copied
    pset.points[:] = np.array([[1.0, 0.0, 0.0]])
    assert np.array_equal(vtk_pset.GetPoint(0), pset.points[0])


def test_pointset(pointset):
    assert pointset.n_points == pointset.points.shape[0]
    assert pointset.n_cells == 0

    arr_name = 'arr'
    pointset.point_data[arr_name] = np.random.random(10)
    assert arr_name in pointset.point_data

    # test that points can be modified
    pointset.points[:] = 0
    assert np.allclose(pointset.points, 0)
    pointset.points = np.ones((10, 3))
    assert np.allclose(pointset.points, 1)


def test_save(tmpdir, pointset):
    filename = str(tmpdir.mkdir("tmpdir").join(f'{"tmp.xyz"}'))
    pointset.save(filename)
    points = np.loadtxt(filename)
    assert np.allclose(points, pointset.points)


@pytest.mark.parametrize('deep', [True, False])
def test_cast_to_polydata(pointset, deep):
    data = np.linspace(0, 1, pointset.n_points)
    key = 'key'
    pointset.point_data[key] = data

    pdata = pointset.cast_to_polydata(deep)
    assert key in pdata.point_data
    assert np.allclose(pdata.point_data[key], pointset.point_data[key])
    pdata.point_data[key][:] = 0
    if deep:
        assert not np.allclose(pdata.point_data[key], pointset.point_data[key])
    else:
        assert np.allclose(pdata.point_data[key], pointset.point_data[key])


def test_filters_return_pointset(sphere):
    pointset = sphere.cast_to_pointset()
    clipped = pointset.clip()
    assert isinstance(clipped, pyvista.PointSet)


# tests/test_polydata.py

from math import pi
import os
import pathlib

import numpy as np
import pytest

import pyvista
from pyvista import examples
from pyvista.core.errors import NotAllTrianglesError
from pyvista.plotting import system_supports_plotting
from pyvista.utilities.misc import PyVistaFutureWarning

radius = 0.5

skip_plotting = pytest.mark.skipif(
    not system_supports_plotting(), reason="Requires system to support plotting"
)


@pytest.fixture
def sphere():
    # this shadows the main sphere fixture from conftest!
    return pyvista.Sphere(radius, theta_resolution=10, phi_resolution=10)


@pytest.fixture
def sphere_shifted():
    return pyvista.Sphere(center=[0.5, 0.5, 0.5], theta_resolution=10, phi_resolution=10)


@pytest.fixture
def sphere_dense():
    return pyvista.Sphere(radius, theta_resolution=100, phi_resolution=100)


@pytest.fixture
def cube_dense():
    return pyvista.Cube()


test_path = os.path.dirname(os.path.abspath(__file__))


def is_binary(filename):
    """Return ``True`` when a file is binary."""
    textchars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})
    with open(filename, 'rb') as f:
        data = f.read(1024)
    return bool(data.translate(None, textchars))


def test_init():
    mesh = pyvista.PolyData()
    assert not mesh.n_points
    assert not mesh.n_cells


def test_init_from_pdata(sphere):
    mesh = pyvista.PolyData(sphere, deep=True)
    assert mesh.n_points
    assert mesh.n_cells
    mesh.points[0] += 1
    assert not np.allclose(sphere.points[0], mesh.points[0])


def test_init_from_arrays():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0.5, 0.5, -1]])

    # mesh faces
    faces = np.hstack([[4, 0, 1, 2, 3], [3, 0, 1, 4], [3, 1, 2, 4]]).astype(np.int8)

    mesh = pyvista.PolyData(vertices, faces)
    assert mesh.n_points == 5
    assert mesh.n_cells == 3

    mesh = pyvista.PolyData(vertices, faces, deep=True)
    vertices[0] += 1
    assert not np.allclose(vertices[0], mesh.points[0])

    # ensure that polydata raises a warning when inputting non-float dtype
    with pytest.warns(Warning):
        mesh = pyvista.PolyData(vertices.astype(np.int32), faces)


def test_init_from_arrays_with_vert():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0.5, 0.5, -1], [0, 1.5, 1.5]])

    # mesh faces
    faces = np.hstack(
        [[4, 0, 1, 2, 3], [3, 0, 1, 4], [3, 1, 2, 4], [1, 5]]  # [quad, triangle, triangle, vertex]
    ).astype(np.int8)

    mesh = pyvista.PolyData(vertices, faces)
    assert mesh.n_points == 6
    assert mesh.n_cells == 4


def test_init_from_arrays_triangular():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0.5, 0.5, -1]])

    # mesh faces
    faces = np.vstack([[3, 0, 1, 2], [3, 0, 1, 4], [3, 1, 2, 4]])

    mesh = pyvista.PolyData(vertices, faces)
    assert mesh.n_points == 5
    assert mesh.n_cells == 3

    mesh = pyvista.PolyData(vertices, faces, deep=True)
    assert mesh.n_points == 5
    assert mesh.n_cells == 3


def test_init_as_points():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0.5, 0.5, -1]])

    mesh = pyvista.PolyData(vertices)
    assert mesh.n_points == vertices.shape[0]
    assert mesh.n_cells == vertices.shape[0]
    assert len(mesh.verts) == vertices.shape[0] * 2

    vertices = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]])
    cells = np.array([1, 0, 1, 1, 1, 2], np.int16)
    to_check = pyvista.PolyData._make_vertex_cells(len(vertices)).ravel()
    assert np.allclose(to_check, cells)

    # from list
    mesh.verts = [[1, 0], [1, 1], [1, 2]]
    to_check = pyvista.PolyData._make_vertex_cells(len(vertices)).ravel()
    assert np.allclose(to_check, cells)

    mesh = pyvista.PolyData()
    mesh.points = vertices
    mesh.verts = cells
    assert mesh.n_points == vertices.shape[0]
    assert mesh.n_cells == vertices.shape[0]
    assert np.allclose(mesh.verts, cells)


def test_init_as_points_from_list():
    points = [[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
    mesh = pyvista.PolyData(points)
    assert np.allclose(mesh.points, points)


def test_invalid_init():
    with pytest.raises(ValueError):
        pyvista.PolyData(np.array([1.0]))

    with pytest.raises(TypeError):
        pyvista.PolyData([1.0, 2.0, 3.0], 'woa')

    with pytest.raises(ValueError):
        pyvista.PolyData('woa', 'woa')

    poly = pyvista.PolyData()
    with pytest.raises(ValueError):
        pyvista.PolyData(poly, 'woa')

    with pytest.raises(TypeError):
        pyvista.PolyData({'woa'})


def test_invalid_file():
    with pytest.raises(FileNotFoundError):
        pyvista.PolyData('file.bad')

    with pytest.raises(IOError):
        filename = os.path.join(test_path, 'test_polydata.py')
        pyvista.PolyData(filename)


def test_lines_on_init():
    lines = [2, 0, 1, 3, 2, 3, 4]
    points = np.random.random((5, 3))
    pd = pyvista.PolyData(points, lines=lines)
    assert not pd.faces.size
    assert np.array_equal(pd.lines, lines)
    assert np.array_equal(pd.points, points)


def test_polydata_repr_str():
    pd = pyvista.PolyData()
    assert repr(pd) == str(pd)
    assert 'N Cells' in str(pd)
    assert 'N Points' in str(pd)
    assert 'X Bounds' in str(pd)
    assert 'N Arrays' in str(pd)


def test_geodesic(sphere):
    start, end = 0, sphere.n_points - 1
    geodesic = sphere.geodesic(start, end)
    assert isinstance(geodesic, pyvista.PolyData)
    assert "vtkOriginalPointIds" in geodesic.array_names
    ids = geodesic.point_data["vtkOriginalPointIds"]
    assert np.allclose(geodesic.points, sphere.points[ids])

    # check keep_order
    geodesic_legacy = sphere.geodesic(start, end, keep_order=False)
    assert geodesic_legacy["vtkOriginalPointIds"][0] == end
    geodesic_ordered = sphere.geodesic(start, end, keep_order=True)
    assert geodesic_ordered["vtkOriginalPointIds"][0] == start

    # finally, inplace
    geodesic_inplace = sphere.geodesic(start, end, inplace=True)
    assert geodesic_inplace is sphere
    assert np.allclose(geodesic.points, sphere.points)


def test_geodesic_fail(sphere, plane):
    with pytest.raises(IndexError):
        sphere.geodesic(-1, -1)
    with pytest.raises(IndexError):
        sphere.geodesic(sphere.n_points, 0)

    with pytest.raises(NotAllTrianglesError):
        plane.geodesic(0, 10)


def test_geodesic_distance(sphere):
    distance = sphere.geodesic_distance(0, sphere.n_points - 1)
    assert isinstance(distance, float)


def test_ray_trace(sphere):
    points, ind = sphere.ray_trace([0, 0, 0], [1, 1, 1])
    assert np.any(points)
    assert np.any(ind)


@skip_plotting
def test_ray_trace_plot(sphere):
    points, ind = sphere.ray_trace(
        [0, 0, 0], [1, 1, 1], plot=True, first_point=True, off_screen=True
    )
    assert np.any(points)
    assert np.any(ind)


def test_multi_ray_trace(sphere):
    pytest.importorskip('rtree')
    pytest.importorskip('pyembree')
    pytest.importorskip('trimesh')
    origins = [[1, 0, 1], [0.5, 0, 1], [0.25, 0, 1], [0, 0, 1]]
    directions = [[0, 0, -1]] * 4
    points, ind_r, ind_t = sphere.multi_ray_trace(origins, directions, retry=True)
    assert np.any(points)
    assert np.any(ind_r)
    assert np.any(ind_t)

    # check non-triangulated
    mesh = pyvista.Cylinder()
    with pytest.raises(NotAllTrianglesError):
        mesh.multi_ray_trace(origins, directions)


@skip_plotting
def test_plot_curvature(sphere):
    sphere.plot_curvature(off_screen=True)


def test_edge_mask(sphere):
    _ = sphere.edge_mask(10, progress_bar=True)


def test_boolean_union_intersection(sphere, sphere_shifted):
    union = sphere.boolean_union(sphere_shifted, progress_bar=True)
    intersection = sphere.boolean_intersection(sphere_shifted, progress_bar=True)

    # union is volume of sphere + sphere_shifted minus the part intersecting
    expected_volume = sphere.volume + sphere_shifted.volume - intersection.volume
    assert np.isclose(union.volume, expected_volume, atol=1e-3)

    # intersection volume is the volume of both isolated meshes minus the union
    expected_volume = sphere.volume + sphere_shifted.volume - union.volume
    assert np.isclose(intersection.volume, expected_volume, atol=1e-3)


def test_boolean_difference(sphere, sphere_shifted):
    difference = sphere.boolean_difference(sphere_shifted, progress_bar=True)
    intersection = sphere.boolean_intersection(sphere_shifted, progress_bar=True)

    expected_volume = sphere.volume - intersection.volume
    assert np.isclose(difference.volume, expected_volume, atol=1e-3)


def test_boolean_difference_fail(plane):
    with pytest.raises(NotAllTrianglesError):
        plane - plane


def test_subtract(sphere, sphere_shifted):
    sub_mesh = sphere - sphere_shifted
    assert sub_mesh.n_points == sphere.boolean_difference(sphere_shifted).n_points


def test_merge(sphere, sphere_shifted, hexbeam):
    merged = sphere.merge(hexbeam, progress_bar=True)
    assert merged.n_points == (sphere.n_points + hexbeam.n_points)
    assert isinstance(merged, pyvista.UnstructuredGrid)

    # list with unstructuredgrid case
    merged = sphere.merge([hexbeam, hexbeam], merge_points=False, progress_bar=True)
    assert merged.n_points == (sphere.n_points + hexbeam.n_points * 2)
    assert isinstance(merged, pyvista.UnstructuredGrid)

    # with polydata
    merged = sphere.merge(sphere_shifted, progress_bar=True)
    assert isinstance(merged, pyvista.PolyData)
    assert merged.n_points == sphere.n_points + sphere_shifted.n_points

    # with polydata list (no merge)
    merged = sphere.merge([sphere_shifted, sphere_shifted], merge_points=False, progress_bar=True)
    assert isinstance(merged, pyvista.PolyData)
    assert merged.n_points == sphere.n_points + sphere_shifted.n_points * 2

    # with polydata list (merge)
    merged = sphere.merge([sphere_shifted, sphere_shifted], progress_bar=True)
    assert isinstance(merged, pyvista.PolyData)
    assert merged.n_points == sphere.n_points + sphere_shifted.n_points

    # test in-place merge
    mesh = sphere.copy()
    merged = mesh.merge(sphere_shifted, inplace=True)
    assert merged is mesh

    # test main_has_priority
    mesh = sphere.copy()
    data_main = np.arange(mesh.n_points, dtype=float)
    mesh.point_data['present_in_both'] = data_main
    other = mesh.copy()
    data_other = -data_main
    other.point_data['present_in_both'] = data_other
    merged = mesh.merge(other, main_has_priority=True)

    # note: order of points can change after point merging
    def matching_point_data(this, that, scalars_name):
        """Return True if scalars on two meshes only differ by point order."""
        return all(
            new_val == this.point_data[scalars_name][j]
            for point, new_val in zip(that.points, that.point_data[scalars_name])
            for j in (this.points == point).all(-1).nonzero()
        )

    assert matching_point_data(merged, mesh, 'present_in_both')
    merged = mesh.merge(other, main_has_priority=False)
    assert matching_point_data(merged, other, 'present_in_both')


def test_add(sphere, sphere_shifted):
    merged = sphere + sphere_shifted
    assert isinstance(merged, pyvista.PolyData)
    assert merged.n_points == sphere.n_points + sphere_shifted.n_points
    assert merged.n_faces == sphere.n_cells + sphere_shifted.n_cells


def test_intersection(sphere, sphere_shifted):
    intersection, first, second = sphere.intersection(
        sphere_shifted, split_first=True, split_second=True, progress_bar=True
    )

    assert intersection.n_points
    assert first.n_points > sphere.n_points
    assert second.n_points > sphere_shifted.n_points

    intersection, first, second = sphere.intersection(
        sphere_shifted, split_first=False, split_second=False, progress_bar=True
    )
    assert intersection.n_points
    assert first.n_points == sphere.n_points
    assert second.n_points == sphere_shifted.n_points


@pytest.mark.parametrize('curv_type', ['mean', 'gaussian', 'maximum', 'minimum'])
def test_curvature(sphere, curv_type):
    curv = sphere.curvature(curv_type)
    assert np.any(curv)
    assert curv.size == sphere.n_points


def test_invalid_curvature(sphere):
    with pytest.raises(ValueError):
        sphere.curvature('not valid')


@pytest.mark.parametrize('binary', [True, False])
@pytest.mark.parametrize('extension', pyvista.core.pointset.PolyData._WRITERS)
def test_save(sphere, extension, binary, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join(f'tmp{extension}'))
    sphere.save(filename, binary)

    if binary:
        if extension == '.vtp':
            assert 'binary' in open(filename).read(1000)
        else:
            is_binary(filename)
    else:
        with open(filename) as f:
            fst = f.read(100).lower()
            assert 'ascii' in fst or 'xml' in fst or 'solid' in fst

    mesh = pyvista.PolyData(filename)
    assert mesh.faces.shape == sphere.faces.shape
    assert mesh.points.shape == sphere.points.shape


@pytest.mark.parametrize('as_str', [True, False])
@pytest.mark.parametrize('ndim', [3, 4])
def test_save_ply_texture_array(sphere, ndim, as_str, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.ply'))

    texture = np.ones((sphere.n_points, ndim), np.uint8)
    texture[:, 2] = np.arange(sphere.n_points)[::-1]
    if as_str:
        sphere.point_data['texture'] = texture
        sphere.save(filename, texture='texture')
    else:
        sphere.save(filename, texture=texture)

    mesh = pyvista.PolyData(filename)
    color_array_name = 'RGB' if ndim == 3 else 'RGBA'
    assert np.allclose(mesh[color_array_name], texture)


@pytest.mark.parametrize('as_str', [True, False])
def test_save_ply_texture_array_catch(sphere, as_str, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.ply'))

    texture = np.ones((sphere.n_points, 3), np.float32)
    with pytest.raises(ValueError, match='Invalid datatype'):
        if as_str:
            sphere.point_data['texture'] = texture
            sphere.save(filename, texture='texture')
        else:
            sphere.save(filename, texture=texture)

    with pytest.raises(TypeError):
        sphere.save(filename, texture=[1, 2, 3])


def test_pathlib_read_write(tmpdir, sphere):
    path = pathlib.Path(str(tmpdir.mkdir("tmpdir").join('tmp.vtk')))
    sphere.save(path)
    assert path.is_file()

    mesh = pyvista.PolyData(path)
    assert mesh.faces.shape == sphere.faces.shape
    assert mesh.points.shape == sphere.points.shape

    mesh = pyvista.read(path)
    assert isinstance(mesh, pyvista.PolyData)
    assert mesh.faces.shape == sphere.faces.shape
    assert mesh.points.shape == sphere.points.shape


def test_invalid_save(sphere):
    with pytest.raises(ValueError):
        sphere.save('file.abc')


def test_triangulate_filter(plane):
    assert not plane.is_all_triangles
    plane.triangulate(inplace=True)
    assert plane.is_all_triangles
    # Make a point cloud and assert false
    assert not pyvista.PolyData(plane.points).is_all_triangles
    # Extract lines and make sure false
    assert not plane.extract_all_edges().is_all_triangles


@pytest.mark.parametrize('subfilter', ['butterfly', 'loop', 'linear'])
def test_subdivision(sphere, subfilter):
    mesh = sphere.subdivide(1, subfilter, progress_bar=True)
    assert mesh.n_points > sphere.n_points
    assert mesh.n_faces > sphere.n_faces

    mesh = sphere.copy()
    mesh.subdivide(1, subfilter, inplace=True)
    assert mesh.n_points > sphere.n_points
    assert mesh.n_faces > sphere.n_faces


def test_invalid_subdivision(sphere):
    with pytest.raises(ValueError):
        sphere.subdivide(1, 'not valid')

    # check non-triangulated
    mesh = pyvista.Cylinder()
    with pytest.raises(NotAllTrianglesError):
        mesh.subdivide(1)


def test_extract_feature_edges(sphere):
    # Test extraction of NO edges
    edges = sphere.extract_feature_edges(90)
    assert not edges.n_points

    mesh = pyvista.Cube()  # use a mesh that actually has strongly defined edges
    more_edges = mesh.extract_feature_edges(10)
    assert more_edges.n_points


def test_decimate(sphere):
    mesh = sphere.decimate(0.5, progress_bar=True)
    assert mesh.n_points < sphere.n_points
    assert mesh.n_faces < sphere.n_faces

    mesh.decimate(0.5, inplace=True, progress_bar=True)
    assert mesh.n_points < sphere.n_points
    assert mesh.n_faces < sphere.n_faces

    # check non-triangulated
    mesh = pyvista.Cylinder()
    with pytest.raises(NotAllTrianglesError):
        mesh.decimate(0.5)


def test_decimate_pro(sphere):
    mesh = sphere.decimate_pro(0.5, progress_bar=True, max_degree=10)
    assert mesh.n_points < sphere.n_points
    assert mesh.n_faces < sphere.n_faces

    mesh.decimate_pro(0.5, inplace=True, progress_bar=True)
    assert mesh.n_points < sphere.n_points
    assert mesh.n_faces < sphere.n_faces

    # check non-triangulated
    mesh = pyvista.Cylinder()
    with pytest.raises(NotAllTrianglesError):
        mesh.decimate_pro(0.5)


def test_compute_normals(sphere):
    sphere_normals = sphere
    sphere_normals.compute_normals(inplace=True)

    point_normals = sphere_normals.point_data['Normals']
    cell_normals = sphere_normals.cell_data['Normals']
    assert point_normals.shape[0] == sphere.n_points
    assert cell_normals.shape[0] == sphere.n_cells


def test_compute_normals_split_vertices(cube):
    # verify edge splitting occurs and point IDs are tracked
    cube_split_norm = cube.compute_normals(split_vertices=True)
    assert cube_split_norm.n_points == 24
    assert 'pyvistaOriginalPointIds' in cube_split_norm.point_data
    assert len(set(cube_split_norm.point_data['pyvistaOriginalPointIds'])) == 8


def test_point_normals(sphere):
    sphere = sphere.compute_normals(cell_normals=False, point_normals=True)

    # when `Normals` already exist, make sure they are returned
    normals = sphere.point_normals
    assert normals.shape[0] == sphere.n_points
    assert np.all(normals == sphere.point_data['Normals'])
    assert np.shares_memory(normals, sphere.point_data['Normals'])

    # when they don't, compute them
    sphere.point_data.pop('Normals')
    normals = sphere.point_normals
    assert normals.shape[0] == sphere.n_points


def test_cell_normals(sphere):
    sphere = sphere.compute_normals(cell_normals=True, point_normals=False)

    # when `Normals` already exist, make sure they are returned
    normals = sphere.cell_normals
    assert normals.shape[0] == sphere.n_cells
    assert np.all(normals == sphere.cell_data['Normals'])
    assert np.shares_memory(normals, sphere.cell_data['Normals'])

    # when they don't, compute them
    sphere.cell_data.pop('Normals')
    normals = sphere.cell_normals
    assert normals.shape[0] == sphere.n_cells


def test_face_normals(sphere):
    assert sphere.face_normals.shape[0] == sphere.n_faces


def test_clip_plane(sphere):
    clipped_sphere = sphere.clip(
        origin=[0, 0, 0], normal=[0, 0, -1], invert=False, progress_bar=True
    )
    faces = clipped_sphere.faces.reshape(-1, 4)[:, 1:]
    assert np.all(clipped_sphere.points[faces, 2] <= 0)

    sphere.clip(origin=[0, 0, 0], normal=[0, 0, -1], inplace=True, invert=False, progress_bar=True)
    faces = clipped_sphere.faces.reshape(-1, 4)[:, 1:]
    assert np.all(clipped_sphere.points[faces, 2] <= 0)


def test_extract_largest(sphere):
    mesh = sphere + pyvista.Sphere(0.1, theta_resolution=5, phi_resolution=5)
    largest = mesh.extract_largest()
    assert largest.n_faces == sphere.n_faces

    mesh.extract_largest(inplace=True)
    assert mesh.n_faces == sphere.n_faces


def test_clean(sphere):
    mesh = sphere.merge(sphere, merge_points=False).extract_surface()
    assert mesh.n_points > sphere.n_points
    cleaned = mesh.clean(merge_tol=1e-5)
    assert cleaned.n_points == sphere.n_points

    mesh.clean(merge_tol=1e-5, inplace=True)
    assert mesh.n_points == sphere.n_points

    cleaned = mesh.clean(point_merging=False)
    assert cleaned.n_points == mesh.n_points


def test_area(sphere_dense, cube_dense):
    radius = 0.5
    ideal_area = 4 * pi * radius**2
    assert np.isclose(sphere_dense.area, ideal_area, rtol=1e-3)

    ideal_area = 6 * np.cbrt(cube_dense.volume) ** 2
    assert np.isclose(cube_dense.area, ideal_area, rtol=1e-3)


def test_volume(sphere_dense):
    ideal_volume = (4 / 3.0) * pi * radius**3
    assert np.isclose(sphere_dense.volume, ideal_volume, rtol=1e-3)


@skip_plotting
def test_plot_boundaries():
    # make sure to plot an object that has boundaries
    pyvista.Cube().plot_boundaries(off_screen=True)


@skip_plotting
@pytest.mark.parametrize('flip', [True, False])
@pytest.mark.parametrize('faces', [True, False])
def test_plot_normals(sphere, flip, faces):
    sphere.plot_normals(off_screen=True, flip=flip, faces=faces)


def test_remove_points_any(sphere):
    remove_mask = np.zeros(sphere.n_points, np.bool_)
    remove_mask[:3] = True
    sphere_mod, ind = sphere.remove_points(remove_mask, inplace=False, mode='any')
    assert (sphere_mod.n_points + remove_mask.sum()) == sphere.n_points
    assert np.allclose(sphere_mod.points, sphere.points[ind])


def test_remove_points_all(sphere):
    sphere_copy = sphere.copy()
    sphere_copy.cell_data['ind'] = np.arange(sphere_copy.n_faces)
    remove = sphere.faces[1:4]
    sphere_copy.remove_points(remove, inplace=True, mode='all')
    assert sphere_copy.n_points == sphere.n_points
    assert sphere_copy.n_faces == sphere.n_faces - 1


def test_remove_points_fail(sphere, plane):
    # not triangles:
    with pytest.raises(NotAllTrianglesError):
        plane.remove_points([0])

    # invalid bool mask size
    with pytest.raises(ValueError):
        sphere.remove_points(np.ones(10, np.bool_))

    # invalid mask type
    with pytest.raises(TypeError):
        sphere.remove_points([0.0])


def test_vertice_cells_on_read(tmpdir):
    point_cloud = pyvista.PolyData(np.random.rand(100, 3))
    filename = str(tmpdir.mkdir("tmpdir").join('foo.ply'))
    point_cloud.save(filename)
    recovered = pyvista.read(filename)
    assert recovered.n_cells == 100
    recovered = pyvista.PolyData(filename)
    assert recovered.n_cells == 100


def test_center_of_mass(sphere):
    assert np.allclose(sphere.center_of_mass(), [0, 0, 0])
    cloud = pyvista.PolyData(np.random.rand(100, 3))
    assert len(cloud.center_of_mass()) == 3
    cloud['weights'] = np.random.rand(cloud.n_points)
    center = cloud.center_of_mass(True)
    assert len(center) == 3


def test_project_points_to_plane():
    # Define a simple Gaussian surface
    n = 20
    x = np.linspace(-200, 200, num=n) + np.random.uniform(-5, 5, size=n)
    y = np.linspace(-200, 200, num=n) + np.random.uniform(-5, 5, size=n)
    xx, yy = np.meshgrid(x, y)
    A, b = 100, 100
    zz = A * np.exp(-0.5 * ((xx / b) ** 2.0 + (yy / b) ** 2.0))
    poly = pyvista.StructuredGrid(xx, yy, zz).extract_geometry(progress_bar=True)
    poly['elev'] = zz.ravel(order='f')

    # Wrong normal length
    with pytest.raises(TypeError):
        poly.project_points_to_plane(normal=(0, 0, 1, 1))
    # allow Sequence but not Iterable
    with pytest.raises(TypeError):
        poly.project_points_to_plane(normal={0, 1, 2})

    # Test the filter
    projected = poly.project_points_to_plane(origin=poly.center, normal=(0, 0, 1))
    assert np.allclose(projected.points[:, -1], poly.center[-1])
    projected = poly.project_points_to_plane(normal=(0, 1, 1))
    assert projected.n_points

    # finally, test inplace
    poly.project_points_to_plane(normal=(0, 1, 1), inplace=True)
    assert np.allclose(poly.points, projected.points)


def test_tube(spline):
    # Simple
    line = pyvista.Line()
    tube = line.tube(n_sides=2, progress_bar=True)
    assert tube.n_points, tube.n_cells

    # inplace
    line.tube(n_sides=2, inplace=True, progress_bar=True)
    assert np.allclose(line.points, tube.points)

    # Complicated
    tube = spline.tube(radius=0.5, scalars='arc_length', progress_bar=True)
    assert tube.n_points, tube.n_cells

    with pytest.raises(TypeError):
        spline.tube(scalars=range(10))


def test_smooth_inplace(sphere):
    orig_pts = sphere.points.copy()
    sphere.smooth(inplace=True, progress_bar=True)
    assert not np.allclose(orig_pts, sphere.points)


def test_delaunay_2d():
    n = 20
    x = np.linspace(-200, 200, num=n) + np.random.uniform(-5, 5, size=n)
    y = np.linspace(-200, 200, num=n) + np.random.uniform(-5, 5, size=n)
    xx, yy = np.meshgrid(x, y)
    A, b = 100, 100
    zz = A * np.exp(-0.5 * ((xx / b) ** 2.0 + (yy / b) ** 2.0))
    # Get the points as a 2D NumPy array (N by 3)
    points = np.c_[xx.reshape(-1), yy.reshape(-1), zz.reshape(-1)]
    pdata = pyvista.PolyData(points)
    surf = pdata.delaunay_2d(progress_bar=True)
    # Make sure we have an all triangle mesh now
    assert np.all(surf.faces.reshape((-1, 4))[:, 0] == 3)

    # test inplace
    pdata.delaunay_2d(inplace=True, progress_bar=True)
    assert np.allclose(pdata.points, surf.points)


def test_lines():
    theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
    z = np.linspace(-2, 2, 100)
    r = z**2 + 1
    x = r * np.sin(theta)
    y = r * np.cos(theta)
    points = np.column_stack((x, y, z))
    # Create line segments
    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_)
    poly.lines = cells
    assert poly.n_points == len(points)
    assert poly.n_cells == len(points) - 1
    # Create a poly line
    poly = pyvista.PolyData()
    poly.points = points
    the_cell = np.arange(0, len(points), dtype=np.int_)
    the_cell = np.insert(the_cell, 0, len(points))
    poly.lines = the_cell
    assert poly.n_points == len(points)
    assert poly.n_cells == 1


def test_strips():
    # init with strips test
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 0.5, 0], [0, 0.5, 0]])
    strips = np.array([4, 0, 1, 3, 2])
    strips_init = pyvista.PolyData(vertices, strips=strips)
    assert len(strips_init.strips) == len(strips)

    # add strips using the setter
    strips_setter = pyvista.PolyData(vertices)
    strips_setter.strips = strips
    assert len(strips_setter.strips) == len(strips)

    # test n_strips function
    strips = np.array([[4, 0, 1, 3, 2], [4, 1, 2, 3, 0]])
    strips_stack = np.hstack(strips)
    n_strips_test = pyvista.PolyData(vertices, strips=strips_stack)
    assert n_strips_test.n_strips == len(strips)


def test_ribbon_filter():
    line = examples.load_spline().compute_arc_length(progress_bar=True)
    ribbon = line.ribbon(width=0.5, scalars='arc_length')
    assert ribbon.n_points

    for tcoords in [True, 'lower', 'normalized', False]:
        ribbon = line.ribbon(width=0.5, tcoords=tcoords)
        assert ribbon.n_points


def test_is_all_triangles():
    # mesh points
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0.5, 0.5, -1]])

    # mesh faces
    faces = np.hstack([[4, 0, 1, 2, 3], [3, 0, 1, 4], [3, 1, 2, 4]])  # [square, triangle, triangle]

    mesh = pyvista.PolyData(vertices, faces)
    assert not mesh.is_all_triangles
    mesh = mesh.triangulate()
    assert mesh.is_all_triangles
    # for backwards compatibility, check if we can call this
    assert mesh.is_all_triangles()


def test_extrude():
    arc = pyvista.CircularArc([-1, 0, 0], [1, 0, 0], [0, 0, 0])
    poly = arc.extrude([0, 0, 1], progress_bar=True, capping=True)
    assert poly.n_points
    assert poly.n_cells
    assert np.any(poly.strips)

    n_points_old = arc.n_points
    arc.extrude([0, 0, 1], inplace=True, capping=True)
    assert arc.n_points != n_points_old


def test_extrude_capping_warnings():
    arc = pyvista.CircularArc([-1, 0, 0], [1, 0, 0], [0, 0, 0])
    with pytest.warns(PyVistaFutureWarning, match='default value of the ``capping`` keyword'):
        arc.extrude([0, 0, 1])
    with pytest.warns(PyVistaFutureWarning, match='default value of the ``capping`` keyword'):
        arc.extrude_rotate()


def test_flip_normals(sphere, plane):
    sphere_flipped = sphere.copy()
    sphere_flipped.flip_normals()

    sphere.compute_normals(inplace=True)
    sphere_flipped.compute_normals(inplace=True)
    assert np.allclose(sphere_flipped.point_data['Normals'], -sphere.point_data['Normals'])

    # invalid case
    with pytest.raises(NotAllTrianglesError):
        plane.flip_normals()


def test_n_verts():
    mesh = pyvista.PolyData([[1.0, 0.0, 0.0], [1.0, 1.0, 1.0]])
    assert mesh.n_verts == 2


def test_n_lines():
    mesh = pyvista.Line()
    assert mesh.n_lines == 1


@pytest.mark.needs_vtk9
def test_geodesic_disconnected(sphere, sphere_shifted):
    # the sphere and sphere_shifted are disconnected - no path between them
    combined = sphere + sphere_shifted
    start_vertex = 0
    end_vertex = combined.n_points - 1
    match = f"There is no path between vertices {start_vertex} and {end_vertex}."

    with pytest.raises(ValueError, match=match):
        combined.geodesic(start_vertex, end_vertex)

    with pytest.raises(ValueError, match=match):
        combined.geodesic_distance(start_vertex, end_vertex)


# tests/test_property.py

import pytest

import pyvista as pv


@pytest.fixture()
def prop():
    return pv.Property()


def test_property_init():
    prop = pv.Property()

    # copy but equal
    assert prop._theme is not pv.global_theme
    assert prop._theme == pv.global_theme


def test_property_style(prop):
    style = 'Surface'
    prop.style = style
    assert prop.style == style


def test_property_edge_color(prop):
    prop.edge_color = 'b'
    assert prop.edge_color.float_rgb == (0, 0, 1)


def test_property_opacity(prop):
    opacity = 0.5
    prop.opacity = opacity
    assert prop.opacity == opacity


def test_property_show_edges(prop):
    value = False
    prop.show_edges = value
    assert prop.show_edges == value


def test_property_lighting(prop):
    value = False
    prop.lighting = value
    assert prop.lighting == value


def test_property_ambient(prop):
    value = 0.45
    prop.ambient = value
    assert prop.ambient == value


def test_property_diffuse(prop):
    value = 0.5
    prop.diffuse = value
    assert prop.diffuse == value


def test_property_specular(prop):
    value = 0.5
    prop.specular = value
    assert prop.specular == value


def test_property_specular_power(prop):
    value = 0.5
    prop.specular_power = value
    assert prop.specular_power == value


def test_property_metallic(prop):
    value = 0.1
    if pv.vtk_version_info < (9,):
        with pytest.raises(pv.core.errors.VTKVersionError):
            prop.metallic = value
        return
    prop.metallic = value
    assert prop.metallic == value


def test_property_roughness(prop):
    value = 0.1
    if pv.vtk_version_info < (9,):
        with pytest.raises(pv.core.errors.VTKVersionError):
            prop.roughness = value
        return
    prop.roughness = value
    assert prop.roughness == value


def test_property_interpolation(prop):
    value = 'Gouraud'
    prop.interpolation = value
    assert prop.interpolation == value

    with pytest.raises(TypeError, match='`interpolation`'):
        prop.interpolation = 1
    with pytest.raises(ValueError, match='Should be one of'):
        prop.interpolation = 'foo'


def test_property_render_points_as_spheres(prop):
    value = True
    prop.render_points_as_spheres = value
    assert prop.render_points_as_spheres is value


def test_property_render_lines_as_tubes(prop):
    value = True
    prop.render_lines_as_tubes = value
    assert prop.render_lines_as_tubes is value


def test_property_point_size(prop):
    value = 10.0
    prop.point_size = value
    assert prop.point_size == value


def test_property_line_width(prop):
    assert isinstance(prop.line_width, float)
    value = 10.0
    prop.line_width = value
    assert prop.line_width == value


@pytest.mark.parametrize("value", ['back', 'front', 'none'])
def test_property_culling(prop, value):
    prop.culling = value
    assert prop.culling == value

    with pytest.raises(ValueError, match='Invalid culling'):
        prop.culling = 'foo'


def test_property_diffuse_color(prop):
    prop.diffuse_color = 'b'
    assert prop.diffuse_color.float_rgb == (0, 0, 1)


def test_property_ambient_color(prop):
    prop.ambient_color = 'b'
    assert prop.ambient_color.float_rgb == (0, 0, 1)


def test_property_specular_color(prop):
    prop.specular_color = 'b'
    assert prop.specular_color.float_rgb == (0, 0, 1)


def test_property_anisotropy(prop):
    value = 0.1
    if pv.vtk_version_info < (9, 1, 0):
        with pytest.raises(pv.core.errors.VTKVersionError):
            prop.anisotropy = value
        return

    assert isinstance(prop.anisotropy, float)
    prop.anisotropy = value
    assert prop.anisotropy == value


# tests/test_pyvista_ndarray.py

from unittest import mock

import numpy as np
import pytest
import vtk as _vtk

from pyvista import examples, pyvista_ndarray


@pytest.fixture
def pv_ndarray_1d():
    return pyvista_ndarray([1.0, 2.0, 3.0])


def test_slices_are_associated():
    dataset = examples.load_structured()
    points = pyvista_ndarray(dataset.GetPoints().GetData(), dataset=dataset)

    # check that slices of pyvista_ndarray are associated correctly
    assert points[1, :].VTKObject == points.VTKObject
    assert points[1, :].dataset.Get() == points.dataset.Get()
    assert points[1, :].association == points.association


def test_copies_are_not_associated():
    dataset = examples.load_structured()
    points = pyvista_ndarray(dataset.GetPoints().GetData(), dataset=dataset)
    points_2 = points.copy()

    # check that copies of pyvista_ndarray are dissociated from the original dataset
    assert points_2.VTKObject is None
    assert points_2.dataset is None
    assert points_2.association.name == 'NONE'
    assert not np.shares_memory(points, points_2)


def test_modifying_modifies_dataset():
    dataset = examples.load_structured()
    points = pyvista_ndarray(dataset.GetPoints().GetData(), dataset=dataset)

    dataset_modified = mock.Mock()
    array_modified = mock.Mock()
    dataset.AddObserver(_vtk.vtkCommand.ModifiedEvent, dataset_modified)
    points.AddObserver(_vtk.vtkCommand.ModifiedEvent, array_modified)

    # __setitem__ calls dataset.Modified() and points.Modified()
    points[:] *= 0.5
    assert dataset_modified.call_count == 1
    assert array_modified.call_count == 1

    # __setitem__ with single-indices works does same
    points[0, 0] = 0.5
    assert dataset_modified.call_count == 2
    assert array_modified.call_count == 2

    # setting all new points calls dataset.Modified()
    dataset.points = points.copy()
    assert dataset_modified.call_count == 3
    assert array_modified.call_count == 2


# TODO: This currently doesn't work for single element indexing operations!
# in these cases, the __array_finalize__ method is not called
@pytest.mark.skip
def test_slices_are_associated_single_index():
    dataset = examples.load_structured()
    points = pyvista_ndarray(dataset.GetPoints().GetData(), dataset=dataset)

    assert points[1, 1].VTKObject == points.VTKObject
    assert points[1, 1].dataset.Get() == points.dataset.Get()
    assert points[1, 1].association == points.association


def test_min(pv_ndarray_1d):
    arr = np.array(pv_ndarray_1d)
    assert pv_ndarray_1d.min() == arr.min()

    # also ensure that methods return float-like values just like numpy
    assert isinstance(pv_ndarray_1d.min(), type(arr.min()))


def test_squeeze(pv_ndarray_1d):
    reshaped_pvarr = pv_ndarray_1d.reshape((3, 1))
    assert np.array_equal(reshaped_pvarr.squeeze(), np.array(reshaped_pvarr.squeeze()))


def test_tobytes(pv_ndarray_1d):
    assert pv_ndarray_1d.tobytes() == np.array(pv_ndarray_1d).tobytes()


def test_add_1d():
    # ensure that 1d single value arrays match numpy
    pv_arr = pyvista_ndarray([1]) + pyvista_ndarray([1])
    np_arr = np.array([1]) + np.array([1])
    assert np.array_equal(pv_arr, np_arr)


# tests/test_regression.py

import pytest

import pyvista as pv
from pyvista.plotting import system_supports_plotting

# skip all tests if unable to render
if not system_supports_plotting():
    pytestmark = pytest.mark.skip


def test_compare_images_two_plotters_same(sphere, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.png'))
    pl1 = pv.Plotter()
    pl1.add_mesh(sphere)
    arr1 = pl1.screenshot(filename)
    im1 = pv.read(filename)

    pl2 = pv.Plotter()
    pl2.add_mesh(sphere)

    assert not pv.compare_images(pl1, pl2)
    assert not pv.compare_images(arr1, pl2)
    assert not pv.compare_images(im1, pl2)
    assert not pv.compare_images(filename, pl2)
    assert not pv.compare_images(arr1, pl2, use_vtk=True)

    with pytest.raises(TypeError):
        pv.compare_images(im1, pl1.ren_win)

    # test that this fails when the plotter is closed
    pl1.close()
    with pytest.raises(RuntimeError, match='already been closed'):
        pv.compare_images(pl1, pl2)


def test_compare_images_two_plotter_different(sphere, airplane, tmpdir):
    tmppath = tmpdir.mkdir("tmpdir")
    filename = str(tmppath.join('tmp.png'))
    filename2 = str(tmppath.join('tmp2.png'))
    pl1 = pv.Plotter()
    pl1.add_mesh(sphere)
    arr1 = pl1.screenshot(filename)
    im1 = pv.read(filename)

    pl2 = pv.Plotter()
    pl2.add_mesh(airplane)
    arr2 = pl2.screenshot(filename2)
    im2 = pv.read(filename2)

    assert pv.compare_images(arr1, pl2) > 10000
    assert pv.compare_images(arr1, arr2) > 10000

    assert pv.compare_images(pl1, pl2) > 10000

    assert pv.compare_images(im1, pl2) > 10000
    assert pv.compare_images(im1, im2) > 10000

    assert pv.compare_images(filename, pl2) > 10000
    assert pv.compare_images(filename, filename2) > 10000

    assert pv.compare_images(arr1, pl2, use_vtk=True) > 10000

    with pytest.raises(TypeError):
        pv.compare_images(im1, pl1.ren_win)


# tests/test_render_pass.py

import pytest

import pyvista as pv
from pyvista import _vtk
from pyvista.plotting.render_passes import RenderPasses


# this ideally would be a fixture, but if it's a fixture the renderer object
# collects immediately since RenderPasses only holds a weakref
def make_passes():
    ren = _vtk.vtkRenderer()
    passes = RenderPasses(ren)
    return ren, passes


def test_render_passes_init():
    ren, passes = make_passes()
    assert passes._renderer is ren
    del ren

    # ensure renderer is collected
    assert passes._renderer is None


def test_blur_pass():
    ren, passes = make_passes()
    assert not passes._blur_passes
    blur_pass = passes.add_blur_pass()
    assert isinstance(blur_pass, _vtk.vtkGaussianBlurPass)
    assert len(passes._blur_passes) == 1

    passes.remove_blur_pass()
    assert not passes._blur_passes

    # double pass should work
    blur_pass = passes.add_blur_pass()
    blur_pass = passes.add_blur_pass()
    assert len(passes._blur_passes) == 2


def test_ssaa_pass():
    ren, passes = make_passes()
    assert not passes._passes
    ssaa_pass = passes.enable_ssaa_pass()
    assert isinstance(ssaa_pass, _vtk.vtkSSAAPass)
    assert list(passes._passes.keys()).count('vtkSSAAPass') == 1

    # enabling again should not add the pass again
    ssaa_pass = passes.enable_ssaa_pass()
    assert list(passes._passes.keys()).count('vtkSSAAPass') == 1

    passes.disable_ssaa_pass()
    assert not passes._passes

    # disabling again should just do nothing
    passes.disable_ssaa_pass()
    assert not passes._passes


def test_depth_of_field_pass():
    ren, passes = make_passes()
    assert not passes._passes
    ren_pass = passes.enable_depth_of_field_pass()
    assert isinstance(ren_pass, _vtk.vtkDepthOfFieldPass)
    assert list(passes._passes.keys()).count('vtkDepthOfFieldPass') == 1

    # enabling again should not add the pass again
    ren_pass = passes.enable_depth_of_field_pass()
    assert list(passes._passes.keys()).count('vtkDepthOfFieldPass') == 1

    passes.disable_depth_of_field_pass()
    assert not passes._passes

    # disabling again should just do nothing
    passes.disable_depth_of_field_pass()
    assert not passes._passes


@pytest.mark.needs_vtk9
def test_depth_of_field_raise_no_ssao():
    ren, passes = make_passes()
    passes.enable_ssao_pass(0.5, 0.005, 16, False)
    with pytest.raises(RuntimeError, match='Depth of field pass is incompatible'):
        passes.enable_depth_of_field_pass()


@pytest.mark.needs_vtk9
def test_ssao_raise_no_depth_of_field():
    ren, passes = make_passes()
    passes.enable_depth_of_field_pass()
    with pytest.raises(RuntimeError, match='SSAO pass is incompatible'):
        passes.enable_ssao_pass(0.5, 0.005, 16, False)


def test_shadow_pass():
    ren, passes = make_passes()
    ren_pass = passes.enable_shadow_pass()
    assert isinstance(ren_pass, _vtk.vtkShadowMapPass)

    assert passes._pass_collection.IsItemPresent(ren_pass)

    passes.disable_shadow_pass()
    assert not passes._pass_collection.IsItemPresent(ren_pass)


def test_edl_pass():
    ren, passes = make_passes()
    assert not passes._passes
    ren_pass = passes.enable_edl_pass()
    assert isinstance(ren_pass, _vtk.vtkEDLShading)
    assert list(passes._passes.keys()).count('vtkEDLShading') == 1

    # enabling again should just not add the pass again
    ren_pass = passes.enable_edl_pass()
    assert list(passes._passes.keys()).count('vtkEDLShading') == 1

    passes.disable_edl_pass()
    assert not passes._passes

    # disabling again should just do nothing
    passes.disable_edl_pass()
    assert not passes._passes


def test_ssao_pass():
    ren, passes = make_passes()
    assert not passes._passes
    if pv.vtk_version_info < (9,):
        with pytest.raises(pv.core.errors.VTKVersionError):
            ren_pass = passes.enable_ssao_pass(0.5, 0.005, 16, False)
        return

    ren_pass = passes.enable_ssao_pass(0.5, 0.005, 16, False)
    assert isinstance(ren_pass, _vtk.vtkSSAOPass)
    assert list(passes._passes.keys()).count('vtkSSAOPass') == 1

    # enabling again should just not add the pass again
    ren_pass = passes.enable_ssao_pass(0.5, 0.005, 16, False)
    assert list(passes._passes.keys()).count('vtkSSAOPass') == 1

    passes.disable_ssao_pass()
    assert not passes._passes

    # disabling again should just do nothing
    passes.disable_ssao_pass()
    assert not passes._passes


def test_render_passes_deep_clean():
    ren, passes = make_passes()
    passes.add_blur_pass()
    passes.enable_depth_of_field_pass()
    passes.enable_edl_pass()
    passes.enable_shadow_pass()
    passes.enable_ssaa_pass()

    passes.deep_clean()
    del ren
    assert passes._renderer is None

    assert passes._RenderPasses__seq_pass is None
    assert passes._RenderPasses__pass_collection is None
    assert passes._RenderPasses__camera_pass is None
    assert passes._passes == {}
    assert passes._shadow_map_pass is None
    assert passes._edl_pass is None
    assert passes._dof_pass is None
    assert passes._ssaa_pass is None
    assert passes._blur_passes == []


# tests/test_render_window_interactor.py

"""Test render window interactor"""

import pytest

import pyvista
from pyvista import _vtk
from pyvista.plotting import system_supports_plotting

skip_no_plotting = pytest.mark.skipif(
    not system_supports_plotting(), reason="Requires system to support plotting"
)


def empty_callback():
    return


@pytest.mark.needs_vtk_version(9, 1)
def test_observers():
    pl = pyvista.Plotter()

    # Key events
    with pytest.raises(TypeError):
        pl.add_key_event('w', 1)

    key = 'w'
    pl.add_key_event(key, empty_callback)
    assert key in pl.iren._key_press_event_callbacks
    pl.clear_events_for_key(key)
    assert key not in pl.iren._key_press_event_callbacks

    # Custom events
    assert not pl.iren.interactor.HasObserver(
        "PickEvent"
    ), "Subsequent PickEvent HasObserver tests are wrong if this fails."
    # Add different observers
    obs_move = pl.iren.add_observer(_vtk.vtkCommand.MouseMoveEvent, empty_callback)
    obs_double1 = pl.iren.add_observer(_vtk.vtkCommand.LeftButtonDoubleClickEvent, empty_callback)
    obs_double2 = pl.iren.add_observer("LeftButtonDoubleClickEvent", empty_callback)
    obs_picks = tuple(pl.iren.add_observer("PickEvent", empty_callback) for _ in range(5))
    pl.iren.add_observer("SelectionChangedEvent", empty_callback)
    assert pl.iren._observers[obs_move] == "MouseMoveEvent"
    assert pl.iren.interactor.HasObserver("MouseMoveEvent")
    assert pl.iren._observers[obs_double1] == "LeftButtonDoubleClickEvent"
    assert pl.iren._observers[obs_double2] == "LeftButtonDoubleClickEvent"
    assert pl.iren.interactor.HasObserver("LeftButtonDoubleClickEvent")
    assert all(pl.iren._observers[obs_pick] == "PickEvent" for obs_pick in obs_picks)
    assert pl.iren.interactor.HasObserver("SelectionChangedEvent")
    # Remove a specific observer
    pl.iren.remove_observer(obs_move)
    assert obs_move not in pl.iren._observers
    # Remove all observers of a specific event
    pl.iren.remove_observers(_vtk.vtkCommand.LeftButtonDoubleClickEvent)
    assert obs_double1 not in pl.iren._observers and obs_double2 not in pl.iren._observers
    # Remove all (remaining) observers
    pl.iren.remove_observers()
    assert len(pl.iren._observers) == 0
    assert not pl.iren.interactor.HasObserver("PickEvent")


def test_clear_key_event_callbacks():
    pl = pyvista.Plotter()
    pl.reset_key_events()


@skip_no_plotting
def test_track_mouse_position():
    pl = pyvista.Plotter()
    pl.track_mouse_position()
    pl.show(auto_close=False)
    assert pl.mouse_position is None
    x, y = 10, 20
    pl.iren._mouse_move(x, y)
    assert pl.mouse_position == (x, y)

    pl.iren.untrack_mouse_position()
    assert "MouseMoveEvent" not in pl.iren._observers.values()


@skip_no_plotting
def test_track_click_position_multi_render():
    points = []

    def callback(mouse_point):
        points.append(mouse_point)

    pl = pyvista.Plotter()
    with pytest.raises(TypeError):
        pl.track_click_position(side='dark')

    pl.track_click_position(callback=callback, side='left', viewport=True)
    pl.show(auto_close=False)
    x, y = 10, 20
    pl.iren._mouse_right_button_click(2 * x, 2 * y)
    pl.iren._mouse_left_button_click(x, y)
    assert points[0] == (x, y)

    # disable and ensure that clicking is no longer being tracked
    pl.untrack_click_position(side='left')
    pl.iren._mouse_left_button_click(50, 50)
    assert len(points) == 1


# tests/test_renderer.py

import pytest
from pytest import raises

import pyvista
from pyvista.plotting import system_supports_plotting
from pyvista.plotting.renderer import ACTOR_LOC_MAP


def test_show_bounds_axes_ranges():
    plotter = pyvista.Plotter()

    # test empty call
    plotter.show_bounds()
    cube_axes_actor = plotter.renderer.cube_axes_actor
    assert cube_axes_actor.GetBounds() == tuple(plotter.bounds)

    # send bounds but no axes ranges
    bounds = (0, 1, 0, 1, 0, 1)
    plotter.show_bounds(bounds=bounds)
    cube_axes_actor = plotter.renderer.cube_axes_actor
    assert cube_axes_actor.GetBounds() == bounds

    # send bounds and axes ranges
    axes_ranges = [0, 1, 0, 2, 0, 3]
    plotter.show_bounds(bounds=bounds, axes_ranges=axes_ranges)
    cube_axes_actor = plotter.renderer.cube_axes_actor
    assert cube_axes_actor.GetBounds() == bounds
    test_ranges = [
        *cube_axes_actor.GetXAxisRange(),
        *cube_axes_actor.GetYAxisRange(),
        *cube_axes_actor.GetZAxisRange(),
    ]
    assert test_ranges == axes_ranges


def test_show_bounds_invalid_axes_ranges():
    plotter = pyvista.Plotter()

    # send incorrect axes_ranges types
    with raises(TypeError, match='numeric sequence'):
        axes_ranges = 1
        plotter.show_bounds(axes_ranges=axes_ranges)

    with raises(TypeError, match='All of the elements'):
        axes_ranges = [0, 1, 'a', 'b', 2, 3]
        plotter.show_bounds(axes_ranges=axes_ranges)

    with raises(ValueError, match='[xmin, xmax, ymin, max, zmin, zmax]'):
        axes_ranges = [0, 1, 2, 3, 4]
        plotter.show_bounds(axes_ranges=axes_ranges)


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_camera_position():
    plotter = pyvista.Plotter()
    plotter.add_mesh(pyvista.Sphere())
    plotter.show()
    assert isinstance(plotter.camera_position, pyvista.CameraPosition)


@pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def test_plotter_camera_position():
    plotter = pyvista.Plotter()
    plotter.set_position([1, 1, 1], render=True)


def test_renderer_set_viewup():
    plotter = pyvista.Plotter()
    plotter.renderer.set_viewup([1, 1, 1])


def test_reset_camera():
    plotter = pyvista.Plotter()
    plotter.reset_camera(bounds=(-1, 1, -1, 1, -1, 1))


def test_layer():
    plotter = pyvista.Plotter()
    plotter.renderer.layer = 1
    assert plotter.renderer.layer == 1
    plotter.renderer.layer = 0
    assert plotter.renderer.layer == 0


@pytest.mark.parametrize('has_border', (True, False))
def test_border(has_border):
    border_color = (1.0, 1.0, 1.0)
    border_width = 1
    plotter = pyvista.Plotter(
        border=has_border, border_color=border_color, border_width=border_width
    )
    assert plotter.renderer.has_border is has_border

    if has_border:
        assert plotter.renderer.border_color == border_color
    else:
        assert plotter.renderer.border_color is None

    if has_border:
        assert plotter.renderer.border_width == border_width
    else:
        assert plotter.renderer.border_width == 0


def test_bad_legend_origin_and_size(sphere):
    """Ensure bad parameters to origin/size raise ValueErrors."""
    plotter = pyvista.Plotter()
    plotter.add_mesh(sphere)
    legend_labels = [['sphere', 'r']]
    with pytest.raises(ValueError, match='Invalid loc'):
        plotter.add_legend(labels=legend_labels, loc='bar')
    with pytest.raises(ValueError, match='size'):
        plotter.add_legend(labels=legend_labels, size=[])
    # test non-sequences also raise
    with pytest.raises(ValueError, match='size'):
        plotter.add_legend(labels=legend_labels, size=type)


@pytest.mark.parametrize('loc', ACTOR_LOC_MAP)
def test_add_legend_loc(loc):
    pl = pyvista.Plotter()
    pl.add_mesh(pyvista.PolyData([0.0, 0.0, 0.0]), label='foo')
    legend = pl.add_legend(loc=loc)

    # note: this is only valid with the defaults:
    # border=0.05 and size=(0.2, 0.2)
    positions = {
        'upper right': (0.75, 0.75),
        'upper left': (0.05, 0.75),
        'lower left': (0.05, 0.05),
        'lower right': (0.75, 0.05),
        'center left': (0.05, 0.4),
        'center right': (0.75, 0.4),
        'lower center': (0.4, 0.05),
        'upper center': (0.4, 0.75),
        'center': (0.4, 0.4),
    }
    assert legend.GetPosition() == positions[loc]


def test_add_legend_no_face(sphere):
    pl = pyvista.Plotter()
    sphere.point_data["Z"] = sphere.points[:, 2]
    pl.add_mesh(sphere, scalars='Z', label='sphere')
    pl.add_legend(face=None)

    pl = pyvista.Plotter()
    pl.add_mesh(sphere)
    pl.add_legend(labels=[['sphere', 'k']], face=None)


def test_add_remove_legend(sphere):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, label='sphere')
    pl.add_legend()
    pl.remove_legend()


@pytest.mark.parametrize('face', ['-', '^', 'o', 'r', None, pyvista.PolyData([0.0, 0.0, 0.0])])
def test_legend_face(sphere, face):
    pl = pyvista.Plotter()
    pl.add_mesh(sphere, label='sphere')
    pl.add_legend(face=face)


# tests/test_scalar_bars.py

import pytest

import pyvista as pv
from pyvista import _vtk

KEY = 'Data'


@pytest.fixture()
def scalar_bars(sphere):
    sphere[KEY] = sphere.points[:, 2]

    plotter = pv.Plotter()
    plotter.add_mesh(sphere, show_scalar_bar=False)
    plotter.add_scalar_bar(
        KEY,
        interactive=True,
        vertical=False,
        outline=True,
        fmt='%10.5f',
        nan_annotation=True,
        fill=True,
        background_color='k',
    )
    return plotter.scalar_bars


def test_repr(scalar_bars):
    repr_ = repr(scalar_bars)
    assert f'"{KEY}"' in repr_
    assert 'False' in repr_ or 'True' in repr_, 'missing interactive flag'
    assert 'Scalar Bar Title     Interactive' in repr_


def test_remove_fail(scalar_bars):
    scalar_bars.add_scalar_bar('MOARDATA', mapper=scalar_bars._plotter.mapper)
    with pytest.raises(ValueError, match='Multiple scalar bars'):
        scalar_bars.remove_scalar_bar()


def test_add_fail(scalar_bars):
    with pytest.raises(ValueError, match='Mapper cannot be ``None``'):
        scalar_bars.add_scalar_bar('MOARDATA')


def test_dict(scalar_bars):
    assert KEY in scalar_bars
    assert 'Data' in scalar_bars.keys()
    assert len(scalar_bars) == 1
    assert list(scalar_bars.keys())[0] == KEY
    assert isinstance(list(scalar_bars.values())[0], _vtk.vtkScalarBarActor)

    for key, value in scalar_bars.items():
        assert isinstance(value, _vtk.vtkScalarBarActor)
        assert key == 'Data'

    assert isinstance(scalar_bars[KEY], _vtk.vtkScalarBarActor)


def test_clear(scalar_bars):
    assert len(scalar_bars) == 1
    scalar_bars.clear()
    assert len(scalar_bars) == 0


def test_actor_removal(sphere):
    # verify that when removing an actor we also remove the
    # corresponding scalar bar

    sphere['scalars'] = sphere.points[:, 2]

    pl = pv.Plotter()
    actor = pl.add_mesh(sphere, show_scalar_bar=True)
    assert len(pl.scalar_bars) == 1
    pl.remove_actor(actor)
    assert len(pl.scalar_bars) == 0


def test_add_remove_bar(sphere):
    pl = pv.Plotter()
    pl.add_mesh(sphere, scalars=sphere.points[:, 2], show_scalar_bar=False)

    # verify that the number of slots is restored
    init_slots = len(pl._scalar_bar_slots)
    pl.add_scalar_bar()
    pl.remove_scalar_bar()
    assert len(pl._scalar_bar_slots) == init_slots


def test_too_many_scalar_bars(sphere):
    pl = pv.Plotter()
    with pytest.raises(RuntimeError, match='Maximum number of color'):
        for i in range(100):
            mesh = pv.Sphere()
            mesh[str(i)] = range(mesh.n_points)
            pl.add_mesh(mesh)


# tests/test_scraper.py

import os
import os.path as op

import pytest

import pyvista
from pyvista.plotting import system_supports_plotting
from pyvista.utilities import Scraper

# skip all tests if unable to render
if not system_supports_plotting():
    pytestmark = pytest.mark.skip(reason='Requires system to support plotting')


def test_scraper(tmpdir):
    try:
        pyvista.BUILDING_GALLERY = True
        pytest.importorskip('sphinx_gallery')
        pyvista.close_all()
        plotter = pyvista.Plotter(off_screen=True)
        scraper = Scraper()
        src_dir = str(tmpdir)
        out_dir = op.join(str(tmpdir), '_build', 'html')
        img_fname = op.join(src_dir, 'auto_examples', 'images', 'sg_img.png')
        gallery_conf = {"src_dir": src_dir, "builder_name": "html"}
        target_file = op.join(src_dir, 'auto_examples', 'sg.py')
        block = None
        block_vars = dict(
            image_path_iterator=(img for img in [img_fname]),
            example_globals=dict(a=1),
            target_file=target_file,
        )
        os.makedirs(op.dirname(img_fname))
        assert not os.path.isfile(img_fname)
        os.makedirs(out_dir)
        scraper(block, block_vars, gallery_conf)
        assert os.path.isfile(img_fname)
        plotter.close()
    finally:
        pyvista.BUILDING_GALLERY = False


def test_scraper_raise(tmpdir):
    pytest.importorskip('sphinx_gallery')
    pyvista.close_all()
    plotter = pyvista.Plotter(off_screen=True)
    scraper = Scraper()
    src_dir = str(tmpdir)
    out_dir = op.join(str(tmpdir), '_build', 'html')
    img_fname = op.join(src_dir, 'auto_examples', 'images', 'sg_img.png')
    gallery_conf = {"src_dir": src_dir, "builder_name": "html"}
    target_file = op.join(src_dir, 'auto_examples', 'sg.py')
    block = None
    block_vars = dict(
        image_path_iterator=(img for img in [img_fname]),
        example_globals=dict(a=1),
        target_file=target_file,
    )
    os.makedirs(op.dirname(img_fname))
    assert not os.path.isfile(img_fname)
    os.makedirs(out_dir)

    with pytest.raises(RuntimeError, match="pyvista.BUILDING_GALLERY"):
        scraper(block, block_vars, gallery_conf)

    plotter.close()


# tests/test_theme.py

import pytest
import vtk

import pyvista
from pyvista import colors
from pyvista.utilities.misc import PyVistaDeprecationWarning


@pytest.fixture
def default_theme():
    return pyvista.themes.DefaultTheme()


def test_backwards_compatibility():
    try:
        color = (0.1, 0.4, 0.7)
        pyvista.rcParams['color'] = color
        assert pyvista.rcParams['color'] == pyvista.Color(color)

        # test nested values
        init_value = pyvista.rcParams['axes']['show']
        pyvista.rcParams['axes']['show'] = not init_value
        assert pyvista.rcParams['axes']['show'] is not init_value
    finally:
        # always return to testing theme
        pyvista.set_plot_theme('testing')


@pytest.mark.parametrize(
    'parm', [('enabled', True), ('occlusion_ratio', 0.5), ('number_of_peels', 2)]
)
def test_depth_peeling_config(default_theme, parm):
    attr, value = parm
    assert hasattr(default_theme.depth_peeling, attr)
    setattr(default_theme.depth_peeling, attr, value)
    assert getattr(default_theme.depth_peeling, attr) == value


def test_depth_peeling_eq(default_theme):
    my_theme = pyvista.themes.DefaultTheme()
    my_theme.depth_peeling.enabled = not my_theme.depth_peeling.enabled
    assert my_theme.depth_peeling != default_theme.depth_peeling
    assert my_theme.depth_peeling != 1


@pytest.mark.parametrize(
    'parm',
    [
        ('color', (0.1, 0.1, 0.1)),
        ('line_width', 1),
        ('opacity', 1.0),
        ('feature_angle', 20),
        ('decimate', 0.5),
    ],
)
def test_silhouette_config(default_theme, parm):
    attr, value = parm
    assert hasattr(default_theme.silhouette, attr)
    setattr(default_theme.silhouette, attr, value)
    assert getattr(default_theme.silhouette, attr) == value


def test_depth_silhouette_eq(default_theme):
    my_theme = pyvista.themes.DefaultTheme()
    my_theme.silhouette.opacity = 0.11111
    assert my_theme.silhouette != default_theme.silhouette
    assert my_theme.silhouette != 1


def test_depth_silhouette_opacity_outside_clamp(default_theme):
    my_theme = pyvista.themes.DefaultTheme()
    with pytest.raises(ValueError):
        my_theme.silhouette.opacity = 10
    with pytest.raises(ValueError):
        my_theme.silhouette.opacity = -1


@pytest.mark.parametrize(
    'parm',
    [
        ('slider_length', 0.03),
        ('slider_width', 0.02),
        ('slider_color', (0.5, 0.5, 0.3)),
        ('tube_width', 0.02),
        ('tube_color', (0.5, 0.5, 0.5)),
        ('cap_opacity', 0.5),
        ('cap_length', 0.02),
        ('cap_width', 0.04),
    ],
)
@pytest.mark.parametrize('style', ('modern', 'classic'))
def test_slider_style_config(default_theme, parm, style):
    attr, value = parm

    slider_style = getattr(default_theme.slider_styles, style)
    assert hasattr(slider_style, attr)
    setattr(slider_style, attr, value)
    assert getattr(slider_style, attr) == value


def test_slider_style_config_eq(default_theme):
    assert default_theme.slider_styles.modern != default_theme.slider_styles.classic
    assert default_theme.slider_styles.modern != 1


def test_slider_style_eq(default_theme):
    my_theme = pyvista.themes.DefaultTheme()
    my_theme.slider_styles.modern.slider_length *= 2
    assert default_theme.slider_styles != my_theme.slider_styles


def test_invalid_color_str_single_char():
    with pytest.raises(ValueError):
        colors.Color('x')


def test_color_str():
    clr = colors.Color("k")
    assert (0.0, 0.0, 0.0) == clr
    clr = colors.Color("black")
    assert (0.0, 0.0, 0.0) == clr
    clr = colors.Color("white")
    assert (1.0, 1.0, 1.0) == clr
    with pytest.raises(ValueError):
        colors.Color('not a color')


def test_font():
    font = pyvista.parse_font_family('times')
    assert font == vtk.VTK_TIMES
    with pytest.raises(ValueError):
        pyvista.parse_font_family('not a font')


def test_font_eq(default_theme):
    defa_theme = pyvista.themes.DefaultTheme()
    assert defa_theme.font == default_theme.font

    paraview_theme = pyvista.themes.ParaViewTheme()
    assert paraview_theme.font != default_theme.font
    assert paraview_theme.font != 1


def test_font_family(default_theme):
    font = 'courier'
    default_theme.font.family = font
    assert default_theme.font.family == font

    with pytest.raises(ValueError):
        default_theme.font.family = 'bla'


def test_font_title_size(default_theme):
    default_theme.font.title_size = None
    assert default_theme.font.title_size is None


def test_font_label_size(default_theme):
    default_theme.font.label_size = None
    assert default_theme.font.label_size is None


def test_font_fmt(default_theme):
    fmt = '%.6e'
    default_theme.font.fmt = fmt
    assert default_theme.font.fmt == fmt


def test_axes_eq(default_theme):
    assert default_theme.axes == pyvista.themes.DefaultTheme().axes

    theme = pyvista.themes.DefaultTheme()
    theme.axes.box = True
    assert default_theme.axes != theme.axes
    assert default_theme.axes != 1


def test_theme_wrong_type(default_theme):
    with pytest.raises(TypeError):
        default_theme.font = None
    with pytest.raises(TypeError):
        default_theme.colorbar_horizontal = None
    with pytest.raises(TypeError):
        default_theme.colorbar_vertical = None
    with pytest.raises(TypeError):
        default_theme.depth_peeling = None
    with pytest.raises(TypeError):
        default_theme.silhouette = None
    with pytest.raises(TypeError):
        default_theme.slider_styles = None
    with pytest.raises(TypeError):
        default_theme.slider_styles.classic = None
    with pytest.raises(TypeError):
        default_theme.slider_styles.modern = None
    with pytest.raises(TypeError):
        default_theme.axes = None


def test_axes_box(default_theme):
    new_value = not default_theme.axes.box
    default_theme.axes.box = new_value
    assert default_theme.axes.box == new_value


def test_axes_show(default_theme):
    new_value = not default_theme.axes.show
    default_theme.axes.show = new_value
    assert default_theme.axes.show == new_value


def test_colorbar_eq(default_theme):
    theme = pyvista.themes.DefaultTheme()
    assert default_theme.colorbar_horizontal == theme.colorbar_horizontal

    assert default_theme.colorbar_horizontal != 1
    assert default_theme.colorbar_horizontal != theme.colorbar_vertical


def test_colorbar_height(default_theme):
    height = 0.3
    default_theme.colorbar_horizontal.height = height
    assert default_theme.colorbar_horizontal.height == height


def test_colorbar_width(default_theme):
    width = 0.3
    default_theme.colorbar_horizontal.width = width
    assert default_theme.colorbar_horizontal.width == width


def test_colorbar_position_x(default_theme):
    position_x = 0.3
    default_theme.colorbar_horizontal.position_x = position_x
    assert default_theme.colorbar_horizontal.position_x == position_x


def test_colorbar_position_y(default_theme):
    position_y = 0.3
    default_theme.colorbar_horizontal.position_y = position_y
    assert default_theme.colorbar_horizontal.position_y == position_y


@pytest.mark.parametrize('theme', pyvista.themes._ALLOWED_THEMES)
def test_themes(theme):
    try:
        pyvista.set_plot_theme(theme.name)
        assert pyvista.global_theme == theme.value()
    finally:
        # always return to testing theme
        pyvista.set_plot_theme('testing')


def test_invalid_theme():
    with pytest.raises(KeyError):
        pyvista.set_plot_theme('this is not a valid theme')


def test_invalid_theme_type_error():
    with pytest.raises(TypeError):
        pyvista.set_plot_theme(1)


def test_set_theme():
    theme = pyvista.themes.DarkTheme()
    try:
        pyvista.set_plot_theme(theme)
        assert pyvista.global_theme == theme
    finally:
        # always return to testing theme
        pyvista.set_plot_theme('testing')


def test_invalid_load_theme(default_theme):
    with pytest.raises(TypeError):
        default_theme.load_theme(123)


def test_window_size(default_theme):
    with pytest.raises(ValueError):
        default_theme.window_size = [1, 2, 3]

    with pytest.raises(ValueError, match='Window size must be a positive value'):
        default_theme.window_size = [-1, -2]

    window_size = [1, 1]
    default_theme.window_size = window_size
    assert default_theme.window_size == window_size


def test_camera(default_theme):
    with pytest.raises(TypeError):
        default_theme.camera = [1, 0, 0]

    with pytest.raises(KeyError, match='Expected the "viewup"'):
        default_theme.camera = {'position': [1, 0, 0]}

    with pytest.raises(KeyError, match='Expected the "position"'):
        default_theme.camera = {'viewup': [1, 0, 0]}

    camera = {'position': [1, 0, 0], 'viewup': [1, 0, 0]}
    default_theme.camera = camera
    assert default_theme.camera == camera


def test_cmap(default_theme):
    cmap = 'jet'
    default_theme.cmap = cmap
    assert default_theme.cmap == cmap

    with pytest.raises(KeyError, match='not a color map'):
        default_theme.cmap = 'not a color map'


def test_volume_mapper(default_theme):
    assert hasattr(default_theme, 'volume_mapper')
    volume_mapper = 'gpu'
    default_theme.volume_mapper = volume_mapper
    assert default_theme.volume_mapper == volume_mapper

    with pytest.raises(ValueError, match='unknown'):
        default_theme.volume_mapper = 'invalid'


def test_set_hidden_line_removal(default_theme):
    default_theme.hidden_line_removal = True
    assert default_theme.hidden_line_removal is True
    default_theme.hidden_line_removal = False
    assert default_theme.hidden_line_removal is False


@pytest.mark.parametrize(
    'parm',
    [
        ('background', (0.1, 0.2, 0.3)),
        ('auto_close', False),
        ('notebook', False),
        ('full_screen', True),
        ('nan_color', (0.5, 0.5, 0.5)),
        ('edge_color', (1.0, 0.0, 0.0)),
        ('outline_color', (1.0, 0.0, 0.0)),
        ('floor_color', (1.0, 0.0, 0.0)),
        ('show_scalar_bar', False),
        ('lighting', False),
        ('interactive', False),
        ('render_points_as_spheres', True),
        ('transparent_background', True),
        ('title', 'test_title'),
        ('multi_samples', 10),
        ('multi_rendering_splitting_position', 0.1),
        ('smooth_shading', True),
        ('name', 'test_theme'),
        ('split_sharp_edges', True),
        ('sharp_edges_feature_angle', 45.0),
    ],
)
def test_theme_parm(default_theme, parm):
    attr, value = parm
    assert hasattr(default_theme, attr)
    setattr(default_theme, attr, value)
    assert getattr(default_theme, attr) == value


def test_theme_colorbar_orientation(default_theme):
    orient = 'vertical'
    default_theme.colorbar_orientation = orient
    assert default_theme.colorbar_orientation == orient

    with pytest.raises(ValueError):
        default_theme.colorbar_orientation = 'invalid'


def test_restore_defaults(default_theme):
    orig_value = default_theme.show_edges
    default_theme.show_edges = not orig_value
    default_theme.restore_defaults()
    assert default_theme.show_edges == orig_value


def test_repr(default_theme):
    rep = str(default_theme)
    assert 'Background' in rep
    assert default_theme.cmap in rep
    assert str(default_theme.colorbar_orientation) in rep
    assert default_theme._name.capitalize() in rep

    # verify that the key for each line in the repr is less than the minimum
    # key size. This makes sure that any new keys are either less than the size
    # of the key in the repr or the key length is increased
    for line in rep.splitlines():
        if ':' in line:
            pref, *rest = line.split(':', 1)
            assert pref.endswith(' '), f"Key str too long or need to raise key length:\n{pref!r}"


def test_theme_slots(default_theme):
    # verify we can't create an arbitrary attribute
    with pytest.raises(AttributeError, match='has no attribute'):
        default_theme.new_attr = 1


def test_theme_eq():
    defa_theme0 = pyvista.themes.DefaultTheme()
    defa_theme1 = pyvista.themes.DefaultTheme()
    assert defa_theme0 == defa_theme1
    dark_theme = pyvista.themes.DarkTheme()
    assert defa_theme0 != dark_theme

    # for coverage
    assert defa_theme0 != 'apple'


def test_plotter_set_theme():
    # test that the plotter theme is set to the new theme
    my_theme = pyvista.themes.DefaultTheme()
    my_theme.color = [1.0, 0.0, 0.0]
    pl = pyvista.Plotter(theme=my_theme)
    assert pl.theme.color == my_theme.color
    assert pyvista.global_theme.color != pl.theme.color

    pl = pyvista.Plotter()
    assert pl.theme == pyvista.global_theme
    pl.theme = my_theme
    assert pl.theme != pyvista.global_theme
    assert pl.theme == my_theme


def test_load_theme(tmpdir, default_theme):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.json'))
    pyvista.themes.DarkTheme().save(filename)
    loaded_theme = pyvista.load_theme(filename)
    assert loaded_theme == pyvista.themes.DarkTheme()

    default_theme.load_theme(filename)
    assert default_theme == pyvista.themes.DarkTheme()


def test_anti_aliasing(default_theme):
    # test backwards compatibility
    with pytest.warns(PyVistaDeprecationWarning, match='is now a string'):
        default_theme.anti_aliasing = True
        pl = pyvista.Plotter(theme=default_theme)
        assert pl.renderer.GetUseFXAA()

    with pytest.raises(ValueError, match='anti_aliasing must be either'):
        default_theme.anti_aliasing = 'invalid value'

    with pytest.raises(TypeError, match='must be either'):
        default_theme.anti_aliasing = 42


def test_anti_aliasing_fxaa(default_theme):
    default_theme.anti_aliasing = 'fxaa'
    assert default_theme.anti_aliasing == 'fxaa'
    pl = pyvista.Plotter(theme=default_theme)
    assert pl.renderer.GetUseFXAA()


def test_anti_aliasing_ssaa(default_theme):

    # default should is not enabled
    if default_theme.anti_aliasing != 'ssaa':
        pl = pyvista.Plotter(theme=default_theme)
        assert 'vtkSSAAPass' not in pl.renderer._render_passes._passes

    default_theme.anti_aliasing = 'ssaa'
    assert default_theme.anti_aliasing == 'ssaa'
    pl = pyvista.Plotter(theme=default_theme)
    assert 'vtkSSAAPass' in pl.renderer._render_passes._passes


def test_anti_aliasing_msaa(default_theme):
    if default_theme.anti_aliasing != 'msaa':
        pl = pyvista.Plotter(theme=default_theme)
        assert pl.ren_win.GetMultiSamples() == 0

    default_theme.anti_aliasing = 'msaa'
    default_theme.multi_samples = 4
    assert default_theme.anti_aliasing == 'msaa'
    pl = pyvista.Plotter(theme=default_theme)
    assert pl.ren_win.GetMultiSamples() == default_theme.multi_samples


def test_antialiasing_deprication(default_theme):
    with pytest.warns(PyVistaDeprecationWarning, match='anti_aliasing'):
        default_theme.antialiasing
    with pytest.warns(PyVistaDeprecationWarning, match='anti_aliasing'):
        default_theme.antialiasing = True


# tests/test_tinypages.py

"""Tests for tinypages build using sphinx extensions."""

import os
from pathlib import Path
from subprocess import PIPE, Popen
import sys

import pytest

from pyvista.plotting import system_supports_plotting

pytest.importorskip('sphinx')

# skip all tests if unable to render
if not system_supports_plotting():
    pytestmark = pytest.mark.skip(reason='Requires system to support plotting')


@pytest.mark.skipif(os.name == 'nt', reason='path issues on Azure Windows CI')
def test_tinypages(tmpdir):
    tmp_path = Path(tmpdir)
    html_dir = tmp_path / 'html'
    doctree_dir = tmp_path / 'doctrees'
    # Build the pages with warnings turned into errors
    cmd = [
        sys.executable,
        '-msphinx',
        '-W',
        '-b',
        'html',
        '-d',
        str(doctree_dir),
        str(Path(__file__).parent / 'tinypages'),
        str(html_dir),
    ]
    proc = Popen(
        cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, env={**os.environ, "MPLBACKEND": ""}
    )
    out, err = proc.communicate()

    assert proc.returncode == 0, f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n"

    if err:
        if err.strip() != 'vtkDebugLeaks has found no leaks.':
            pytest.fail(f"sphinx build emitted the following warnings:\n{err}")

    assert html_dir.is_dir()

    def plot_file(plt, num, subnum):
        return html_dir / f'some_plots-{plt}_{num:02d}_{subnum:02d}.png'

    # verify directives generating a figure generated figures
    assert plot_file(1, 0, 0).exists()
    assert plot_file(2, 0, 0).exists()
    assert plot_file(4, 0, 0).exists()
    assert plot_file(8, 0, 0).exists()
    assert plot_file(9, 0, 0).exists()
    assert plot_file(9, 1, 0).exists()

    # test skip directive
    assert not plot_file(10, 0, 0).exists()

    # verify external file generated figure
    cone_file = html_dir / 'plot_cone_00_00.png'
    assert cone_file.exists()

    html_contents = (html_dir / 'some_plots.html').read_bytes()
    assert b'# Only a comment' in html_contents

    # check if figure caption made it into html file
    assert b'This is the caption for plot 8.' in html_contents

    # check if figure caption using :caption: made it into html file
    assert b'Plot 10 uses the caption option.' in html_contents

    # check that the multi-image caption is applied twice
    assert html_contents.count(b'This caption applies to both plots.') == 2

    assert b'you should not be reading this right now' not in html_contents
    assert b'should be printed: include-source with no args' in html_contents


# tests/test_utilities.py

""" test pyvista.utilities """
import itertools
import os
import pathlib
import pickle
import shutil
import unittest.mock as mock
import warnings

import numpy as np
import pytest
import vtk

import pyvista
from pyvista import examples as ex
from pyvista.plotting import system_supports_plotting
from pyvista.utilities import (
    GPUInfo,
    Observer,
    cells,
    check_valid_vector,
    errors,
    fileio,
    get_ext,
    helpers,
    transformations,
)
from pyvista.utilities.misc import PyVistaDeprecationWarning, has_duplicates, raise_has_duplicates

skip_no_plotting = pytest.mark.skipif(
    not system_supports_plotting(), reason="Requires system to support plotting"
)


def test_version():
    assert "major" in str(pyvista.vtk_version_info)
    ver = vtk.vtkVersion()
    assert ver.GetVTKMajorVersion() == pyvista.vtk_version_info.major
    assert ver.GetVTKMinorVersion() == pyvista.vtk_version_info.minor
    assert ver.GetVTKBuildVersion() == pyvista.vtk_version_info.micro
    ver_tup = (
        ver.GetVTKMajorVersion(),
        ver.GetVTKMinorVersion(),
        ver.GetVTKBuildVersion(),
    )
    assert ver_tup == pyvista.vtk_version_info
    assert pyvista.vtk_version_info >= (0, 0, 0)


def test_createvectorpolydata_error():
    orig = np.random.random((3, 1))
    vec = np.random.random((3, 1))
    with pytest.raises(ValueError):
        helpers.vector_poly_data(orig, vec)


def test_createvectorpolydata_1D():
    orig = np.random.random(3)
    vec = np.random.random(3)
    vdata = helpers.vector_poly_data(orig, vec)
    assert np.any(vdata.points)
    assert np.any(vdata.point_data['vectors'])


def test_createvectorpolydata():
    orig = np.random.random((100, 3))
    vec = np.random.random((100, 3))
    vdata = helpers.vector_poly_data(orig, vec)
    assert np.any(vdata.points)
    assert np.any(vdata.point_data['vectors'])


@pytest.mark.parametrize(
    'path, target_ext',
    [
        ("/data/mesh.stl", ".stl"),
        ("/data/image.nii.gz", '.nii.gz'),
        ("/data/other.gz", ".gz"),
    ],
)
def test_get_ext(path, target_ext):
    ext = get_ext(path)
    assert ext == target_ext


@pytest.mark.parametrize('use_pathlib', [True, False])
def test_read(tmpdir, use_pathlib):
    fnames = (ex.antfile, ex.planefile, ex.hexbeamfile, ex.spherefile, ex.uniformfile, ex.rectfile)
    if use_pathlib:
        fnames = [pathlib.Path(fname) for fname in fnames]
    types = (
        pyvista.PolyData,
        pyvista.PolyData,
        pyvista.UnstructuredGrid,
        pyvista.PolyData,
        pyvista.UniformGrid,
        pyvista.RectilinearGrid,
    )
    for i, filename in enumerate(fnames):
        obj = fileio.read(filename)
        assert isinstance(obj, types[i])
    # Now test the standard_reader_routine
    for i, filename in enumerate(fnames):
        # Pass attrs to for the standard_reader_routine to be used
        with pytest.warns(PyVistaDeprecationWarning):
            obj = fileio.read(filename, attrs={'DebugOn': None})
        assert isinstance(obj, types[i])
    # this is also tested for each mesh types init from file tests
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.npy'))
    arr = np.random.rand(10, 10)
    np.save(filename, arr)
    with pytest.raises(IOError):
        _ = pyvista.read(filename)
    # read non existing file
    with pytest.raises(IOError):
        _ = pyvista.read('this_file_totally_does_not_exist.vtk')
    # Now test reading lists of files as multi blocks
    multi = pyvista.read(fnames)
    assert isinstance(multi, pyvista.MultiBlock)
    assert multi.n_blocks == len(fnames)
    nested = [ex.planefile, [ex.hexbeamfile, ex.uniformfile]]

    multi = pyvista.read(nested)
    assert isinstance(multi, pyvista.MultiBlock)
    assert multi.n_blocks == 2
    assert isinstance(multi[1], pyvista.MultiBlock)
    assert multi[1].n_blocks == 2


def test_read_force_ext(tmpdir):
    fnames = (ex.antfile, ex.planefile, ex.hexbeamfile, ex.spherefile, ex.uniformfile, ex.rectfile)
    types = (
        pyvista.PolyData,
        pyvista.PolyData,
        pyvista.UnstructuredGrid,
        pyvista.PolyData,
        pyvista.UniformGrid,
        pyvista.RectilinearGrid,
    )

    dummy_extension = '.dummy'
    for fname, type in zip(fnames, types):
        root, original_ext = os.path.splitext(fname)
        _, name = os.path.split(root)
        new_fname = tmpdir / name + '.' + dummy_extension
        shutil.copy(fname, new_fname)
        data = fileio.read(new_fname, force_ext=original_ext)
        assert isinstance(data, type)


@mock.patch('pyvista.BaseReader.read')
@mock.patch('pyvista.BaseReader.reader')
def test_read_attrs(mock_reader, mock_read):
    """Test passing attrs in read."""
    with pytest.warns(PyVistaDeprecationWarning):
        pyvista.read(ex.antfile, attrs={'test': 'test_arg'})
    mock_reader.test.assert_called_once_with('test_arg')

    mock_reader.reset_mock()
    with pytest.warns(PyVistaDeprecationWarning):
        pyvista.read(ex.antfile, attrs={'test': ['test_arg1', 'test_arg2']})
    mock_reader.test.assert_called_once_with('test_arg1', 'test_arg2')


@mock.patch('pyvista.BaseReader.read')
@mock.patch('pyvista.BaseReader.reader')
@mock.patch('pyvista.BaseReader.show_progress')
def test_read_progress_bar(mock_show_progress, mock_reader, mock_read):
    """Test passing attrs in read."""
    pyvista.read(ex.antfile, progress_bar=True)
    mock_show_progress.assert_called_once()


def test_read_force_ext_wrong_extension(tmpdir):
    # try to read a .vtu file as .vts
    # vtkXMLStructuredGridReader throws a VTK error about the validity of the XML file
    # the returned dataset is empty
    fname = tmpdir / 'airplane.vtu'
    ex.load_airplane().cast_to_unstructured_grid().save(fname)
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        data = fileio.read(fname, force_ext='.vts')
    assert data.n_points == 0

    # try to read a .ply file as .vtm
    # vtkXMLMultiBlockDataReader throws a VTK error about the validity of the XML file
    # the returned dataset is empty
    fname = ex.planefile
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        data = fileio.read(fname, force_ext='.vtm')
    assert len(data) == 0

    fname = ex.planefile
    with pytest.raises(IOError):
        fileio.read(fname, force_ext='.not_supported')


@mock.patch('pyvista.utilities.fileio.read')
def test_read_legacy(read_mock):
    with pytest.warns(PyVistaDeprecationWarning):
        pyvista.read_legacy(ex.globefile, progress_bar=False)
    read_mock.assert_called_once_with(ex.globefile, progress_bar=False)


@mock.patch('pyvista.utilities.fileio.read_exodus')
def test_pyvista_read_exodus(read_exodus_mock):
    # check that reading a file with extension .e calls `read_exodus`
    # use the globefile as a dummy because pv.read() checks for the existence of the file
    pyvista.read(ex.globefile, force_ext='.e')
    args, kwargs = read_exodus_mock.call_args
    filename = args[0]
    assert filename == ex.globefile


@pytest.mark.parametrize('auto_detect', (True, False))
@mock.patch('pyvista.utilities.reader.BaseReader.read')
@mock.patch('pyvista.utilities.reader.BaseReader.path')
def test_read_plot3d(path_mock, read_mock, auto_detect):
    # with grid only
    with pytest.warns(PyVistaDeprecationWarning):
        pyvista.read_plot3d(filename='grid.in', auto_detect=auto_detect)
    read_mock.assert_called_once()

    # with grid and q
    read_mock.reset_mock()
    with pytest.warns(PyVistaDeprecationWarning):
        pyvista.read_plot3d(filename='grid.in', q_filenames='q1.save', auto_detect=auto_detect)
    read_mock.assert_called_once()


def test_get_array_cell(hexbeam):
    carr = np.random.rand(hexbeam.n_cells)
    hexbeam.cell_data.set_array(carr, 'test_data')

    data = helpers.get_array(hexbeam, 'test_data', preference='cell')
    assert np.allclose(carr, data)


def test_get_array_point(hexbeam):
    parr = np.random.rand(hexbeam.n_points)
    hexbeam.point_data.set_array(parr, 'test_data')

    data = helpers.get_array(hexbeam, 'test_data', preference='point')
    assert np.allclose(parr, data)

    oarr = np.random.rand(hexbeam.n_points)
    hexbeam.point_data.set_array(oarr, 'other')

    data = helpers.get_array(hexbeam, 'other')
    assert np.allclose(oarr, data)


def test_get_array_field(hexbeam):
    hexbeam.clear_data()
    # no preference
    farr = np.random.rand(hexbeam.n_points * hexbeam.n_cells)
    hexbeam.field_data.set_array(farr, 'data')
    data = helpers.get_array(hexbeam, 'data')
    assert np.allclose(farr, data)

    # preference and multiple data
    hexbeam.point_data.set_array(np.random.rand(hexbeam.n_points), 'data')

    data = helpers.get_array(hexbeam, 'data', preference='field')
    assert np.allclose(farr, data)


def test_get_array_error(hexbeam):
    parr = np.random.rand(hexbeam.n_points)
    hexbeam.point_data.set_array(parr, 'test_data')

    # invalid inputs
    with pytest.raises(TypeError):
        helpers.get_array(hexbeam, 'test_data', preference={'invalid'})
    with pytest.raises(ValueError):
        helpers.get_array(hexbeam, 'test_data', preference='invalid')
    with pytest.raises(ValueError, match='`preference` must be'):
        helpers.get_array(hexbeam, 'test_data', preference='row')


def test_get_array_none(hexbeam):
    arr = helpers.get_array(hexbeam, 'foo')
    assert arr is None


def get_array_vtk(hexbeam):
    # test raw VTK input
    grid_vtk = vtk.vtkUnstructuredGrid()
    grid_vtk.DeepCopy(hexbeam)
    helpers.get_array(grid_vtk, 'test_data')
    helpers.get_array(grid_vtk, 'foo')


def test_is_inside_bounds():
    data = ex.load_uniform()
    bnds = data.bounds
    assert helpers.is_inside_bounds((0.5, 0.5, 0.5), bnds)
    assert not helpers.is_inside_bounds((12, 5, 5), bnds)
    assert not helpers.is_inside_bounds((5, 12, 5), bnds)
    assert not helpers.is_inside_bounds((5, 5, 12), bnds)
    assert not helpers.is_inside_bounds((12, 12, 12), bnds)


def test_get_sg_image_scraper():
    scraper = pyvista._get_sg_image_scraper()
    assert isinstance(scraper, pyvista.Scraper)
    assert callable(scraper)


def test_voxelize():
    mesh = pyvista.PolyData(ex.load_uniform().points)
    vox = pyvista.voxelize(mesh, 0.5)
    assert vox.n_cells


def test_voxelize_non_uniform_desnity():
    mesh = pyvista.PolyData(ex.load_uniform().points)
    vox = pyvista.voxelize(mesh, [0.5, 0.3, 0.2])
    assert vox.n_cells


def test_voxelize_throws_when_density_is_not_length_3():
    with pytest.raises(ValueError) as e:
        mesh = pyvista.PolyData(ex.load_uniform().points)
        _ = pyvista.voxelize(mesh, [0.5, 0.3])
    assert "not enough values to unpack" in str(e.value)


def test_report():
    report = pyvista.Report(gpu=True)
    assert report is not None
    report = pyvista.Report(gpu=False)
    assert report is not None


def test_line_segments_from_points():
    points = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0], [1, 1, 0]])
    poly = pyvista.line_segments_from_points(points)
    assert poly.n_cells == 2
    assert poly.n_points == 4
    cells = poly.lines
    assert np.allclose(cells[:3], [2, 0, 1])
    assert np.allclose(cells[3:], [2, 2, 3])


def test_lines_from_points():
    points = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0]])
    poly = pyvista.lines_from_points(points)
    assert poly.n_cells == 2
    assert poly.n_points == 3
    cells = poly.lines
    assert np.allclose(cells[:3], [2, 0, 1])
    assert np.allclose(cells[3:], [2, 1, 2])


def test_grid_from_sph_coords():
    x = np.arange(0.0, 360.0, 40.0)  # longitude
    y = np.arange(0.0, 181.0, 60.0)  # colatitude
    z = [1]  # elevation (radius)
    g = pyvista.grid_from_sph_coords(x, y, z)
    assert g.n_cells == 24
    assert g.n_points == 36
    assert np.allclose(
        g.bounds,
        [
            -0.8137976813493738,
            0.8660254037844387,
            -0.8528685319524434,
            0.8528685319524433,
            -1.0,
            1.0,
        ],
    )
    assert np.allclose(g.points[1], [0.8660254037844386, 0.0, 0.5])
    z = np.linspace(10, 30, 3)
    g = pyvista.grid_from_sph_coords(x, y, z)
    assert g.n_cells == 48
    assert g.n_points == 108
    assert np.allclose(g.points[0], [0.0, 0.0, 10.0])


def test_transform_vectors_sph_to_cart():
    lon = np.arange(0.0, 360.0, 40.0)  # longitude
    lat = np.arange(0.0, 181.0, 60.0)  # colatitude
    lev = [1]  # elevation (radius)
    u, v = np.meshgrid(lon, lat, indexing="ij")
    w = u**2 - v**2
    uu, vv, ww = pyvista.transform_vectors_sph_to_cart(lon, lat, lev, u, v, w)
    assert np.allclose(
        [uu[-1, -1], vv[-1, -1], ww[-1, -1]],
        [67.80403533828323, 360.8359915416445, -70000.0],
    )


def test_vtkmatrix_to_from_array():
    rng = np.random.default_rng()
    array3x3 = rng.integers(0, 10, size=(3, 3))
    matrix = pyvista.vtkmatrix_from_array(array3x3)
    assert isinstance(matrix, vtk.vtkMatrix3x3)
    for i in range(3):
        for j in range(3):
            assert matrix.GetElement(i, j) == array3x3[i, j]

    array = pyvista.array_from_vtkmatrix(matrix)
    assert isinstance(array, np.ndarray)
    assert array.shape == (3, 3)
    for i in range(3):
        for j in range(3):
            assert array[i, j] == matrix.GetElement(i, j)

    array4x4 = rng.integers(0, 10, size=(4, 4))
    matrix = pyvista.vtkmatrix_from_array(array4x4)
    assert isinstance(matrix, vtk.vtkMatrix4x4)
    for i in range(4):
        for j in range(4):
            assert matrix.GetElement(i, j) == array4x4[i, j]

    array = pyvista.array_from_vtkmatrix(matrix)
    assert isinstance(array, np.ndarray)
    assert array.shape == (4, 4)
    for i in range(4):
        for j in range(4):
            assert array[i, j] == matrix.GetElement(i, j)

    # invalid cases
    with pytest.raises(ValueError):
        matrix = pyvista.vtkmatrix_from_array(np.arange(3 * 4).reshape(3, 4))
    with pytest.raises(TypeError):
        invalid = vtk.vtkTransform()
        array = pyvista.array_from_vtkmatrix(invalid)


def test_assert_empty_kwargs():
    kwargs = {}
    assert errors.assert_empty_kwargs(**kwargs)
    with pytest.raises(TypeError):
        kwargs = {"foo": 6}
        errors.assert_empty_kwargs(**kwargs)
    with pytest.raises(TypeError):
        kwargs = {"foo": 6, "goo": "bad"}
        errors.assert_empty_kwargs(**kwargs)


def test_convert_id_list():
    ids = np.array([4, 5, 8])
    id_list = vtk.vtkIdList()
    id_list.SetNumberOfIds(len(ids))
    for i, v in enumerate(ids):
        id_list.SetId(i, v)
    converted = helpers.vtk_id_list_to_array(id_list)
    assert np.allclose(converted, ids)


def test_progress_monitor():
    mesh = pyvista.Sphere()
    ugrid = mesh.delaunay_3d(progress_bar=True)
    assert isinstance(ugrid, pyvista.UnstructuredGrid)


def test_observer():
    msg = "KIND: In PATH, line 0\nfoo (ADDRESS): ALERT"
    obs = Observer()
    ret = obs.parse_message("foo")
    assert ret[3] == "foo"
    ret = obs.parse_message(msg)
    assert ret[3] == "ALERT"
    for kind in ["WARNING", "ERROR"]:
        obs.log_message(kind, "foo")
    obs(obj=None, event=None, message=msg)
    assert obs.has_event_occurred()
    assert obs.get_message() == "ALERT"
    assert obs.get_message(etc=True) == msg

    alg = vtk.vtkSphereSource()
    alg.GetExecutive()
    obs.observe(alg)
    with pytest.raises(RuntimeError, match="algorithm"):
        obs.observe(alg)


@skip_no_plotting
def test_gpuinfo():
    gpuinfo = GPUInfo()
    _repr = gpuinfo.__repr__()
    _repr_html = gpuinfo._repr_html_()
    assert isinstance(_repr, str) and len(_repr) > 1
    assert isinstance(_repr_html, str) and len(_repr_html) > 1

    # test corrupted internal infos
    gpuinfo._gpu_info = 'foo'
    for func_name in ['renderer', 'version', 'vendor']:
        with pytest.raises(RuntimeError, match=func_name):
            getattr(gpuinfo, func_name)()


def test_check_valid_vector():
    with pytest.raises(ValueError, match="length three"):
        check_valid_vector([0, 1])
    check_valid_vector([0, 1, 2])


def test_cells_dict_utils():

    # No pyvista object
    with pytest.raises(ValueError):
        cells.get_mixed_cells(None)

    with pytest.raises(ValueError):
        cells.get_mixed_cells(np.zeros(shape=[3, 3]))

    cells_arr = np.array([3, 0, 1, 2, 3, 3, 4, 5])
    cells_types = np.array([vtk.VTK_TRIANGLE] * 2)

    assert np.array_equal(
        cells.generate_cell_offsets(cells_arr, cells_types),
        cells.generate_cell_offsets_loop(cells_arr, cells_types),
    )

    # Non-integer type
    with pytest.raises(ValueError):
        cells.generate_cell_offsets(cells_arr, cells_types.astype(np.float32))

    with pytest.raises(ValueError):
        cells.generate_cell_offsets_loop(cells_arr, cells_types.astype(np.float32))

    # Inconsistency of cell array lengths
    with pytest.raises(ValueError):
        cells.generate_cell_offsets(np.array(cells_arr.tolist() + [6]), cells_types)

    with pytest.raises(ValueError):
        cells.generate_cell_offsets_loop(np.array(cells_arr.tolist() + [6]), cells_types)

    with pytest.raises(ValueError):
        cells.generate_cell_offsets(cells_arr, np.array(cells_types.tolist() + [vtk.VTK_TRIANGLE]))

    with pytest.raises(ValueError):
        cells.generate_cell_offsets_loop(
            cells_arr, np.array(cells_types.tolist() + [vtk.VTK_TRIANGLE])
        )

    # Unknown cell type
    np.all(
        cells.generate_cell_offsets(cells_arr, cells_types)
        == cells.generate_cell_offsets(cells_arr, np.array([255, 255]))
    )


def test_apply_transformation_to_points():
    mesh = ex.load_airplane()
    points = mesh.points
    points_orig = points.copy()

    # identity 3 x 3
    tf = np.eye(3)
    points_new = transformations.apply_transformation_to_points(tf, points, inplace=False)
    assert points_new == pytest.approx(points)

    # identity 4 x 4
    tf = np.eye(4)
    points_new = transformations.apply_transformation_to_points(tf, points, inplace=False)
    assert points_new == pytest.approx(points)

    # scale in-place
    tf = np.eye(4) * 2
    tf[3, 3] = 1
    r = transformations.apply_transformation_to_points(tf, points, inplace=True)
    assert r is None
    assert mesh.points == pytest.approx(2 * points_orig)


def _generate_vtk_err():
    """Simple operation which generates a VTK error."""
    x, y, z = np.meshgrid(np.arange(-10, 10, 0.5), np.arange(-10, 10, 0.5), np.arange(-10, 10, 0.5))
    mesh = pyvista.StructuredGrid(x, y, z)
    x2, y2, z2 = np.meshgrid(np.arange(-1, 1, 0.5), np.arange(-1, 1, 0.5), np.arange(-1, 1, 0.5))
    mesh2 = pyvista.StructuredGrid(x2, y2, z2)

    alg = vtk.vtkStreamTracer()
    obs = pyvista.Observer()
    obs.observe(alg)
    alg.SetInputDataObject(mesh)
    alg.SetSourceData(mesh2)
    alg.Update()


def test_vtk_error_catcher():
    # raise_errors: False
    error_catcher = pyvista.utilities.errors.VtkErrorCatcher()
    with error_catcher:
        _generate_vtk_err()
        _generate_vtk_err()
    assert len(error_catcher.events) == 2

    # raise_errors: False, no error
    error_catcher = pyvista.utilities.errors.VtkErrorCatcher()
    with error_catcher:
        pass

    # raise_errors: True
    error_catcher = pyvista.utilities.errors.VtkErrorCatcher(raise_errors=True)
    with pytest.raises(RuntimeError):
        with error_catcher:
            _generate_vtk_err()
    assert len(error_catcher.events) == 1

    # raise_errors: True, no error
    error_catcher = pyvista.utilities.errors.VtkErrorCatcher(raise_errors=True)
    with error_catcher:
        pass


def test_axis_angle_rotation():
    # rotate cube corners around body diagonal
    points = np.array(
        [
            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1],
        ]
    )
    axis = [1, 1, 1]

    # no-op case
    angle = 360
    trans = transformations.axis_angle_rotation(axis, angle)
    actual = transformations.apply_transformation_to_points(trans, points)
    assert np.array_equal(actual, points)

    # default origin
    angle = np.radians(120)
    expected = points[[1, 2, 0], :]
    trans = transformations.axis_angle_rotation(axis, angle, deg=False)
    actual = transformations.apply_transformation_to_points(trans, points)
    assert np.allclose(actual, expected)

    # non-default origin
    p0 = [-2, -3, 4]
    points += p0
    expected += p0
    trans = transformations.axis_angle_rotation(axis, angle, point=p0, deg=False)
    actual = transformations.apply_transformation_to_points(trans, points)
    assert np.allclose(actual, expected)

    # invalid cases
    with pytest.raises(ValueError):
        transformations.axis_angle_rotation([1, 0, 0, 0], angle)
    with pytest.raises(ValueError):
        transformations.axis_angle_rotation(axis, angle, point=[1, 0, 0, 0])
    with pytest.raises(ValueError):
        transformations.axis_angle_rotation([0, 0, 0], angle)


def test_reflection():
    # reflect points of a square across a diagonal
    points = np.array(
        [
            [1, 1, 0],
            [-1, 1, 0],
            [-1, -1, 0],
            [1, -1, 0],
        ]
    )
    normal = [1, 1, 0]

    # default origin
    expected = points[[2, 1, 0, 3], :]
    trans = transformations.reflection(normal)
    actual = transformations.apply_transformation_to_points(trans, points)
    assert np.allclose(actual, expected)

    # non-default origin
    p0 = [1, 1, 0]
    expected += 2 * np.array(p0)
    trans = transformations.reflection(normal, point=p0)
    actual = transformations.apply_transformation_to_points(trans, points)
    assert np.allclose(actual, expected)

    # invalid cases
    with pytest.raises(ValueError):
        transformations.reflection([1, 0, 0, 0])
    with pytest.raises(ValueError):
        transformations.reflection(normal, point=[1, 0, 0, 0])
    with pytest.raises(ValueError):
        transformations.reflection([0, 0, 0])


def test_merge(sphere, cube, datasets):
    with pytest.raises(TypeError, match="Expected a sequence"):
        pyvista.merge(None)

    with pytest.raises(ValueError, match="Expected at least one"):
        pyvista.merge([])

    with pytest.raises(TypeError, match="Expected pyvista.DataSet"):
        pyvista.merge([None, sphere])

    # check polydata
    merged_poly = pyvista.merge([sphere, cube])
    assert isinstance(merged_poly, pyvista.PolyData)
    assert merged_poly.n_points == sphere.n_points + cube.n_points

    merged = pyvista.merge([sphere, sphere], merge_points=True)
    assert merged.n_points == sphere.n_points

    merged = pyvista.merge([sphere, sphere], merge_points=False)
    assert merged.n_points == sphere.n_points * 2

    # check unstructured
    merged_ugrid = pyvista.merge(datasets, merge_points=False)
    assert isinstance(merged_ugrid, pyvista.UnstructuredGrid)
    assert merged_ugrid.n_points == sum([ds.n_points for ds in datasets])
    # check main has priority
    sphere_a = sphere.copy()
    sphere_b = sphere.copy()
    sphere_a['data'] = np.zeros(sphere_a.n_points)
    sphere_b['data'] = np.ones(sphere_a.n_points)

    merged = pyvista.merge(
        [sphere_a, sphere_b],
        merge_points=True,
        main_has_priority=False,
    )
    assert np.allclose(merged['data'], 1)

    merged = pyvista.merge(
        [sphere_a, sphere_b],
        merge_points=True,
        main_has_priority=True,
    )
    assert np.allclose(merged['data'], 0)


def test_color():
    name, name2 = "blue", "b"
    i_rgba, f_rgba = (0, 0, 255, 255), (0.0, 0.0, 1.0, 1.0)
    h = "0000ffff"
    i_opacity, f_opacity, h_opacity = 153, 0.6, "99"
    invalid_colors = (
        (300, 0, 0),
        (0, -10, 0),
        (0, 0, 1.5),
        (-0.5, 0, 0),
        (0, 0),
        "#hh0000",
        "invalid_name",
        {"invalid_name": 100},
    )
    invalid_opacities = (275, -50, 2.4, -1.2, "#zz")
    i_types = (int, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64)
    f_types = (float, np.float16, np.float32, np.float64)
    h_prefixes = ("", "0x", "#")
    assert pyvista.Color(name) == i_rgba
    assert pyvista.Color(name2) == i_rgba
    # Check integer types
    for i_type in i_types:
        i_color = [i_type(c) for c in i_rgba]
        # Check list, tuple and numpy array
        assert pyvista.Color(i_color) == i_rgba
        assert pyvista.Color(tuple(i_color)) == i_rgba
        assert pyvista.Color(np.asarray(i_color, dtype=i_type)) == i_rgba
    # Check float types
    for f_type in f_types:
        f_color = [f_type(c) for c in f_rgba]
        # Check list, tuple and numpy array
        assert pyvista.Color(f_color) == i_rgba
        assert pyvista.Color(tuple(f_color)) == i_rgba
        assert pyvista.Color(np.asarray(f_color, dtype=f_type)) == i_rgba
    # Check hex
    for h_prefix in h_prefixes:
        assert pyvista.Color(h_prefix + h) == i_rgba
    # Check dict
    for channels in itertools.product(*pyvista.Color.CHANNEL_NAMES):
        dct = dict(zip(channels, i_rgba))
        assert pyvista.Color(dct) == i_rgba
    # Check opacity
    for opacity in (i_opacity, f_opacity, h_opacity):
        # No opacity in color provided => use opacity
        assert pyvista.Color(name, opacity) == (*i_rgba[:3], i_opacity)
        # Opacity in color provided => overwrite using opacity
        assert pyvista.Color(i_rgba, opacity) == (*i_rgba[:3], i_opacity)
    # Check default_opacity
    for opacity in (i_opacity, f_opacity, h_opacity):
        # No opacity in color provided => use default_opacity
        assert pyvista.Color(name, default_opacity=opacity) == (*i_rgba[:3], i_opacity)
        # Opacity in color provided => keep that opacity
        assert pyvista.Color(i_rgba, default_opacity=opacity) == i_rgba
    # Check default_color
    assert pyvista.Color(None, default_color=name) == i_rgba
    # Check invalid colors and opacities
    for invalid_color in invalid_colors:
        with pytest.raises(ValueError):
            pyvista.Color(invalid_color)
    for invalid_opacity in invalid_opacities:
        with pytest.raises(ValueError):
            pyvista.Color('b', invalid_opacity)
    # Check hex and name getters
    assert pyvista.Color(name).hex_rgba == f'#{h}'
    assert pyvista.Color(name).hex_rgb == f'#{h[:-2]}'
    assert pyvista.Color('b').name == 'blue'
    # Check sRGB conversion
    assert pyvista.Color('gray', 0.5).linear_to_srgb() == '#bcbcbcbc'
    assert pyvista.Color('#bcbcbcbc').srgb_to_linear() == '#80808080'
    # Check iteration and indexing
    c = pyvista.Color(i_rgba)
    assert all(ci == fi for ci, fi in zip(c, f_rgba))
    for i, cnames in enumerate(pyvista.Color.CHANNEL_NAMES):
        assert c[i] == f_rgba[i]
        assert all(c[i] == c[cname] for cname in cnames)
    assert c[-1] == f_rgba[-1]
    assert c[1:3] == f_rgba[1:3]
    with pytest.raises(TypeError):
        c[None]  # Invalid index type
    with pytest.raises(ValueError):
        c["invalid_name"]  # Invalid string index
    with pytest.raises(IndexError):
        c[4]  # Invalid integer index


def test_convert_array():
    arr = np.arange(4).astype('O')
    arr2 = pyvista.utilities.convert_array(arr, array_type=np.dtype('O'))
    assert arr2.GetNumberOfValues() == 4

    # https://github.com/pyvista/pyvista/issues/2370
    arr3 = pyvista.utilities.convert_array(
        pickle.loads(pickle.dumps(np.arange(4).astype('O'))), array_type=np.dtype('O')
    )
    assert arr3.GetNumberOfValues() == 4

    # check lists work
    my_list = [1, 2, 3]
    arr4 = pyvista.utilities.convert_array(my_list)
    assert arr4.GetNumberOfValues() == len(my_list)


def test_has_duplicates():
    assert not has_duplicates(np.arange(100))
    assert has_duplicates(np.array([0, 1, 2, 2]))
    assert has_duplicates(np.array([[0, 1, 2], [0, 1, 2]]))

    with pytest.raises(ValueError):
        raise_has_duplicates(np.array([0, 1, 2, 2]))


def test_copy_vtk_array():
    with pytest.raises(TypeError, match='Invalid type'):
        pyvista.utilities.misc.copy_vtk_array([1, 2, 3])

    value_0 = 10
    value_1 = 10
    arr = vtk.vtkFloatArray()
    arr.SetNumberOfValues(2)
    arr.SetValue(0, value_0)
    arr.SetValue(1, value_1)
    arr_copy = pyvista.utilities.misc.copy_vtk_array(arr, deep=True)
    assert arr_copy.GetNumberOfValues()
    assert value_0 == arr_copy.GetValue(0)

    arr_copy_shallow = pyvista.utilities.misc.copy_vtk_array(arr, deep=False)
    new_value = 5
    arr.SetValue(1, new_value)
    assert value_1 == arr_copy.GetValue(1)
    assert new_value == arr_copy_shallow.GetValue(1)


def test_set_pickle_format():
    pyvista.set_pickle_format('legacy')
    assert pyvista.PICKLE_FORMAT == 'legacy'

    pyvista.set_pickle_format('xml')
    assert pyvista.PICKLE_FORMAT == 'xml'

    with pytest.raises(ValueError):
        pyvista.set_pickle_format('invalid_format')


# tests/test_lookup_table.py
import numpy as np
import pytest
import vtk

import pyvista as pv
from pyvista import Color, LookupTable


@pytest.fixture
def lut():
    return LookupTable()


@pytest.fixture
def lut_w_cmap():
    return LookupTable('viridis')


def test_values(lut):
    values = [
        [0, 0, 0, 255],
        [85, 0, 0, 255],
        [170, 0, 0, 255],
        [255, 0, 0, 255],
    ]
    lut.values = values
    assert lut.values.dtype == np.uint8
    assert np.allclose(lut.values, values)

    with pytest.raises(RuntimeError, match='cannot be set'):
        lut.n_values = 10


def test_apply_cmap(lut):
    n_values = 5
    lut.cmap = 'reds'
    lut.n_values = n_values
    assert lut.values.shape == (n_values, 4)
    assert lut.n_values == n_values


def test_init_cmap():
    new_lut = LookupTable('gray', n_values=2, flip=True)
    assert np.allclose([[254, 255, 255, 255], [0, 0, 0, 255]], new_lut.values)


def test_init_values():
    new_lut = LookupTable(values=[[254, 255, 255, 255], [0, 0, 0, 255]])
    assert np.allclose([[254, 255, 255, 255], [0, 0, 0, 255]], new_lut.values)


def test_init_custom():
    value_range = (0.1, 0.2)
    hue_range = (0.2, 0.3)
    alpha_range = (0.3, 0.4)
    scalar_range = (1, 9)
    ramp = 'linear'
    nan_color = Color('r')
    above_range_color = Color('b')
    below_range_color = Color('g')
    log_scale = True
    annotations = {0: 'low', 4.5: 'medium', 9: 'high'}

    new_lut = LookupTable(
        value_range=value_range,
        hue_range=hue_range,
        alpha_range=alpha_range,
        scalar_range=scalar_range,
        ramp=ramp,
        nan_color=nan_color,
        above_range_color=above_range_color,
        below_range_color=below_range_color,
        log_scale=log_scale,
        annotations=annotations,
    )

    assert new_lut.value_range == value_range
    assert new_lut.hue_range == hue_range
    assert new_lut.alpha_range == alpha_range
    assert new_lut.scalar_range == scalar_range
    assert new_lut.ramp == ramp
    assert new_lut.nan_color == nan_color
    assert new_lut.above_range_color == above_range_color
    assert new_lut.below_range_color == below_range_color
    assert new_lut.log_scale == log_scale
    assert new_lut.annotations == annotations


def test_annotations(lut):
    assert lut.annotations == {}
    anno = {0: 'low', 0.5: 'medium', 1: 'high'}
    lut.annotations = anno
    assert lut.annotations == anno


def test_value_range(lut, lut_w_cmap):
    assert lut_w_cmap.value_range is None

    value_range = (0, 1.0)
    lut.value_range = value_range
    assert lut.value_range == value_range


def test_hue_range(lut, lut_w_cmap):
    assert lut_w_cmap.hue_range is None

    hue_range = (0, 1.0)
    lut.hue_range = hue_range
    assert lut.hue_range == hue_range


def test_saturation_range(lut, lut_w_cmap):
    assert lut_w_cmap.saturation_range is None

    saturation_range = (0, 1.0)
    lut.saturation_range = saturation_range
    assert lut.saturation_range == saturation_range


def test_alpha_range(lut, lut_w_cmap):
    assert lut_w_cmap.alpha_range is None

    alpha_range = (0, 1.0)
    lut.alpha_range = alpha_range
    assert lut.alpha_range == alpha_range


def test_nan_color(lut):
    lut.nan_color = 'b'
    assert lut.nan_color == Color('b')


def test_below_range_color(lut):
    lut.below_range_color = 'r'
    assert lut.below_range_color == Color('r')
    assert lut.GetUseBelowRangeColor()

    lut.below_range_color = None
    assert lut.below_range_color is None
    assert not lut.GetUseBelowRangeColor()

    lut.below_range_color = True
    assert lut.below_range_color == pv.global_theme.below_range_color


def test_above_range_color(lut):
    lut.above_range_color = 'r'
    assert lut.above_range_color == Color('r')
    assert lut.GetUseAboveRangeColor()

    lut.above_range_color = None
    assert lut.above_range_color is None
    assert not lut.GetUseAboveRangeColor()

    lut.above_range_color = True
    assert lut.above_range_color == pv.global_theme.above_range_color


def test_ramp(lut):
    lut.ramp = 'linear'
    assert lut.ramp == 'linear'
    with pytest.raises(ValueError, match='must be one of the following'):
        lut.ramp = 'foo'


def test_log_scale(lut):
    lut.log_scale = True
    assert lut.log_scale is True

    lut.log_scale = False
    assert lut.log_scale is False


def test_repr(lut):
    assert 'PyVista' in repr(lut)

    lut.values = lut.values**0.5
    assert 'From values' in repr(lut)

    lut.cmap = 'viridis'
    assert 'viridis' in repr(lut)

    # try a colorcet
    lut.cmap = 'cet_fire'
    assert 'fire' in repr(lut)


def test_scalar_range(lut):
    scalar_range = (0.5, 1.0)
    lut.scalar_range = scalar_range
    assert lut.scalar_range == scalar_range


def test_table_cmap_list(lut):
    lut.cmap = ['red', 'green', 'blue']
    assert lut.n_values == 3


def test_table_values_update(lut):
    lut.cmap = 'Greens'
    lut.values[:, -1] = np.linspace(0, 255, lut.n_values)
    assert lut.values.max() == 255
    assert lut.values[:, 2].max() < 255


def test_to_tf(lut):
    tf = lut.to_color_tf()
    assert isinstance(tf, vtk.vtkColorTransferFunction)


def test_map_value(lut):
    assert lut.map_value(0.5) == (0.0, 1.0, 0.0, 1.0)


def test_call(lut):
    n_values = 10
    arr = lut(np.linspace(0, 1, n_values))
    assert isinstance(arr, np.ndarray)
    assert arr.shape[0] == n_values

    assert lut.map_value(0.5) == lut.map_value(0.5)
