The raw cascade output is ~4M verts / 8M faces and is not usable as-is. cleanup.py is
ordinary mesh hygiene, kept out of the model code, and the ORDER is the whole point:
weld -> strip floaters -> decimate -> strip again -> fix normals
CORRECTION TO THE FLOATER COUNT. The health pass reported 52,855 components with
52,838 fragments under 100 faces, and I took those for stray shells. They were mostly
NOT: the decoder emits per-voxel vertices, so coincident corners are duplicated and
the same continuous surface reads as tens of thousands of islands. Welding FIRST
collapses it to a single component, and only 6,332 faces are genuinely stray. Ordering
the pass the other way round removes 250k faces of real geometry and calls it cleaning.
Two performance fixes, both because an operator runs this every job:
- Component labelling is a scipy union-find over the VERTEX graph, not
trimesh.face_adjacency. Same answer, ~240s -> ~1s on this mesh.
- fix_normals runs LAST, on the decimated mesh. It walks face adjacency, so on the
raw 7.99M-face mesh it costs minutes and the result is then thrown away by
decimation. Cleanup went ~243s -> ~20s.
Decimation is iterative. A single fast_simplification call will not reduce past
roughly 4.4% of its input whatever target_reduction (or agg) is asked for: from 7.99M
faces, targets of 200k, 50k and 20k ALL returned 351,535. Repeated smaller passes get
further because each re-evaluates quadrics on the collapsed mesh.
MEASURED, on the sample:
target 500,000 -> 499,984 faces silhouette IoU 0.965 19.3s
target 200,000 -> 214,322 faces silhouette IoU 0.823 34.8s
target 100,000 -> 214,322 faces silhouette IoU 0.823
target 20,000 -> 214,322 faces silhouette IoU 0.823
HONEST LIMITATION: ~214k is a hard floor, and reaching it costs real fidelity
(0.965 -> 0.823). The cause is the ~180,000 BOUNDARY edges the Flexible Dual Grid
produces for open surfaces - quadric decimation will not collapse those, and no
aggressiveness setting changes it. Below ~214k needs a REMESH, not a decimator.
500k is effectively lossless and is the setting to use; anything under 214k is not
currently reachable and the loop stops rather than spinning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>