fem.tetmesh.sdf_to_tet_mesh
fem.tetmesh.sdf_to_tet_mesh(
sdf,
grid,
*,
sharp=True,
min_ratio=1.5,
min_dihedral=10.0,
max_refinements=2,
)Extract the DC surface of sdf on grid and tet-mesh its inside.
The DC vertices are Newton-projected onto the zero set before tetrahedralization (QEF vertices sit near but not exactly on the surface), so the meshed boundary is the projected surface and :func:~cadjoint.fem.motion.recompute_tet_points — which runs the identical projection from the frozen raw DC positions — reproduces mesh.points exactly at the nominal design. Without this, re-projection at solve time would move the boundary of an already-meshed volume and collapse sliver tets.
Automatic refinement. Tets need a finer grid than hexes on thin features: the hex mesher only has to decide in/out per cell, while TetGen needs the DC surface to be a valid PLC, and a wall thinner than about two cells makes dual contouring fold that surface over itself. So this function walks a ladder of grids over the same box — the declared resolution, then x1.5 and x2.25 (rounded up per axis, see :data:_REFINEMENT_FACTORS) — and at each rung tries exact sharp-feature placement first and the more robust Tikhonov placement second, exactly as a caller would by hand. Before each TetGen call the projected surface goes through the sampled self-intersection diagnostic, so a rung it catches is recorded as folded, with a count, rather than as an opaque TetGen string (it is a sampled check, and cheap, but it is not a speed-up – see :data:_DIAGNOSTIC_PAIRS_PER_TRIANGLE). The first rung that TetGen accepts wins.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sdf | Callable[[Any], Any] | Signed distance field callable on (..., 3) points. |
required |
| grid | GridSpec | DC sampling lattice (must fully contain the surface); its box is held fixed while the ladder re-dices it. | required |
| sharp | bool | Try exact sharp-feature vertex placement at each rung (False uses only the Tikhonov QEF placement, which is more robust against self-intersections at coarse grids). |
True |
| min_ratio | float | TetGen radius-edge quality bound. | 1.5 |
| min_dihedral | float | TetGen minimum dihedral angle bound in degrees. | 10.0 |
| max_refinements | int | How many refinement rungs to try after the declared resolution (0 restores the pre-refinement behaviour of failing at the declared grid). | 2 |
Returns
| Name | Type | Description |
|---|---|---|
| The | TetMesh | class:TetMesh; its first num_surface points are the |
| TetMesh | projected DC surface vertices, its base_points hold the raw |
|
| TetMesh | DC positions the projection restarts from, its grid is the |
|
| TetMesh | rung that succeeded, and its | |
| TetMesh | attr:~TetMesh.refinement records the ladder:: {“declared”: (26, 26, 13), # the resolution asked for “used”: (39, 39, 20), # the resolution that worked “factor”: 1.5, # scale of the winning rung “refined”: True, # used != declared “attempts”: [ # every rung/placement tried {“resolution”: (26, 26, 13), “factor”: 1.0, “sharp”: True, “self_intersections”: 3, “pairs_tested”: 146432, “outcome”: “self-intersecting”}, … {“resolution”: (39, 39, 20), “factor”: 1.5, “sharp”: True, “self_intersections”: 0, “pairs_tested”: 331264, “outcome”: “meshed”}]} |
|
| TetMesh | outcome is "meshed", "self-intersecting" (the |
|
| TetMesh | diagnostic fired, TetGen was not run) or "rejected" (TetGen |
|
| TetMesh | ran and refused; the attempt also carries "error"). |
Raises
| Name | Type | Description |
|---|---|---|
| RuntimeError | If no rung of the ladder produces a mesh. The message names the declared and the finest attempted resolution and the thinnest-feature heuristic. |