Repository: pyvista/pyvista

Issue:
vtkSurfaceReconstructionFilter: reconstruct surface from point cloud around 3D volume
Over time, I have seen a ton of questions asking how to triangulate the surface of a 3D object from a point cloud. As many examples have demonstrated, this is tough to do with delaunay triangualation:

- https://github.com/pyvista/pyvista-support/issues/397
- https://github.com/pyvista/pyvista-support/issues/213
- https://github.com/pyvista/pyvista-support/issues/170
- https://github.com/pyvista/pyvista-support/issues/178
- and many more...

This task is actually handled quite well by the [`vtkSurfaceReconstructionFilter` filter in VTK](https://vtk.org/doc/nightly/html/classvtkSurfaceReconstructionFilter.html#details) and we should work to make this a new filter in PyVista for `PolyData` meshes. Here is example usage:

```py
import pyvista as pv
import numpy as np
from pyvista import _vtk as vtk

points = pv.wrap(pv.Sphere().points)

alg = vtk.vtkSurfaceReconstructionFilter()
alg.SetInputDataObject(points)
alg.Update()
mc = vtk.vtkMarchingCubes()
mc.SetInputDataObject(alg.GetOutput())
mc.SetValue(0, 0.0)
mc.Update()
surf = pv.wrap(mc.GetOutput())
surf.clear_data()

p = pv.Plotter(shape=(1,2))
p.add_mesh(points)
p.add_title('Point Cloud of 3D Surface')
p.subplot(0,1)
p.add_mesh(surf, color=True, show_edges=True)
p.add_title('Reconstructed Surface')
p.show()
```

<img width="768" alt="Screen Shot 2021-08-28 at 1 28 00 PM" src="https://user-images.githubusercontent.com/22067021/131228784-a6dd7278-338c-404f-bf7f-2bd0da790979.png">

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pyvista/pyvista/pull/1617?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) Report
> Merging [#1617](https://codecov.io/gh/pyvista/pyvista/pull/1617?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) (fcf2b79) into [main](https://codecov.io/gh/pyvista/pyvista/commit/790b7b4f4bb8afc86ab36d14313cabd04cfbf854?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) (790b7b4) will **increase** coverage by `0.02%`.
> The diff coverage is `100.00%`.

> :exclamation: Current head fcf2b79 differs from pull request most recent head 5c3b04c. Consider uploading reports for the commit 5c3b04c to get more accurate results
```diff
@@            Coverage Diff             @@
##             main    #1617      +/-   ##
==========================================
+ Coverage   90.30%   90.33%   +0.02%     
==========================================
  Files          70       70              
  Lines       13352    13369      +17     
==========================================
+ Hits        12058    12077      +19     
+ Misses       1294     1292       -2     
```


Patch:
diff --git a/.github/workflows/intersphinx-update-pull-request.yml b/.github/workflows/intersphinx-update-pull-request.yml
new file mode 100644
index 00000000..4acbeb7a
--- /dev/null
+++ b/.github/workflows/intersphinx-update-pull-request.yml
@@ -0,0 +1,37 @@
+name: Update Local Intersphinx Mappings
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: "0 3 1 * *"
+
+
+jobs:
+  intersphinx_update:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: python
+      run: curl https://docs.python.org/dev/objects.inv > doc/intersphinx/python-inv.txt
+    - name: scipy
+      run: curl https://docs.scipy.org/doc/scipy/reference/objects.inv > doc/intersphinx/scipy-inv.txt
+    - name: numpy
+      run: curl https://numpy.org/devdocs/objects.inv > doc/intersphinx/numpy-inv.txt
+    - name: matplotlib
+      run: curl https://matplotlib.org/stable/objects.inv > doc/intersphinx/matplotlib-inv.txt
+    - name: imageio
+      run: curl https://imageio.readthedocs.io/en/stable/objects.inv > doc/intersphinx/imageio-inv.txt
+    - name: pandas
+      run: curl https://pandas.pydata.org/pandas-docs/stable/objects.inv > doc/intersphinx/pandas-inv.txt
+    - name: pytest
+      run: curl https://docs.pytest.org/en/stable/objects.inv > doc/intersphinx/pytest-inv.txt
+
+    - name: Create Pull Request
+      uses: peter-evans/create-pull-request@v3
+      with:
+        commit-message: "[create-pull-request] update local intersphinx"
+        body: |
+          Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action
+          These changes update all the intersphinx mappings. To run CI close and reopen PR.
+        title: "Automated PR: Update Local Intersphinx"
diff --git a/.gitignore b/.gitignore
index 239b681d..be52eb7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,7 +30,7 @@ doc/_build
 wiki/
 **/beam.gif
 **/beam_wireframe.gif
-
+doc/build_errors.txt
 
 # testing coverage and cache
 .pytest_cache/
diff --git a/Makefile b/Makefile
index 20d5187b..fad7cc45 100644
--- a/Makefile
+++ b/Makefile
@@ -29,6 +29,9 @@ doctest-modules-local-namespace:
 	@echo "Running module doctesting using docstring local namespace"
 	python tests/check_doctest_names.py
 
+example-coverage:
+	python -m ansys.tools.example_coverage -f pyvista
+
 coverage:
 	@echo "Running coverage"
 	@pytest -v --cov pyvista
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 035fe091..7ef218bc 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -300,10 +300,21 @@ jobs:
     displayName: Build pyvista doc examples cache
 
   - script: |
-      make -C doc phtml -b coverage SPHINXOPTS="-W"
-      cat doc/_build/coverage/python.txt
+      make -C doc html SPHINXOPTS="-j auto -w build_errors.txt -N"
     displayName: 'Build documentation'
 
+  # Verify that sphinx generates no warnings
+  - script: |
+      python doc/print_errors.py
+    displayName: Check for warnings
+
+  # report coverage.  This must be run separately as to not overwrite
+  # build errors file.  Run quietly since we've already run sphinx.
+  - script: |
+      make -C doc html SPHINXOPTS="-Q" -b coverage
+      cat doc/_build/coverage/python.txt
+    displayName: Report documentation coverage
+
   # Push example notebooks to pyvista-examples
   - script: |
       pip install cookiecutter
diff --git a/doc/extras/docker.rst b/doc/extras/docker.rst
index f22679f9..5eb8db25 100644
--- a/doc/extras/docker.rst
+++ b/doc/extras/docker.rst
@@ -5,7 +5,7 @@ PyVista within a Docker Container
 You can use ``pyvista`` from within a docker container with
 jupyterlab.  To create a local docker image install ``docker`` and be
 sure you've logged into docker by following the directions at
-`Configuring Docker for use with GitHub Packages <https://docs.github.com/en/free-pro-team@latest/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-with-a-personal-access-token>`_)
+`Working with the Docker registry <https://docs.github.com/en/enterprise-server@3.0/packages/working-with-a-github-packages-registry/working-with-the-docker-registry>`_)
 
 Next pull and run the image with:
 
