It's hard to say what will work for your real use case, but I cobbled together something using two mismatched cylinders as example data.
The idea is pretty much what you laid out, just perhaps done a bit more carefully:
- find the boundary edges of both meshes
- use some heuristic to identify edges that should be fused (I assume this is already solved for your use case),
- pull out cells in both meshes that contain the respective edge lines, delete these cells (along with edge points) from the original meshes
- triangulate these cells using
delaunay_3d()
- use some heuristic to discard the "capping" introduced by triangulation; looking at your screenshot what I did will probably work: check the cell normals and discard "too horizontal" cells
- merge the two truncated input meshes and the middle triangulated strip
I've left some comments in the code, e.g. the assumption that your meshes are triangulated, or if not they can be triangulated. If we can't triangulate the meshes then remove_points()
won't work, so we'd have to mess around with cell extraction to get the same result (not too difficult, just more fiddly).
The code first plots the input meshes to show the mismatch, and it plots the merged mesh at the end. delaunay_3d()
complains about mesh quality, which is probably related to the 8 larger triangles along the seam in the triangulation. Whether this is something that affects your real use case is not something I can guess.
import pyvista as pv
# plotting setup
theme = pv.themes.DocumentTheme()
theme.show_edges = True
# create dummy meshes
mesh1 = pv.Cylinder(resolution=8, center=(0, 0, 0.5), direction=(0, 0, 1), capping=False).triangulate().subdivide(3)
mesh2 = pv.Cylinder(resolution=16, center=(0, 0, -0.5), direction=(0, 0, 1), capping=False).triangulate().subdivide(3)
mesh2.rotate_z(360/32, inplace=True)
# plot them together
plotter = pv.Plotter(theme=theme)
plotter.add_mesh(mesh1, color='pink')
plotter.add_mesh(mesh2, color='cyan')
plotter.show()
edge_kwargs = dict(
manifold_edges=False,
non_manifold_edges=False,
feature_edges=False,
boundary_edges=True,
)
def tag_and_extract(mesh):
"""The work we have to do for both meshes.
This adds some scalars and extracts cells involved in the common
edge of both meshes. You'll need to tweak the filtering that selects
the appropriate feature edges below, see the "first hack" comment.
This will also modify the input mesh by deleting edge points and
the cells containing them. If you want to preserve your input
mesh, pass in a copy, and use that copy after calling this
function.
"""
# grab interesting edges
# add scalars to keep track of point and cell indices after extraction
mesh.point_data['point_inds'] = range(mesh.n_points)
mesh.cell_data['cell_inds'] = range(mesh.n_cells)
mesh_edges = mesh.extract_feature_edges(**edge_kwargs)
# first hack:
# you'll need some way to locate corresponding pairs of edges; this is specific to your problem
# in this example there's only one pair of edges so we'll clip with two planes
mesh_edges = mesh_edges.clip('z', origin=mesh1.center).clip('-z', origin=mesh2.center)
# extract original cells containing edge lines
mesh_edge_cells = mesh.extract_points(mesh_edges['point_inds'])
# delete edge points along with their containing cells
# the example data is already triangulated, otherwise we'd have to triangulate it
# (or have to do more work)
mesh.remove_points(mesh_edges['point_inds'], inplace=True)
return mesh_edge_cells
mesh1_edge_cells = tag_and_extract(mesh1)
mesh2_edge_cells = tag_and_extract(mesh2)
# triangulate the edge strip
edge_strip = (mesh1_edge_cells + mesh2_edge_cells).delaunay_3d().extract_surface()
# second hack that needs fitting to your problem: remove capping
normals = edge_strip.compute_normals().cell_data['Normals']
horizontals = abs(normals[:, -1]) < 0.9 # has lots of leeway
edge_strip = edge_strip.extract_cells(horizontals).extract_surface()
# merge meshes
merged = mesh1 + edge_strip + mesh2
# plot the result
plotter = pv.Plotter(theme=theme)
plotter.add_mesh(merged)
plotter.show()
Screenshot of the input meshes:

Merged mesh:

Hopefully there are better (and more reliable) ways to do this, but this is my best guess.