634 lines
19 KiB
Python
634 lines
19 KiB
Python
"""Internal :vtk:`vtkAlgorithm` support helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import traceback
|
|
from typing import TYPE_CHECKING
|
|
|
|
import numpy as np
|
|
|
|
import pyvista
|
|
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
|
from pyvista.core.errors import PyVistaPipelineError
|
|
from pyvista.core.utilities.helpers import wrap
|
|
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
|
from pyvista.plotting import _vtk
|
|
|
|
if TYPE_CHECKING:
|
|
from pyvista.core.utilities.arrays import CellLiteral
|
|
from pyvista.core.utilities.arrays import PointLiteral
|
|
|
|
|
|
def algorithm_to_mesh_handler(
|
|
mesh_or_algo, port=0
|
|
) -> tuple[pyvista.DataSet, _vtk.vtkAlgorithm | _vtk.vtkAlgorithmOutput | None]:
|
|
"""Handle :vtk:`vtkAlgorithms` where mesh objects are expected.
|
|
|
|
This is a convenience method to handle :vtk:`vtkAlgorithms` when passed to methods
|
|
that expect a :class:`~pyvista.DataSet`. This method will check if the passed
|
|
object is a :vtk:`vtkAlgorithm` or :vtk:`vtkAlgorithmOutput` and if so,
|
|
return that algorithm's output dataset (mesh) as the mesh to be used by the
|
|
calling function.
|
|
|
|
Parameters
|
|
----------
|
|
mesh_or_algo : DataSet | :vtk:`vtkAlgorithm` | :vtk:`vtkAlgorithmOutput`
|
|
The input to be used as a data set (mesh) or :vtk:`vtkAlgorithm` object.
|
|
|
|
port : int, default: 0
|
|
If the input (``mesh_or_algo``) is an algorithm, this specifies which output
|
|
port to use on that algorithm for the returned mesh.
|
|
|
|
Returns
|
|
-------
|
|
mesh : pyvista.DataSet
|
|
The resulting mesh data set from the input.
|
|
|
|
algorithm : :vtk:`vtkAlgorithm` | :vtk:`vtkAlgorithmOutput` | None
|
|
If an algorithm is passed, it will be returned. Otherwise returns ``None``.
|
|
|
|
"""
|
|
if isinstance(mesh_or_algo, (_vtk.vtkAlgorithm, _vtk.vtkAlgorithmOutput)):
|
|
if isinstance(mesh_or_algo, _vtk.vtkAlgorithmOutput):
|
|
algo = mesh_or_algo.GetProducer()
|
|
# If vtkAlgorithmOutput, override port argument
|
|
port = mesh_or_algo.GetIndex()
|
|
output = mesh_or_algo
|
|
else:
|
|
algo = mesh_or_algo
|
|
output = algo.GetOutputPort(port)
|
|
algo.Update() # NOTE: this could be expensive... but we need it to get the mesh
|
|
# for legacy implementation. This can be refactored.
|
|
mesh = wrap(algo.GetOutputDataObject(port))
|
|
if mesh is None:
|
|
# This is known to happen with vtkPointSet and VTKPythonAlgorithmBase
|
|
# see workaround in PreserveTypeAlgorithmBase.
|
|
# This check remains as a fail-safe.
|
|
msg = 'The passed algorithm is failing to produce an output.' # type: ignore[unreachable]
|
|
raise PyVistaPipelineError(msg)
|
|
# NOTE: Return the vtkAlgorithmOutput only if port is non-zero. Segfaults can sometimes
|
|
# happen with vtkAlgorithmOutput. This logic will mostly avoid those issues.
|
|
# See https://gitlab.kitware.com/vtk/vtk/-/issues/18776
|
|
return mesh, output if port != 0 else algo
|
|
return mesh_or_algo, None
|
|
|
|
|
|
def set_algorithm_input(alg, inp, port=0):
|
|
"""Set the input to a :vtk:`vtkAlgorithm`.
|
|
|
|
Parameters
|
|
----------
|
|
alg : :vtk:`vtkAlgorithm`
|
|
The algorithm whose input is being set.
|
|
|
|
inp : :vtk:`vtkAlgorithm` | :vtk:`vtkAlgorithmOutput` | :vtk:`vtkDataObject`
|
|
The input to the algorithm.
|
|
|
|
port : int, default: 0
|
|
The input port.
|
|
|
|
"""
|
|
if isinstance(inp, _vtk.vtkAlgorithm):
|
|
alg.SetInputConnection(port, inp.GetOutputPort())
|
|
elif isinstance(inp, _vtk.vtkAlgorithmOutput):
|
|
alg.SetInputConnection(port, inp)
|
|
else:
|
|
alg.SetInputDataObject(port, inp)
|
|
|
|
|
|
class PreserveTypeAlgorithmBase(
|
|
_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.VTKPythonAlgorithmBase
|
|
):
|
|
"""Base algorithm to preserve type.
|
|
|
|
Parameters
|
|
----------
|
|
nInputPorts : int, default: 1
|
|
Number of input ports for the algorithm.
|
|
|
|
nOutputPorts : int, default: 1
|
|
Number of output ports for the algorithm.
|
|
|
|
"""
|
|
|
|
def __init__(self, nInputPorts=1, nOutputPorts=1):
|
|
"""Initialize algorithm."""
|
|
_vtk.VTKPythonAlgorithmBase.__init__(
|
|
self,
|
|
nInputPorts=nInputPorts,
|
|
nOutputPorts=nOutputPorts,
|
|
)
|
|
|
|
def GetInputData(self, inInfo, port, idx):
|
|
"""Get input data object.
|
|
|
|
This will convert :vtk:`vtkPointSet` to :vtk:`vtkPolyData`.
|
|
|
|
Parameters
|
|
----------
|
|
inInfo : :vtk:`vtkInformation`
|
|
The information object associated with the input port.
|
|
|
|
port : int
|
|
The index of the input port.
|
|
|
|
idx : int
|
|
The index of the data object within the input port.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkDataObject`
|
|
The input data object.
|
|
|
|
"""
|
|
inp = wrap(_vtk.VTKPythonAlgorithmBase.GetInputData(self, inInfo, port, idx))
|
|
if isinstance(inp, pyvista.PointSet):
|
|
return inp.cast_to_polydata()
|
|
return inp
|
|
|
|
# THIS IS CRUCIAL to preserve data type through filter
|
|
def RequestDataObject(self, _request, inInfo, outInfo) -> int:
|
|
"""Preserve data type.
|
|
|
|
Parameters
|
|
----------
|
|
_request : :vtk:`vtkInformation`
|
|
The request object for the filter.
|
|
|
|
inInfo : :vtk:`vtkInformationVector`
|
|
The input information vector for the filter.
|
|
|
|
outInfo : :vtk:`vtkInformationVector`
|
|
The output information vector for the filter.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
Returns 1 if successful.
|
|
|
|
"""
|
|
class_name = self.GetInputData(inInfo, 0, 0).GetClassName()
|
|
if class_name == 'vtkPointSet':
|
|
# See https://gitlab.kitware.com/vtk/vtk/-/issues/18771
|
|
self.OutputType = 'vtkPolyData'
|
|
else:
|
|
self.OutputType = class_name
|
|
self.FillOutputPortInformation(0, outInfo.GetInformationObject(0))
|
|
return 1
|
|
|
|
|
|
class ActiveScalarsAlgorithm(PreserveTypeAlgorithmBase):
|
|
"""Algorithm to control active scalars.
|
|
|
|
The output of this filter is a shallow copy of the input data
|
|
set with the active scalars set as specified.
|
|
|
|
Parameters
|
|
----------
|
|
name : str
|
|
Name of scalars used to set as active on the output mesh.
|
|
Accepts a string name of an array that is present on the mesh.
|
|
Array should be sized as a single vector.
|
|
|
|
preference : str, default: 'point'
|
|
When ``mesh.n_points == mesh.n_cells`` and setting
|
|
scalars, this parameter sets how the scalars will be
|
|
mapped to the mesh. The default, ``'point'``, causes the
|
|
scalars to be associated with the mesh points. Can be
|
|
either ``'point'`` or ``'cell'``.
|
|
|
|
"""
|
|
|
|
def __init__(self, name: str, preference: PointLiteral | CellLiteral = 'point'):
|
|
"""Initialize algorithm."""
|
|
super().__init__()
|
|
self.scalars_name = name
|
|
self.preference = preference
|
|
|
|
def RequestData(self, _request, inInfo, outInfo) -> int:
|
|
"""Perform algorithm execution.
|
|
|
|
Parameters
|
|
----------
|
|
_request : :vtk:`vtkInformation`
|
|
The request object.
|
|
inInfo : :vtk:`vtkInformationVector`
|
|
Information about the input data.
|
|
outInfo : :vtk:`vtkInformationVector`
|
|
Information about the output data.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
1 on success.
|
|
|
|
"""
|
|
try:
|
|
inp = wrap(self.GetInputData(inInfo, 0, 0))
|
|
out = self.GetOutputData(outInfo, 0)
|
|
output = inp.copy()
|
|
if output.n_arrays:
|
|
output.set_active_scalars(self.scalars_name, preference=self.preference)
|
|
out.ShallowCopy(output)
|
|
except Exception: # pragma: no cover
|
|
traceback.print_exc()
|
|
raise
|
|
return 1
|
|
|
|
|
|
class PointSetToPolyDataAlgorithm(
|
|
_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.VTKPythonAlgorithmBase
|
|
):
|
|
"""Algorithm to cast PointSet to PolyData.
|
|
|
|
This is implemented with :func:`pyvista.PointSet.cast_to_polydata`.
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize algorithm."""
|
|
_vtk.VTKPythonAlgorithmBase.__init__(
|
|
self,
|
|
nInputPorts=1,
|
|
nOutputPorts=1,
|
|
inputType='vtkPointSet',
|
|
outputType='vtkPolyData',
|
|
)
|
|
|
|
def RequestData(self, _request, inInfo, outInfo) -> int:
|
|
"""Perform algorithm execution.
|
|
|
|
Parameters
|
|
----------
|
|
_request : :vtk:`vtkInformation`
|
|
Information associated with the request.
|
|
inInfo : :vtk:`vtkInformationVector`
|
|
Information about the input data.
|
|
outInfo : :vtk:`vtkInformationVector`
|
|
Information about the output data.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
1 when successful.
|
|
|
|
"""
|
|
try:
|
|
inp = wrap(self.GetInputData(inInfo, 0, 0))
|
|
out = self.GetOutputData(outInfo, 0)
|
|
output = inp.cast_to_polydata(deep=False)
|
|
out.ShallowCopy(output)
|
|
except Exception: # pragma: no cover
|
|
traceback.print_exc()
|
|
raise
|
|
return 1
|
|
|
|
|
|
class AddIDsAlgorithm(PreserveTypeAlgorithmBase):
|
|
"""Algorithm to add point or cell IDs.
|
|
|
|
Output of this filter is a shallow copy of the input with
|
|
point and/or cell ID arrays added.
|
|
|
|
Parameters
|
|
----------
|
|
point_ids : bool, default: True
|
|
Whether to add point IDs.
|
|
|
|
cell_ids : bool, default: True
|
|
Whether to add cell IDs.
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
If neither point IDs nor cell IDs are set.
|
|
|
|
"""
|
|
|
|
@_deprecate_positional_args
|
|
def __init__(self, point_ids: bool = True, cell_ids: bool = True): # noqa: FBT001, FBT002
|
|
"""Initialize algorithm."""
|
|
super().__init__()
|
|
if not point_ids and not cell_ids: # pragma: no cover
|
|
msg = 'IDs must be set for points or cells or both.'
|
|
raise ValueError(msg)
|
|
self.point_ids = point_ids
|
|
self.cell_ids = cell_ids
|
|
|
|
def RequestData(self, _request, inInfo, outInfo) -> int:
|
|
"""Perform algorithm execution.
|
|
|
|
Parameters
|
|
----------
|
|
_request : :vtk:`vtkInformation`
|
|
Information associated with the request.
|
|
inInfo : :vtk:`vtkInformationVector`
|
|
Information about the input data.
|
|
outInfo : :vtk:`vtkInformationVector`
|
|
Information about the output data.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
Returns 1 if the algorithm was successful.
|
|
|
|
Raises
|
|
------
|
|
Exception
|
|
If the algorithm fails to execute properly.
|
|
|
|
"""
|
|
try:
|
|
inp = wrap(self.GetInputData(inInfo, 0, 0))
|
|
out = self.GetOutputData(outInfo, 0)
|
|
output = inp.copy()
|
|
if self.point_ids:
|
|
output.point_data['point_ids'] = np.arange(0, output.n_points, dtype=int)
|
|
if self.cell_ids:
|
|
output.cell_data['cell_ids'] = np.arange(0, output.n_cells, dtype=int)
|
|
if output.active_scalars_name in ['point_ids', 'cell_ids']:
|
|
output.active_scalars_name = inp.active_scalars_name
|
|
out.ShallowCopy(output)
|
|
except Exception: # pragma: no cover
|
|
traceback.print_exc()
|
|
raise
|
|
return 1
|
|
|
|
|
|
class CrinkleAlgorithm(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.VTKPythonAlgorithmBase):
|
|
"""Algorithm to crinkle cell IDs."""
|
|
|
|
def __init__(self):
|
|
"""Initialize algorithm."""
|
|
super().__init__(
|
|
nInputPorts=2,
|
|
outputType='vtkUnstructuredGrid',
|
|
)
|
|
|
|
def RequestData(self, _request, inInfo, outInfo) -> int:
|
|
"""Perform algorithm execution based on the input data and produce the output.
|
|
|
|
Parameters
|
|
----------
|
|
_request : :vtk:`vtkInformation`
|
|
The request information associated with the algorithm.
|
|
inInfo : :vtk:`vtkInformationVector`
|
|
Information vector describing the input data.
|
|
outInfo : :vtk:`vtkInformationVector`
|
|
Information vector where the output data should be placed.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
Status of the execution. Returns 1 on successful completion.
|
|
|
|
"""
|
|
try:
|
|
clipped = wrap(self.GetInputData(inInfo, 0, 0))
|
|
source = wrap(self.GetInputData(inInfo, 1, 0))
|
|
out = self.GetOutputData(outInfo, 0)
|
|
output = source.extract_cells(np.unique(clipped.cell_data['cell_ids']))
|
|
out.ShallowCopy(output)
|
|
except Exception: # pragma: no cover
|
|
traceback.print_exc()
|
|
raise
|
|
return 1
|
|
|
|
|
|
@_deprecate_positional_args(allowed=['inp'])
|
|
def outline_algorithm(inp, generate_faces: bool = False): # noqa: FBT001, FBT002
|
|
"""Add :vtk:`vtkOutlineFilter` to pipeline.
|
|
|
|
Parameters
|
|
----------
|
|
inp : pyvista.Common
|
|
Input data to be filtered.
|
|
generate_faces : bool, default: False
|
|
Whether to generate faces for the outline.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkOutlineFilter`
|
|
Outline filter applied to the input data.
|
|
|
|
"""
|
|
alg = _vtk.vtkOutlineFilter()
|
|
set_algorithm_input(alg, inp)
|
|
alg.SetGenerateFaces(generate_faces)
|
|
return alg
|
|
|
|
|
|
@_deprecate_positional_args(allowed=['inp'])
|
|
def extract_surface_algorithm( # noqa: PLR0917
|
|
inp,
|
|
pass_pointid: bool = False, # noqa: FBT001, FBT002
|
|
pass_cellid: bool = False, # noqa: FBT001, FBT002
|
|
nonlinear_subdivision=1,
|
|
):
|
|
"""Add :vtk:`vtkDataSetSurfaceFilter` to pipeline.
|
|
|
|
Parameters
|
|
----------
|
|
inp : pyvista.Common
|
|
Input data to be filtered.
|
|
pass_pointid : bool, default: False
|
|
If ``True``, pass point IDs to the output.
|
|
pass_cellid : bool, default: False
|
|
If ``True``, pass cell IDs to the output.
|
|
nonlinear_subdivision : int, default: 1
|
|
Level of nonlinear subdivision.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkDataSetSurfaceFilter`
|
|
Surface filter applied to the input data.
|
|
|
|
"""
|
|
surf_filter = _vtk.vtkDataSetSurfaceFilter()
|
|
surf_filter.SetPassThroughPointIds(pass_pointid)
|
|
surf_filter.SetPassThroughCellIds(pass_cellid)
|
|
if nonlinear_subdivision != 1:
|
|
surf_filter.SetNonlinearSubdivisionLevel(nonlinear_subdivision)
|
|
set_algorithm_input(surf_filter, inp)
|
|
return surf_filter
|
|
|
|
|
|
def active_scalars_algorithm(inp, name, preference='point'):
|
|
"""Add a filter that sets the active scalars.
|
|
|
|
Parameters
|
|
----------
|
|
inp : pyvista.Common
|
|
Input data to be filtered.
|
|
name : str
|
|
Name of the scalars to set as active.
|
|
preference : str, default: 'point'
|
|
Preference for the scalars to be set as active. Options are 'point', 'cell', or 'field'.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkAlgorithm`
|
|
Active scalars filter applied to the input data.
|
|
|
|
"""
|
|
alg = ActiveScalarsAlgorithm(
|
|
name=name,
|
|
preference=preference,
|
|
)
|
|
set_algorithm_input(alg, inp)
|
|
return alg
|
|
|
|
|
|
def pointset_to_polydata_algorithm(inp):
|
|
"""Add a filter that casts PointSet to PolyData.
|
|
|
|
Parameters
|
|
----------
|
|
inp : pyvista.PointSet
|
|
Input point set to be cast to PolyData.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkAlgorithm`
|
|
Filter that casts the input PointSet to PolyData.
|
|
|
|
"""
|
|
alg = PointSetToPolyDataAlgorithm()
|
|
set_algorithm_input(alg, inp)
|
|
return alg
|
|
|
|
|
|
@_deprecate_positional_args(allowed=['inp'])
|
|
def add_ids_algorithm(inp, point_ids: bool = True, cell_ids: bool = True): # noqa: FBT001, FBT002
|
|
"""Add a filter that adds point and/or cell IDs.
|
|
|
|
Parameters
|
|
----------
|
|
inp : pyvista.DataSet
|
|
The input data to which the IDs will be added.
|
|
point_ids : bool, default: True
|
|
If ``True``, point IDs will be added to the input data.
|
|
cell_ids : bool, default: True
|
|
If ``True``, cell IDs will be added to the input data.
|
|
|
|
Returns
|
|
-------
|
|
AddIDsAlgorithm
|
|
AddIDsAlgorithm filter.
|
|
|
|
"""
|
|
alg = AddIDsAlgorithm(point_ids=point_ids, cell_ids=cell_ids)
|
|
set_algorithm_input(alg, inp)
|
|
return alg
|
|
|
|
|
|
def crinkle_algorithm(clip, source):
|
|
"""Add a filter that crinkles a clip.
|
|
|
|
Parameters
|
|
----------
|
|
clip : pyvista.DataSet
|
|
The input data to be crinkled.
|
|
source : pyvista.DataSet
|
|
The source of the crinkle.
|
|
|
|
Returns
|
|
-------
|
|
CrinkleAlgorithm
|
|
CrinkleAlgorithm filter.
|
|
|
|
"""
|
|
alg = CrinkleAlgorithm()
|
|
set_algorithm_input(alg, clip, 0)
|
|
set_algorithm_input(alg, source, 1)
|
|
return alg
|
|
|
|
|
|
@_deprecate_positional_args(allowed=['inp'])
|
|
def cell_data_to_point_data_algorithm(inp, pass_cell_data: bool = False): # noqa: FBT001, FBT002
|
|
"""Add a filter that converts cell data to point data.
|
|
|
|
Parameters
|
|
----------
|
|
inp : pyvista.DataSet
|
|
The input data whose cell data will be converted to point data.
|
|
pass_cell_data : bool, default: False
|
|
If ``True``, the original cell data will be passed to the output.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkCellDataToPointData`
|
|
The :vtk:`vtkCellDataToPointData` filter.
|
|
|
|
"""
|
|
alg = _vtk.vtkCellDataToPointData()
|
|
alg.SetPassCellData(pass_cell_data)
|
|
set_algorithm_input(alg, inp)
|
|
return alg
|
|
|
|
|
|
@_deprecate_positional_args(allowed=['inp'])
|
|
def point_data_to_cell_data_algorithm(inp, pass_point_data: bool = False): # noqa: FBT001, FBT002
|
|
"""Add a filter that converts point data to cell data.
|
|
|
|
Parameters
|
|
----------
|
|
inp : pyvista.DataSet
|
|
The input data whose point data will be converted to cell data.
|
|
pass_point_data : bool, default: False
|
|
If ``True``, the original point data will be passed to the output.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkPointDataToCellData`
|
|
:vtk:`vtkPointDataToCellData` algorithm.
|
|
|
|
"""
|
|
alg = _vtk.vtkPointDataToCellData()
|
|
alg.SetPassPointData(pass_point_data)
|
|
set_algorithm_input(alg, inp)
|
|
return alg
|
|
|
|
|
|
def triangulate_algorithm(inp):
|
|
"""Triangulate the input data.
|
|
|
|
Parameters
|
|
----------
|
|
inp : :vtk:`vtkDataObject`
|
|
The input data to be triangulated.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkTriangleFilter`
|
|
The triangle filter that has been applied to the input data.
|
|
|
|
"""
|
|
trifilter = _vtk.vtkTriangleFilter()
|
|
trifilter.PassVertsOff()
|
|
trifilter.PassLinesOff()
|
|
set_algorithm_input(trifilter, inp)
|
|
return trifilter
|
|
|
|
|
|
def decimation_algorithm(inp, target_reduction):
|
|
"""Decimate the input data to the target reduction.
|
|
|
|
Parameters
|
|
----------
|
|
inp : :vtk:`vtkDataObject`
|
|
The input data to be decimated.
|
|
target_reduction : float
|
|
The target reduction amount, as a fraction of the original data.
|
|
|
|
Returns
|
|
-------
|
|
:vtk:`vtkQuadricDecimation`
|
|
The decimation algorithm that has been applied to the input data.
|
|
|
|
"""
|
|
alg = _vtk.vtkQuadricDecimation()
|
|
alg.SetTargetReduction(target_reduction)
|
|
set_algorithm_input(alg, inp)
|
|
return alg
|