diff --git a/doc/extras/flask.rst b/doc/extras/flask.rst
index e8c122ca..46533084 100644
--- a/doc/extras/flask.rst
+++ b/doc/extras/flask.rst
@@ -4,7 +4,7 @@ Flask Application
 =================
 You can use ``pyvista`` in to make a flask application to display
 static plots.  See the following example as well as the demo at `Flask
-Example <https://github.com/pyvista/pyvista/raw/main/examples_flask>`__.
+Example <https://github.com/pyvista/pyvista/tree/main/examples_flask>`__.
 
 For dynamic examples, it's recommended to use `Jupyter Notebooks <https://jupyter.org/>`__.  See our documentation regarding this at :ref:`jupyter_plotting`.
 
diff --git a/doc/getting-started/why.rst b/doc/getting-started/why.rst
index 9906eee7..7dd5039f 100644
--- a/doc/getting-started/why.rst
+++ b/doc/getting-started/why.rst
@@ -13,9 +13,10 @@ file:
 
 Plotting a Mesh using Python's VTK
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Using this `example <http://www.vtk.org/Wiki/VTK/Examples/Python/STLReader>`_,
-loading and plotting an STL file requires a lot of code when using only the
-``vtk`` module.
+Using this `example
+<https://kitware.github.io/vtk-examples/site/Python/IO/ReadSTL/>`_ as
+a baseline example, loading and plotting an STL file requires a lot of
+code when using only the ``vtk`` module.
 
 .. code:: python
 
