Repository: pyvista/pyvista

Issue:
to_tetrahedra active scalars
### Describe the bug, what's wrong, and what you expected.

#4311 passes cell data through the `to_tetrahedra` call. However, after these changes.  The active scalars information is lost.

cc @akaszynski who implemented these changes in that PR.

### Steps to reproduce the bug.

```py
import pyvista as pv
import numpy as np
mesh = pv.UniformGrid(dimensions=(10, 10, 10))
mesh["a"] = np.zeros(mesh.n_cells)
mesh["b"] = np.ones(mesh.n_cells)
print(mesh.cell_data)
tet = mesh.to_tetrahedra()
print(tet.cell_data)
```

```txt
pyvista DataSetAttributes
Association     : CELL
Active Scalars  : a
Active Vectors  : None
Active Texture  : None
Active Normals  : None
Contains arrays :
    a                       float64    (729,)               SCALARS
    b                       float64    (729,)
pyvista DataSetAttributes
Association     : CELL
Active Scalars  : None
Active Vectors  : None
Active Texture  : None
Active Normals  : None
Contains arrays :
    a                       float64    (3645,)
    b                       float64    (3645,)
    vtkOriginalCellIds      int32      (3645,)
```

### System Information

```shell
Python 3.11.2 (main, Mar 23 2023, 17:12:29) [GCC 10.2.1 20210110]

           pyvista : 0.39.0
               vtk : 9.2.6
             numpy : 1.24.2
        matplotlib : 3.7.1
            scooby : 0.7.1
             pooch : v1.7.0
           imageio : 2.27.0
           IPython : 8.12.0
--------------------------------------------------------------------------------
```


### Screenshots

_No response_

Discussion:
MatthewFlamm: > Additionally this PR finds that point data is also not transferred over even though the number of points is maintained. I don't think there is a downside to copying over the point data, so this will be added.

