'Multibody Surface to Volumetric Mesh in PyVista

I have a project that involves importing surfaces meshes into PyVista and converting them to volumetric meshes using tetgen. Some of these surface meshes contain multiple bodies, being essentially split along a surface. These cannot be tetrahedralized because they are non-manifold, but I would like to find a way to repair them in a way that preserves both sections. Below is an example showing the type of mesh I am dealing with, and how one section is lost when meshfix.repair is used. (Apologies for the large data set, this problem doesn't seem to happen with simpler meshes)

Given an input mesh like this, how can I end up with a tetrahedralization of both sections?

Edit: Some more thoughts.

  • The mesh has two distinct watertight sections. So maybe there could be an algorithm to break a surface mesh into separate watertight sections. Tried DatasetFilters.Connectivity but this requires the sections to not be touching.
  • The wall elements are shared between the sections so the only non-manifold edges are around the outside of the surface. This makes it hard to select the dividing wall.
  • Perhaps we could do some sort of iterative test checking whether a point is inside a watertight section, then deleting those walls...
import pyvista as pv
import tetgen
import pymeshfix
import numpy as np

points = np.array([[ 76.84349  ,   2.718057 ,   2.718057 ],
                 [ 82.79519  ,   1.491148 ,   1.491148 ],
                 [  0.       ,   0.       ,   0.       ],
                 [ 70.97732  ,   4.304215 ,   4.304215 ],
                 [ 88.81041  ,   0.6280057,   0.6280057],
                 [ 94.3964   ,   0.1571253,   0.1571253],
                 [100.       ,   0.       ,   0.       ],
                 [ 54.10642  ,  11.15306  ,  11.15306  ],
                 [ 59.58774  ,   8.529518 ,   8.529518 ],
                 [ 65.21831  ,   6.243754 ,   6.243754 ],
                 [ 38.75704  ,  20.94748  ,  20.94748  ],
                 [ 43.6718   ,  17.37353  ,  17.37353  ],
                 [ 48.79456  ,  14.1047   ,  14.1047   ],
                 [ 25.43809  ,  33.36277  ,  33.36277  ],
                 [ 29.62332  ,  28.95689  ,  28.95689  ],
                 [ 34.06845  ,  24.81337  ,  24.81337  ],
                 [ 14.59111  ,  47.9873   ,  47.9873   ],
                 [ 17.90807  ,  42.89558  ,  42.89558  ],
                 [ 21.52819  ,  38.01472  ,  38.01472  ],
                 [  6.575722 ,  64.33623  ,  64.33623  ],
                 [  8.914448 ,  58.72746  ,  58.72746  ],
                 [ 11.58954  ,  53.2711   ,  53.2711   ],
                 [  1.657672 ,  81.86754  ,  81.86754  ],
                 [  2.940621 ,  75.92768  ,  75.92768  ],
                 [  4.581992 ,  70.07671  ,  70.07671  ],
                 [  0.       , 100.       , 100.       ],
                 [  0.1846403,  93.92597  ,  93.92597  ],
                 [  0.7378794,  87.87437  ,  87.87437  ],
                 [  0.       ,   0.       , 100.       ],
                 [ 88.81041  ,   0.       ,   0.6280057],
                 [ 76.84349  ,   0.       ,   2.718057 ],
                 [ 65.21831  ,   0.       ,   6.243754 ],
                 [ 54.10642  ,   0.       ,  11.15306  ],
                 [ 43.6718   ,   0.       ,  17.37353  ],
                 [ 34.06845  ,   0.       ,  24.81337  ],
                 [ 25.43809  ,   0.       ,  33.36277  ],
                 [ 17.90807  ,   0.       ,  42.89558  ],
                 [ 11.58954  ,   0.       ,  53.2711   ],
                 [  6.575722 ,   0.       ,  64.33623  ],
                 [  2.940621 ,   0.       ,  75.92768  ],
                 [  0.7378794,   0.       ,  87.87437  ],
                 [ 87.87437  ,   0.7378794,   0.7378794],
                 [ 93.92597  ,   0.1846403,   0.1846403],
                 [ 81.86754  ,   1.657672 ,   1.657672 ],
                 [ 75.92768  ,   2.940621 ,   2.940621 ],
                 [ 70.07671  ,   4.581992 ,   4.581992 ],
                 [ 64.33623  ,   6.575722 ,   6.575722 ],
                 [ 58.72746  ,   8.914448 ,   8.914448 ],
                 [ 53.2711   ,  11.58954  ,  11.58954  ],
                 [ 47.9873   ,  14.59111  ,  14.59111  ],
                 [ 42.89558  ,  17.90807  ,  17.90807  ],
                 [ 38.01472  ,  21.52819  ,  21.52819  ],
                 [ 33.36277  ,  25.43809  ,  25.43809  ],
                 [ 28.95689  ,  29.62332  ,  29.62332  ],
                 [ 24.81337  ,  34.06845  ,  34.06845  ],
                 [ 20.94748  ,  38.75704  ,  38.75704  ],
                 [ 17.37353  ,  43.6718   ,  43.6718   ],
                 [ 14.1047   ,  48.79456  ,  48.79456  ],
                 [ 11.15306  ,  54.10642  ,  54.10642  ],
                 [  8.529518 ,  59.58774  ,  59.58774  ],
                 [  6.243754 ,  65.21831  ,  65.21831  ],
                 [  4.304215 ,  70.97732  ,  70.97732  ],
                 [  2.718057 ,  76.84349  ,  76.84349  ],
                 [  1.491148 ,  82.79519  ,  82.79519  ],
                 [  0.6280057,  88.81041  ,  88.81041  ],
                 [  0.1571253,  94.3964   ,  94.3964   ],
                 [  0.       , 100.       ,   0.       ],
                 [100.       , 100.       ,   0.       ],
                 [ 87.87437  , 100.       ,   0.7378794],
                 [ 75.92768  , 100.       ,   2.940621 ],
                 [ 64.33623  , 100.       ,   6.575722 ],
                 [ 53.2711   , 100.       ,  11.58954  ],
                 [ 42.89558  , 100.       ,  17.90807  ],
                 [ 33.36277  , 100.       ,  25.43809  ],
                 [ 24.81337  , 100.       ,  34.06845  ],
                 [ 17.37353  , 100.       ,  43.6718   ],
                 [ 11.15306  , 100.       ,  54.10642  ],
                 [  6.243754 , 100.       ,  65.21831  ],
                 [  2.718057 , 100.       ,  76.84349  ],
                 [  0.6280057, 100.       ,  88.81041  ]], dtype=float)

faces = np.array([ 3,  0,  1,  2,  3,  0,  2,  3,  3,  1,  4,  2,  3,  2,  4,  5,  3,
        2,  5,  6,  3,  7,  8,  2,  3,  2,  8,  9,  3,  2,  9,  3,  3, 10,
       11,  2,  3,  2, 11, 12,  3,  2, 12,  7,  3, 13, 14,  2,  3,  2, 14,
       15,  3,  2, 15, 10,  3, 16, 17,  2,  3,  2, 17, 18,  3,  2, 18, 13,
        3, 19, 20,  2,  3,  2, 20, 21,  3,  2, 21, 16,  3, 22, 23,  2,  3,
        2, 23, 24,  3,  2, 24, 19,  3, 25, 26,  2,  3,  2, 26, 27,  3,  2,
       27, 22,  3, 25,  2, 28,  3,  2,  6, 29,  3, 29, 30,  2,  3,  2, 30,
       31,  3,  2, 31, 32,  3, 32, 33,  2,  3,  2, 33, 34,  3,  2, 34, 35,
        3, 35, 36,  2,  3,  2, 36, 37,  3,  2, 37, 38,  3, 38, 39,  2,  3,
        2, 39, 40,  3,  2, 40, 28,  3, 40, 27, 28,  3, 28, 27, 26,  3, 28,
       26, 25,  3, 39, 38, 19,  3, 19, 24, 39,  3, 39, 24, 23,  3, 39, 23,
       40,  3, 40, 23, 22,  3, 40, 22, 27,  3, 37, 36, 17,  3, 17, 16, 37,
        3, 37, 16, 21,  3, 37, 21, 38,  3, 38, 21, 20,  3, 38, 20, 19,  3,
       35, 34, 15,  3, 15, 14, 35,  3, 35, 14, 13,  3, 35, 13, 36,  3, 36,
       13, 18,  3, 36, 18, 17,  3, 33, 32,  7,  3,  7, 12, 33,  3, 33, 12,
       11,  3, 33, 11, 34,  3, 34, 11, 10,  3, 34, 10, 15,  3, 31, 30,  0,
        3,  0,  3, 31,  3, 31,  3,  9,  3, 31,  9, 32,  3, 32,  9,  8,  3,
       32,  8,  7,  3,  6,  5, 29,  3, 29,  5,  4,  3, 29,  4, 30,  3, 30,
        4,  1,  3, 30,  1,  0,  3, 41,  2, 42,  3, 42,  2,  6,  3, 41, 43,
        2,  3,  2, 43, 44,  3,  2, 44, 45,  3, 45, 46,  2,  3,  2, 46, 47,
        3,  2, 47, 48,  3, 48, 49,  2,  3,  2, 49, 50,  3,  2, 50, 51,  3,
       51, 52,  2,  3,  2, 52, 53,  3,  2, 53, 54,  3, 54, 55,  2,  3,  2,
       55, 56,  3,  2, 56, 57,  3, 57, 58,  2,  3,  2, 58, 59,  3,  2, 59,
       60,  3, 60, 61,  2,  3,  2, 61, 62,  3,  2, 62, 63,  3, 63, 64,  2,
        3,  2, 64, 65,  3,  2, 65, 25,  3, 25, 66,  2,  3,  6, 67, 42,  3,
       42, 67, 68,  3, 42, 68, 41,  3, 41, 68, 43,  3, 43, 68, 69,  3, 43,
       69, 44,  3, 44, 69, 45,  3, 45, 69, 70,  3, 45, 70, 46,  3, 46, 70,
       47,  3, 47, 70, 71,  3, 47, 71, 48,  3, 48, 71, 49,  3, 49, 71, 72,
        3, 49, 72, 50,  3, 73, 74, 54,  3, 54, 53, 73,  3, 73, 53, 52,  3,
       73, 52, 72,  3, 72, 52, 51,  3, 72, 51, 50,  3, 75, 76, 58,  3, 58,
       57, 75,  3, 75, 57, 56,  3, 75, 56, 74,  3, 74, 56, 55,  3, 74, 55,
       54,  3, 77, 78, 62,  3, 62, 61, 77,  3, 77, 61, 60,  3, 77, 60, 76,
        3, 76, 60, 59,  3, 76, 59, 58,  3, 25, 65, 79,  3, 79, 65, 64,  3,
       79, 64, 78,  3, 78, 64, 63,  3, 78, 63, 62,  3, 66, 25, 79,  3, 79,
       78, 66,  3, 66, 78, 77,  3, 66, 77, 76,  3, 76, 75, 66,  3, 66, 75,
       74,  3, 66, 74, 73,  3, 73, 72, 66,  3, 66, 72, 71,  3, 66, 71, 70,
        3, 70, 69, 66,  3, 66, 69, 68,  3, 66, 68, 67,  3,  2, 66,  6,  3,
        6, 66, 67], dtype=int)


mesh = pv.PolyData(points, faces)
mesh.rotate_x(90, inplace=True)
mesh.rotate_z(90, inplace=True)
tet = tetgen.TetGen(mesh)
meshfix = pymeshfix.MeshFix(tet.v, tet.f)
holes = meshfix.extract_holes()
meshfix.repair()
tet.v, tet.f = meshfix.v, meshfix.f
assert(tet.tetrahedralize())

p = pv.Plotter()
p.add_mesh(mesh, style="wireframe", color="k", label="Original Surface")
p.add_mesh(holes, color='r', label="Holes")
p.add_mesh(meshfix.mesh, label="Repaired surface")
p.add_legend()
p.show()


Solution 1:[1]

I ended up writing a library that can extract enclosed surfaces of a mesh, among a few other useful things. Documentation is available here: https://acreegan.github.io/pyvista_tools/

Below is a plot showing a surface mesh before and after identifying enclosed regions.

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Andrew