diff --git a/doc/print_errors.py b/doc/print_errors.py
new file mode 100644
index 00000000..0681b78e
--- /dev/null
+++ b/doc/print_errors.py
@@ -0,0 +1,33 @@
+"""Read errors output from a sphinx build and remove duplicate groups"""
+import sys
+import os
+import pathlib
+
+sys.tracebacklimit = 0
+my_path = pathlib.Path(__file__).parent.resolve()
+
+errors = set()
+error_file = os.path.join(my_path, 'build_errors.txt')
+if os.path.isfile(error_file):
+    with open(error_file) as fid:
+        group = []
+        for line in fid.readlines():
+            line = line.strip()
+            if line:
+                group.append(line)
+            else:
+                errors.add('\n'.join(group))
+                group = []
+
+    for error in list(errors):
+        print(error)
+        print()
+
+    # There should be no errors here since sphinx will have exited
+    print()
+    if errors:
+        raise Exception(f'Sphinx reported unique {len(errors)} warnings\n\n')
+else:
+    print(f'build_errors.txt not found at {my_path}')
+
+print(f'Sphinx Reported no warnings\n\n')
diff --git a/doc/user-guide/simple.rst b/doc/user-guide/simple.rst
index ae288f99..0a65fc85 100644
--- a/doc/user-guide/simple.rst
+++ b/doc/user-guide/simple.rst
@@ -171,7 +171,7 @@ Be sure to check out all the available plotters for your use case:
 * :class:`pyvista.Plotter`: The standard plotter that pauses the code until closed
 * :class:`pyvistaqt.BackgroundPlotter`: Creates a rendering window that is interactive and does not pause the code execution (for more information see the `pyvistaqt`_ package)
 
-.. _pyvistaqt: http://qtdocs.pyvista.org/
+.. _pyvistaqt: https://qtdocs.pyvista.org/
 
 
 
diff --git a/examples/01-filter/connectivity.py b/examples/01-filter/connectivity.py
index 210e8ea0..0f2b238e 100644
--- a/examples/01-filter/connectivity.py
+++ b/examples/01-filter/connectivity.py
@@ -4,7 +4,7 @@ Connectivity
 
 Use the connectivity filter to remove noisy isosurfaces.
 
-This example is very similar to `this VTK example <https://lorensen.github.io/VTKExamples/site/Python/VisualizationAlgorithms/PineRootConnectivity/>`__
+This example is very similar to `this VTK example <https://kitware.github.io/vtk-examples/site/Python/VisualizationAlgorithms/PineRootConnectivity/>`__
 """
 # sphinx_gallery_thumbnail_number = 2
 import pyvista as pv
diff --git a/examples/01-filter/surface_reconstruction.py b/examples/01-filter/surface_reconstruction.py
new file mode 100644
index 00000000..e8c4b079
--- /dev/null
+++ b/examples/01-filter/surface_reconstruction.py
@@ -0,0 +1,30 @@
+"""
+.. _surface_reconstruction_example:
+
+Surface Reconstruction
+~~~~~~~~~~~~~~~~~~~~~~
+
+Surface reconstruction has a dedicated filter in PyVista and is
+handled by :func:`pyvista.DataSetFilters.reconstruct_surface`.  This
+tends to perform much better than :func:`DataSetFilters.delaunay_3d`.
+
+"""
+import pyvista as pv
+
+###############################################################################
+# Create a point cloud from a sphere and then reconstruct a surface from it.
+
+points = pv.wrap(pv.Sphere().points)
+surf = points.reconstruct_surface()
+surf
+
+###############################################################################
+# Plot the point cloud and the reconstructed sphere.
+
+pl = pv.Plotter(shape=(1,2))
+pl.add_mesh(points)
+pl.add_title('Point Cloud of 3D Surface')
+pl.subplot(0,1)
+pl.add_mesh(surf, color=True, show_edges=True)
+pl.add_title('Reconstructed Surface')
+pl.show()
diff --git a/examples/99-advanced/warp-by-vector-eigenmodes.py b/examples/99-advanced/warp-by-vector-eigenmodes.py
index 98182260..ec83b5dc 100644
--- a/examples/99-advanced/warp-by-vector-eigenmodes.py
+++ b/examples/99-advanced/warp-by-vector-eigenmodes.py
@@ -4,10 +4,10 @@ Displaying eigenmodes of vibration using ``warp_by_vector``
 
 This example applies the ``warp_by_vector`` filter to a cube whose eigenmodes
 have been computed using the Ritz method, as outlined in Visscher, William M.,
-Albert Migliori, Thomas M. Bell, et Robert A. Reinert. « On the normal modes
-of free vibration of inhomogeneous and anisotropic elastic objects ». The
-Journal of the Acoustical Society of America 90, nᵒ 4 (october 1991): 2154‑62.
-https://doi.org/10.1121/1.401643.
+Albert Migliori, Thomas M. Bell, et Robert A. Reinert. "On the normal modes
+of free vibration of inhomogeneous and anisotropic elastic objects". The
+Journal of the Acoustical Society of America 90, n.4 (october 1991): 2154-62.
+https://asa.scitation.org/doi/10.1121/1.401643
 
 """
 
diff --git a/pyvista/_vtk.py b/pyvista/_vtk.py
index 9b4e3bad..96f2f9a5 100644
--- a/pyvista/_vtk.py
+++ b/pyvista/_vtk.py
@@ -20,7 +20,8 @@ except ImportError:  # pragma: no cover
 
 if VTK9:
 
-    from vtkmodules.vtkImagingHybrid import vtkSampleFunction
+    from vtkmodules.vtkImagingHybrid import (vtkSampleFunction,
+                                             vtkSurfaceReconstructionFilter)
     from vtkmodules.vtkInteractionWidgets import (vtkScalarBarWidget,
                                                   vtkSplineWidget,
                                                   vtkSphereWidget,
diff --git a/pyvista/core/datasetattributes.py b/pyvista/core/datasetattributes.py
index 0f4480c7..0d175ecb 100644
--- a/pyvista/core/datasetattributes.py
+++ b/pyvista/core/datasetattributes.py
@@ -939,9 +939,15 @@ class DataSetAttributes(_vtk.VTKObjectWrapper):
         """
         keys = []
         for i in range(self.GetNumberOfArrays()):
-            name = self.VTKObject.GetAbstractArray(i).GetName()
+            array = self.VTKObject.GetAbstractArray(i)
+            name = array.GetName()
             if name:
                 keys.append(name)
+            else:  # pragma: no cover
+                # Assign this array a name
+                name = f'Unnamed_{i}'
+                array.SetName(name)
+                keys.append(name)
         return keys
 
     def values(self) -> List[pyvista_ndarray]:
diff --git a/pyvista/core/filters/data_set.py b/pyvista/core/filters/data_set.py
index 7efa4293..d0133961 100644
--- a/pyvista/core/filters/data_set.py
+++ b/pyvista/core/filters/data_set.py
@@ -2230,7 +2230,10 @@ class DataSetFilters:
     def delaunay_3d(self, alpha=0, tol=0.001, offset=2.5, progress_bar=False):
         """Construct a 3D Delaunay triangulation of the mesh.
 
-        This helps smooth out a rugged mesh.
+        This filter can be used to generate a 3D tetrahedral mesh from
+        a surface or scattered points.  If you want to create a
+        surface from a point cloud, see
+        :func:`pyvista.DataSetFilters.reconstruct_surface`.
 
         Parameters
         ----------
@@ -4417,3 +4420,82 @@ class DataSetFilters:
         t = transformations.reflection(normal, point=point)
         return self.transform(t, transform_all_input_vectors=transform_all_input_vectors,
                                  inplace=inplace, progress_bar=progress_bar)
+
+    def reconstruct_surface(self, nbr_sz=None, sample_spacing=None,
+                            progress_bar=False):
+        """Reconstruct a surface from the points in this dataset.
+
+        This filter takes a list of points assumed to lie on the
+        surface of a solid 3D object. A signed measure of the distance
+        to the surface is computed and sampled on a regular grid. The
+        grid can then be contoured at zero to extract the surface. The
+        default values for neighborhood size and sample spacing should
+        give reasonable results for most uses but can be set if
+        desired.
+
+        This is helpful when generating surfaces from point clouds and
+        is more reliable than :func:`DataSetFilters.delaunay_3d`.
+
+        Parameters
+        ----------
+        nbr_sz : int, optional
+            Specify the number of neighbors each point has, used for
+            estimating the local surface orientation.
+
+            The default value of 20 should be fine for most
+            applications, higher values can be specified if the spread
+            of points is uneven. Values as low as 10 may yield
+            adequate results for some surfaces. Higher values cause
+            the algorithm to take longer and will cause
+            errors on sharp boundaries.
+
+        sample_spacing : float, optional
+            The spacing of the 3D sampling grid.  If not set, a
+            reasonable guess will be made.
+
+        progress_bar : bool, optional
+            Display a progress bar to indicate progress.
+
+        Returns
+        -------
+        pyvista.PolyData
+            Reconstructed surface.
+
+        Examples
+        --------
+        Create a point cloud out of a sphere and reconstruct a surface
+        from it.
+
+        >>> import pyvista as pv
+        >>> points = pv.wrap(pv.Sphere().points)
+        >>> surf = points.reconstruct_surface()
+
+        >>> pl = pv.Plotter(shape=(1,2))
+        >>> _ = pl.add_mesh(points)
+        >>> _ = pl.add_title('Point Cloud of 3D Surface')
+        >>> pl.subplot(0,1)
+        >>> _ = pl.add_mesh(surf, color=True, show_edges=True)
+        >>> _ = pl.add_title('Reconstructed Surface')
+        >>> pl.show()
+
+        See :ref:`surface_reconstruction_example` for more examples
+        using this filter.
+
+        """
+        alg = _vtk.vtkSurfaceReconstructionFilter()
+        alg.SetInputDataObject(self)
+        if nbr_sz is not None:
+            alg.SetNeighborhoodSize(nbr_sz)
+        if sample_spacing is not None:
+            alg.SetSampleSpacing(sample_spacing)
+
+        # connect using ports as this will be slightly faster
+        mc = _vtk.vtkMarchingCubes()
+        mc.SetComputeNormals(False)
+        mc.SetComputeScalars(False)
+        mc.SetComputeGradients(False)
+        mc.SetInputConnection(alg.GetOutputPort())
+        mc.SetValue(0, 0.0)
+        _update_alg(mc, progress_bar, 'Reconstructing surface')
+        surf = pyvista.wrap(mc.GetOutput())
+        return surf
diff --git a/pyvista/core/filters/poly_data.py b/pyvista/core/filters/poly_data.py
index 03db5b97..2b61da10 100644
--- a/pyvista/core/filters/poly_data.py
+++ b/pyvista/core/filters/poly_data.py
@@ -2111,6 +2111,10 @@ class PolyDataFilters(DataSetFilters):
                     inplace=False, edge_source=None, progress_bar=False):
         """Apply a 2D Delaunay filter along the best fitting plane.
 
+        This filter can be used to generate a 2d surface from a set of
+        points on a plane.  If you want to create a surface from a
+        point cloud, see :func:`pyvista.DataSetFilters.reconstruct_surface`.
+
         Parameters
         ----------
         tol : float, optional
diff --git a/pyvista/core/pointset.py b/pyvista/core/pointset.py
index d750e82a..7e3bf172 100644
--- a/pyvista/core/pointset.py
+++ b/pyvista/core/pointset.py
@@ -72,7 +72,7 @@ class PointSet(DataSet):
 
         Parameters
         ----------
-        ind : iterable
+        ind : sequence
             Cell indices to be removed.  The array can also be a
             boolean array of the same size as the number of cells.
 
@@ -1634,7 +1634,7 @@ class StructuredGrid(_vtk.vtkStructuredGrid, PointGrid, StructuredGridFilters):
 
         Parameters
         ----------
-        ind : iterable
+        ind : sequence
             List or array of cell indices to be hidden.  The array can
             also be a boolean array of the same size as the number of
             cells.
@@ -1651,6 +1651,7 @@ class StructuredGrid(_vtk.vtkStructuredGrid, PointGrid, StructuredGridFilters):
         >>> x, y, z = np.meshgrid(x, y, z)
         >>> grid = pv.StructuredGrid(x, y, z)
         >>> grid.hide_cells(range(79*30, 79*50))
+        >>> grid.plot(color=True, show_edges=True)
         """
         if isinstance(ind, np.ndarray):
             if ind.dtype == np.bool_ and ind.size != self.n_cells:
@@ -1665,7 +1666,44 @@ class StructuredGrid(_vtk.vtkStructuredGrid, PointGrid, StructuredGridFilters):
         # properly, additionally, calling self.RemoveGhostCells will
         # have no effect
 
-        self.cell_data[_vtk.vtkDataSetAttributes.GhostArrayName()] = ghost_cells
+        # add but do not make active
+        self.cell_data.set_array(ghost_cells, _vtk.vtkDataSetAttributes.GhostArrayName())
+
+    def hide_points(self, ind):
+        """Hide points without deleting them.
+
+        Hides points by setting the ghost_points array to ``HIDDEN_CELL``.
+
+        Parameters
+        ----------
+        ind : sequence
+            List or array of point indices to be hidden.  The array
+            can also be a boolean array of the same size as the number
+            of points.
+
+        Examples
+        --------
+        Hide part of the middle of a structured surface.
+
+        >>> import pyvista as pv
+        >>> import numpy as np
+        >>> x = np.arange(-10, 10, 0.25)
+        >>> y = np.arange(-10, 10, 0.25)
+        >>> z = 0
+        >>> x, y, z = np.meshgrid(x, y, z)
+        >>> grid = pv.StructuredGrid(x, y, z)
+        >>> grid.hide_points(range(80*30, 80*50))
+        >>> grid.plot(color=True, show_edges=True)
+        """
+        if isinstance(ind, np.ndarray):
+            if ind.dtype == np.bool_ and ind.size != self.n_points:
+                raise ValueError('Boolean array size must match the '
+                                 f'number of points ({self.n_points})')
+        ghost_points = np.zeros(self.n_points, np.uint8)
+        ghost_points[ind] = _vtk.vtkDataSetAttributes.HIDDENPOINT
+
+        # add but do not make active
+        self.point_data.set_array(ghost_points, _vtk.vtkDataSetAttributes.GhostArrayName())
 
     def _reshape_point_array(self, array):
         """Reshape point data to a 3-D matrix."""


Test patch:
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 063aac7f..3433db39 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -1651,3 +1651,9 @@ def test_collision_solid_non_triangle(hexbeam):
     assert n_collision > 40
     assert 'ContactCells' in output.field_data
     assert output.is_all_triangles
+
+
+def test_reconstruct_surface(sphere):
+    pc = pyvista.wrap(sphere.points)
+    surf = pc.reconstruct_surface(nbr_sz=10, sample_spacing=50)
+    assert surf.is_all_triangles
diff --git a/tests/test_grid.py b/tests/test_grid.py
index ffa8aecd..a80dc20c 100644
--- a/tests/test_grid.py
+++ b/tests/test_grid.py
@@ -9,14 +9,14 @@ import vtk
 import pyvista
 from pyvista import examples
 from pyvista.plotting import system_supports_plotting
+from pyvista._vtk import VTK9
 
 test_path = os.path.dirname(os.path.abspath(__file__))
 
-VTK9 = vtk.vtkVersion().GetVTKMajorVersion() >= 9
-
 # 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
 
 
 def test_volume(hexbeam):
@@ -853,13 +853,21 @@ def test_remove_cells_invalid(hexbeam):
 @pytest.mark.parametrize('ind', [range(10), np.arange(10),
                                  STRUCTGRID_CELLS_BOOL])
 def test_hide_cells(ind, struct_grid):
-    sgrid_copy = struct_grid.copy()
-    sgrid_copy.hide_cells(ind)
-    assert sgrid_copy.HasAnyBlankCells()
+    struct_grid.hide_cells(ind)
+    assert struct_grid.HasAnyBlankCells()
 
     with pytest.raises(ValueError, match='Boolean array size must match'):
-        sgrid_copy.hide_cells(np.ones(10, dtype=np.bool))
+        struct_grid.hide_cells(np.ones(10, dtype=np.bool))
+
 
+@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))
 
 
 @pytest.mark.skipif(not VTK9, reason='VTK 9 or higher is required')