Repair a Surface with fill_holes#

Seal small openings in a surface with pyvista.PolyDataFilters.fill_holes().

import numpy as np
import pyvista as pv

Punch holes in a closed surface#

Start from a watertight Sphere(), then drop a handful of faces to create three open holes of different sizes.

rng = np.random.default_rng(seed=0)
sphere = pv.Sphere(theta_resolution=60, phi_resolution=60).triangulate()

centers = sphere.cell_centers().points
seed_indices = [
    int(np.argmin(np.linalg.norm(centers - target, axis=1)))
    for target in [(0.5, 0.0, 0.0), (-0.3, 0.4, 0.1), (0.0, -0.4, -0.3)]
]
hole_sizes = [60, 25, 10]  # cells removed per hole, controls each hole's radius

drop_cells = set()
for seed, size in zip(seed_indices, hole_sizes, strict=True):
    candidates = np.argsort(np.linalg.norm(centers - centers[seed], axis=1))[:size]
    drop_cells.update(int(c) for c in candidates)

keep_mask = np.ones(sphere.n_cells, dtype=bool)
keep_mask[list(drop_cells)] = False
open_mesh = sphere.extract_cells(keep_mask).extract_surface(algorithm=None)

Highlight the open boundary loops#

Extracting boundary edges traces the perimeter of every hole.

boundary = open_mesh.extract_feature_edges(
    boundary_edges=True,
    feature_edges=False,
    manifold_edges=False,
    non_manifold_edges=False,
)

pl = pv.Plotter()
pl.add_mesh(open_mesh, color='wheat', smooth_shading=True, show_edges=False)
pl.add_mesh(boundary, color='tomato', line_width=6)
pl.show()
fill holes

Fill the holes#

The hole_size argument bounds the largest opening that fill_holes will close. Set it large enough to cover every loop you want to repair.

repaired = open_mesh.fill_holes(hole_size=1.0)
repaired_boundary = repaired.extract_feature_edges(
    boundary_edges=True,
    feature_edges=False,
    manifold_edges=False,
    non_manifold_edges=False,
)

pl = pv.Plotter(shape=(1, 2))
pl.subplot(0, 0)
pl.add_mesh(open_mesh, color='wheat', smooth_shading=True)
pl.add_mesh(boundary, color='tomato', line_width=6)
pl.add_text('Open', font_size=14)
pl.subplot(0, 1)
pl.add_mesh(repaired, color='wheat', smooth_shading=True, show_edges=True)
if repaired_boundary.n_cells:
    pl.add_mesh(repaired_boundary, color='tomato', line_width=6)
pl.add_text('Filled', font_size=14)
pl.link_views()
pl.show()
fill holes

Confirm the repair#

The filled surface has zero open edges, while the original had one loop per hole.

(55, 0)

Tags: filter

Total running time of the script: (0 minutes 0.557 seconds)

Gallery generated by Sphinx-Gallery