It would be better now to use `pass_data` instead of `pass_cell_data`.  Unfortunately we already released with `pass_cell_data` as an option.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pyvista/pyvista/pull/4406?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) Report
> Merging [#4406](https://app.codecov.io/gh/pyvista/pyvista/pull/4406?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) (782480b) into [main](https://app.codecov.io/gh/pyvista/pyvista/commit/461bb5ed5be8c9dda6e9ed765fab3224149d9c62?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) (461bb5e) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##             main    #4406   +/-   ##
=======================================
  Coverage   95.82%   95.82%           
=======================================
  Files          97       97           
  Lines       20874    20877    +3     
=======================================
+ Hits        20002    20005    +3     
  Misses        872      872           
```

akaszynski: > It would be better now to use `pass_data` instead of `pass_cell_data`. Unfortunately we already released with `pass_cell_data` as an option.

Let's just change it in this PR and deprecate the old one (safely). I agree that it makes more sense. I know that API changes are generally not considered for patch fixes, but if we safely deprecate, I think we're ok for this release since it's been out in the wild for less than a week.
MatthewFlamm: > Let's just change it in this PR and deprecate the old one (safely). I agree that it makes more sense. I know that API changes are generally not considered for patch fixes, but if we safely deprecate, I think we're ok for this release since it's been out in the wild for less than a week.

This means we should ship this in a patch release quickly.  Thanks for fixing the docstring, this also handles not including a deprecation warning, which feels too heavy handed here given the quick deprecation.
akaszynski: > This means we should ship this in a patch release quickly.

Milestone for v0.39.1 is this Friday.



Patch:
diff --git a/examples/02-plot/gif.py b/examples/02-plot/gif.py
index 1c6d1b3b..3c79b9e9 100644
--- a/examples/02-plot/gif.py
+++ b/examples/02-plot/gif.py
@@ -10,11 +10,18 @@ Generate a moving gif from an active plotter.
    "jittery" GIFs, especially for the scalar bar.
 
 """
+# sphinx_gallery_thumbnail_number = 2
 
 import numpy as np
 
 import pyvista as pv
 
+###############################################################################
+# Create a structured grid
+# ~~~~~~~~~~~~~~~~~~~~~~~~
+# Create a structured grid and make a "wave" my shifting the Z position based
+# on the cartesian distance from the origin.
+
 x = np.arange(-10, 10, 0.5)
 y = np.arange(-10, 10, 0.5)
 x, y = np.meshgrid(x, y)
@@ -23,6 +30,13 @@ z = np.sin(r)
 
 # Create and structured surface
 grid = pv.StructuredGrid(x, y, z)
+grid.plot()
+
+
+###############################################################################
+# Generate a GIF
+# ~~~~~~~~~~~~~~
+# Generate a GIF using ``off_screen=True`` parameter.
 
 # Create a plotter object and set the scalars to the Z height
 plotter = pv.Plotter(notebook=False, off_screen=True)
diff --git a/pyvista/core/dataset.py b/pyvista/core/dataset.py
index faa9d391..14643264 100644
--- a/pyvista/core/dataset.py
+++ b/pyvista/core/dataset.py
@@ -25,6 +25,7 @@ import numpy as np
 
 import pyvista
 from pyvista import _vtk
+from pyvista.core.errors import VTKVersionError
 from pyvista.utilities import (
     FieldAssociation,
     abstract_class,
@@ -2419,7 +2420,7 @@ class DataSet(DataSetFilters, DataObject):
         pointb: Iterable[float],
         tolerance=0.0,
     ) -> np.ndarray:
-        """Find the index of cells in this mesh along a line.
+        """Find the index of cells whose bounds intersect a line.
 
         Line is defined from ``pointa`` to ``pointb``.
 
@@ -2431,14 +2432,21 @@ class DataSet(DataSetFilters, DataObject):
         pointb : sequence[float]
             Length 3 coordinate of the end of the line.
 
-        tolerance : float, default: False
+        tolerance : float, default: 0.0
             The absolute tolerance to use to find cells along line.
 
         Returns
         -------
         numpy.ndarray
-            Index or indices of the cell in this mesh that are closest
-            to the given point.
+            Index or indices of the cell(s) whose bounds intersect
+            the line.
+
+        Warnings
+        --------
+        This method returns cells whose bounds intersect the line.
+        This means that the line may not intersect the cell itself.
+        To obtain cells that intersect the line, use
+        :func:`pyvista.DataSet.find_cells_intersecting_line`.
 
         See Also
         --------
@@ -2446,12 +2454,14 @@ class DataSet(DataSetFilters, DataObject):
         DataSet.find_closest_cell
         DataSet.find_containing_cell
         DataSet.find_cells_within_bounds
+        DataSet.find_cells_intersecting_line
 
         Examples
         --------
         >>> import pyvista
         >>> mesh = pyvista.Sphere()
-        >>> index = mesh.find_cells_along_line([0, 0, 0], [0, 0, 1.0])
+        >>> mesh.find_cells_along_line([0.0, 0, 0], [1.0, 0, 0])
+        array([842, 843, 896, 897])
 
         """
         if np.array(pointa).size != 3:
@@ -2465,6 +2475,66 @@ class DataSet(DataSetFilters, DataObject):
         locator.FindCellsAlongLine(pointa, pointb, tolerance, id_list)
         return vtk_id_list_to_array(id_list)
 
+    def find_cells_intersecting_line(
+        self,
+        pointa: Iterable[float],
+        pointb: Iterable[float],
+        tolerance=0.0,
+    ) -> np.ndarray:
+        """Find the index of cells that intersect a line.
+
+        Line is defined from ``pointa`` to ``pointb``.  This
+        method requires vtk version >=9.2.0.
+
+        Parameters
+        ----------
+        pointa : sequence[float]
+            Length 3 coordinate of the start of the line.
+
+        pointb : sequence[float]
+            Length 3 coordinate of the end of the line.
+
+        tolerance : float, default: 0.0
+            The absolute tolerance to use to find cells along line.
+
+        Returns
+        -------
+        numpy.ndarray
+            Index or indices of the cell(s) that intersect
+            the line.
+
+        See Also
+        --------
+        DataSet.find_closest_point
+        DataSet.find_closest_cell
+        DataSet.find_containing_cell
+        DataSet.find_cells_within_bounds
+        DataSet.find_cells_along_line
+
+        Examples
+        --------
+        >>> import pyvista
+        >>> mesh = pyvista.Sphere()
+        >>> mesh.find_cells_intersecting_line([0.0, 0, 0], [1.0, 0, 0])
+        array([896])
+
+        """
+        if pyvista.vtk_version_info < (9, 2, 0):
+            raise VTKVersionError("pyvista.PointSet requires VTK >= 9.2.0")
+
+        if np.array(pointa).size != 3:
+            raise TypeError("Point A must be a length three tuple of floats.")
+        if np.array(pointb).size != 3:
+            raise TypeError("Point B must be a length three tuple of floats.")
+        locator = _vtk.vtkCellLocator()
+        locator.SetDataSet(self)
+        locator.BuildLocator()
+        id_list = _vtk.vtkIdList()
+        points = _vtk.vtkPoints()
+        cell = _vtk.vtkGenericCell()
+        locator.IntersectWithLine(pointa, pointb, tolerance, points, id_list, cell)
+        return vtk_id_list_to_array(id_list)
+
     def find_cells_within_bounds(self, bounds: Iterable[float]) -> np.ndarray:
         """Find the index of cells in this mesh within bounds.
 
diff --git a/pyvista/core/filters/rectilinear_grid.py b/pyvista/core/filters/rectilinear_grid.py
index 97800de2..b4f3f468 100644
--- a/pyvista/core/filters/rectilinear_grid.py
+++ b/pyvista/core/filters/rectilinear_grid.py
@@ -2,24 +2,29 @@
 
 import collections
 from typing import Sequence, Union
+import warnings
 
 import numpy as np
 
 from pyvista import _vtk, abstract_class
 from pyvista.core.filters import _get_output, _update_alg
+from pyvista.utilities import assert_empty_kwargs
+from pyvista.utilities.misc import PyVistaDeprecationWarning
 
 
 @abstract_class
 class RectilinearGridFilters:
     """An internal class to manage filters/algorithms for rectilinear grid datasets."""
 
+    # Note remove kwargs when removing deprecation for pass_cell_data
     def to_tetrahedra(
         self,
         tetra_per_cell: int = 5,
         mixed: Union[Sequence[int], bool] = False,
         pass_cell_ids: bool = True,
-        pass_cell_data: bool = True,
+        pass_data: bool = True,
         progress_bar: bool = False,
+        **kwargs,
     ):
         """Create a tetrahedral mesh structured grid.
 
@@ -46,7 +51,7 @@ class RectilinearGridFilters:
             :class:`pyvista.RectilinearGrid`. The name of this array is
             ``'vtkOriginalCellIds'`` within the ``cell_data``.
 
-        pass_cell_data : bool, default: True
+        pass_data : bool, default: True
             Set to ``True`` to make the tetrahedra mesh have the cell data from
             the original :class:`pyvista.RectilinearGrid`.  This uses
             ``pass_cell_ids=True`` internally. If ``True``, ``pass_cell_ids``
@@ -55,6 +60,9 @@ class RectilinearGridFilters:
         progress_bar : bool, default: False
             Display a progress bar to indicate progress.
 
+        **kwargs : dict, optional
+            Deprecated keyword argument ``pass_cell_data``.
+
         Returns
         -------
         pyvista.UnstructuredGrid
@@ -87,8 +95,17 @@ class RectilinearGridFilters:
         >>> tet_grid.explode(factor=0.5).plot(show_edges=True)
 
         """
+        # Note remove this section when deprecation is done
+        pass_cell_data = kwargs.pop("pass_cell_data", None)
+        assert_empty_kwargs(**kwargs)
+        if pass_cell_data is not None:
+            warnings.warn(
+                "pass_cell_data is a deprecated option, use pass_data", PyVistaDeprecationWarning
+            )
+            pass_data = pass_cell_data
+
         alg = _vtk.vtkRectilinearGridToTetrahedra()
-        alg.SetRememberVoxelId(pass_cell_ids or pass_cell_data)
+        alg.SetRememberVoxelId(pass_cell_ids or pass_data)
         if mixed is not False:
             if isinstance(mixed, str):
                 self.cell_data.active_scalars_name = mixed
@@ -116,16 +133,27 @@ class RectilinearGridFilters:
         alg.SetInputData(self)
         _update_alg(alg, progress_bar, 'Converting to tetrahedra')
         out = _get_output(alg)
-        if pass_cell_data:
+
+        if pass_data:
             # algorithm stores original cell ids in active scalars
+            # this does not preserve active scalars, but we need to
+            # keep active scalars until they are renamed
             for name in self.cell_data:  # type: ignore
                 if name != out.cell_data.active_scalars_name:
                     out[name] = self.cell_data[name][out.cell_data.active_scalars]  # type: ignore
 
+            for name in self.point_data:  # type: ignore
+                out[name] = self.point_data[name]  # type: ignore
+
         if alg.GetRememberVoxelId():
             # original cell_ids are not named and are the active scalars
             out.cell_data.set_array(
                 out.cell_data.pop(out.cell_data.active_scalars_name), 'vtkOriginalCellIds'
             )
 
+        if pass_data:
+            # Now reset active scalars in cast the original mesh had data with active scalars
+            association, name = self.active_scalars_info  # type: ignore
+            out.set_active_scalars(name, preference=association)
+
         return out
diff --git a/pyvista/utilities/sphinx_gallery.py b/pyvista/utilities/sphinx_gallery.py
index de14f974..ebae4064 100644
--- a/pyvista/utilities/sphinx_gallery.py
+++ b/pyvista/utilities/sphinx_gallery.py
@@ -55,6 +55,7 @@ class Scraper:
             fname = next(image_path_iterator)
             if hasattr(plotter, '_gif_filename'):
                 # move gif to fname
+                fname = fname[:-3] + "gif"
                 shutil.move(plotter._gif_filename, fname)
             else:
                 plotter.screenshot(fname)
diff --git a/requirements_docs.txt b/requirements_docs.txt
index d0caa674..16b68081 100644
--- a/requirements_docs.txt
+++ b/requirements_docs.txt
@@ -10,7 +10,7 @@ jupyter_sphinx==0.4.0
 jupyterlab==3.6.3
 lxml==4.9.2
 meshio==5.3.4
-mypy==1.2.0
+mypy==1.3.0
 mypy-extensions==1.0.0
 numpydoc==1.5.0
 osmnx==1.3.0
@@ -30,8 +30,8 @@ sphinx-notfound-page==0.8.3
 sphinx-toolbox==3.4.0
 sphinxcontrib-asciinema==0.3.7
 sphinxcontrib-websupport==1.2.4
-sympy==1.11.1
-trame==2.4.0
+sympy==1.12
+trame==2.4.1
 trame-client==2.7.5
 trame-server==2.11.0
 trame-vtk==2.4.4


Test patch:
diff --git a/requirements_test.txt b/requirements_test.txt
index 45675cd1..0b80c164 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -23,7 +23,7 @@ pytest_pyvista==0.1.8
 pythreejs<2.5.0
 Sphinx<6.3.0
 sphinx-gallery<0.14.0
-sympy<1.12.0
+sympy<1.13.0
 tqdm<4.66.0
 trame>=2.2.6,<2.5
 trame-client>=2.5.1,<2.8
diff --git a/tests/filters/test_rectilinear_grid.py b/tests/filters/test_rectilinear_grid.py
index ab9eb186..cc6bc190 100644
--- a/tests/filters/test_rectilinear_grid.py
+++ b/tests/filters/test_rectilinear_grid.py
@@ -2,6 +2,7 @@ import numpy as np
 import pytest
 
 import pyvista as pv
+from pyvista.utilities.misc import PyVistaDeprecationWarning
 
 
 @pytest.fixture
@@ -48,22 +49,39 @@ def test_to_tetrahedral_edge_case():
 
 
 def test_to_tetrahedral_pass_cell_ids(tiny_rectilinear):
-    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=False, pass_cell_data=False)
+    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=False, pass_data=False)
     assert not tet_grid.cell_data
-    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=True, pass_cell_data=False)
+    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=True, pass_data=False)
     assert 'vtkOriginalCellIds' in tet_grid.cell_data
     assert np.issubdtype(tet_grid.cell_data['vtkOriginalCellIds'].dtype, np.integer)
 
 
 def test_to_tetrahedral_pass_cell_data(tiny_rectilinear):
+    # test that data isn't passed
     tiny_rectilinear["cell_data"] = np.ones(tiny_rectilinear.n_cells)
-
-    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=False, pass_cell_data=False)
+    tiny_rectilinear["point_data"] = np.arange(tiny_rectilinear.n_points)
+    tiny_rectilinear.set_active_scalars("cell_data")
+    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=False, pass_data=False)
     assert not tet_grid.cell_data
+    assert not tet_grid.point_data
 
-    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=False, pass_cell_data=True)
+    # test with cell data
+    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=False, pass_data=True)
     assert tet_grid.cell_data
     assert "cell_data" in tet_grid.cell_data
+    assert "point_data" in tet_grid.point_data
+    assert tet_grid.active_scalars_name == "cell_data"
 
     # automatically added
     assert 'vtkOriginalCellIds' in tet_grid.cell_data
+
+    with pytest.warns(PyVistaDeprecationWarning):
+        tiny_rectilinear.to_tetrahedra(pass_cell_data=True)
+        if pv._version.version_info >= (0, 43, 0):
+            raise RuntimeError('Remove this deprecated kwarg')
+
+    # Test point data active
+    tiny_rectilinear.set_active_scalars("point_data")
+    assert tiny_rectilinear.active_scalars_name == "point_data"
+    tet_grid = tiny_rectilinear.to_tetrahedra(pass_cell_ids=False, pass_data=True)
+    assert tet_grid.active_scalars_name == "point_data"
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index 47558bf9..ba928337 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -975,6 +975,28 @@ def test_find_cells_along_line():
     assert len(indices) == 2
 
 
+def test_find_cells_intersecting_line():
+    if pyvista.vtk_version_info >= (9, 2, 0):
+        mesh = pyvista.Sphere()
+        indices = mesh.find_cells_intersecting_line([0, 0, 0.0], [1.0, 0, 0.0])
+        assert len(indices) == 1
+
+        indices = mesh.find_cells_intersecting_line([0, 0, 0.0], [1.0, 0, 0.0], tolerance=0.01)
+        assert len(indices) == 2
+
+        with pytest.raises(TypeError):
+            mesh.find_cells_intersecting_line([0, 0], [1.0, 0, 0.0])
+
+        with pytest.raises(TypeError):
+            mesh.find_cells_intersecting_line([0, 0, 0.0], [1.0, 0])
+
+    else:
+        with pytest.raises(VTKVersionError):
+            mesh = pyvista.Sphere()
+            indices = mesh.find_cells_intersecting_line([0, 0, 0.0], [1.0, 0, 0.0])
+            assert len(indices) == 1
+
+
 def test_find_cells_within_bounds():
     mesh = pyvista.Cube()
 
diff --git a/tests/test_scraper.py b/tests/test_scraper.py
index bb8ca04f..dd199c9e 100644
--- a/tests/test_scraper.py
+++ b/tests/test_scraper.py
@@ -18,12 +18,22 @@ def test_scraper(tmpdir, monkeypatch, n_win):
     monkeypatch.setattr(pyvista, 'BUILDING_GALLERY', True)
     pyvista.close_all()
     plotters = [pyvista.Plotter(off_screen=True) for _ in range(n_win)]
+    plotter_gif = pyvista.Plotter()
+
     scraper = Scraper()
     src_dir = str(tmpdir)
     out_dir = op.join(str(tmpdir), '_build', 'html')
+
     img_fnames = [
         op.join(src_dir, 'auto_examples', 'images', f'sg_img_{n}.png') for n in range(n_win)
     ]
+
+    # create and save GIF to tmpdir
+    gif_path = op.abspath(tmpdir + 'sg_img_0.gif')
+    plotter_gif.open_gif(gif_path)
+    plotter_gif.write_frame()
+    plotter_gif.close()
+
     gallery_conf = {"src_dir": src_dir, "builder_name": "html"}
     target_file = op.join(src_dir, 'auto_examples', 'sg.py')
     block = None
@@ -32,9 +42,13 @@ def test_scraper(tmpdir, monkeypatch, n_win):
         example_globals=dict(a=1),
         target_file=target_file,
     )
+
     os.makedirs(op.dirname(img_fnames[0]))
     for img_fname in img_fnames:
         assert not os.path.isfile(img_fname)
+
+    # add gif to list after checking other filenames are empty
+    img_fnames.append(gif_path)
     os.makedirs(out_dir)
     scraper(block, block_vars, gallery_conf)
     for img_fname in img_fnames: