Skip to content

feat(mesh-ops): diagnostic-driven repair_mesh composite op (T-A4) - #378

Merged
lightningpixel merged 2 commits into
devfrom
feat/298-repair-mesh
Oct 19, 2026
Merged

lightningpixel merged 2 commits into
devfrom
feat/298-repair-mesh

Conversation

@lightningpixel

Copy link
Copy Markdown
Owner

Closes #298

Summary

Adds repair_mesh, a composite op driven by the inspect_mesh diagnostic (T-A2): "repair this mesh" now turns a holed, non-manifold mesh into a watertight, manifold one — checked by re-running inspect_mesh on the output, not just by the repair's own bookkeeping.

The existing repair op could not meet that bar: even with the params suggest_fixes chose, it left holes open (size limit), introduced inconsistent winding, and had no way to fix flipped or inside-out faces.

This PR also fixes a dependency bug that already affects inspect_mesh on dev — see below.

How repair_mesh works

  1. Diagnose the mesh (position-merged, so UV seams are not mistaken for holes).
  2. Run only the steps the diagnostic calls for: remove degenerate faces, fix non-manifold edges, fill holes (no size limit), re-orient faces.
  3. Re-diagnose; repeat up to 3 passes, stopping early once the mesh is closed and manifold or a pass makes no progress.
  4. Flip the mesh if it ends up inside-out.

Each pass is judged on position-merged geometry, exactly as inspect_mesh sees it — an earlier version judged pymeshlab's raw output and reported success on meshes that still had non-manifold edges once re-inspected.

  • A sound mesh is left alone: no file is written.
  • fill_holes: false (for meshes meant to stay open) only runs face-preserving fixes, since removing faces would open holes it can't close.
  • The result reports before/after topology, the remaining issues, and textures_dropped — the output is geometry only.

Other changes

  • inspect_mesh now flags inside-out meshes (is_inside_out, counted against printable).
  • suggest_fixes proposes repair_mesh, so inconsistent winding becomes fixable.
  • The generic POST /optimize/op/{id} route writes its output next to the input under a unique name ({stem}_{op}_{id8}.glb), like the dedicated edit routes, instead of Workflows/.
  • Agent: repair_mesh tool (reports what remains and warns when textures were dropped). MCP: modly_repair_mesh.
  • The legacy repair op is unchanged — the mesh-repair workflow node depends on it.

Dependency fix (affects dev today)

The shipped venv installs only api/requirements.txt (50 packages, verified with a pip dry-run), which does not include scipy — trimesh treats it as optional. inspect_mesh and suggest_fixes (T-A2) need it for connected parts, hole loops and vertex merging, so they fail in the installed app; tests passed only because dev venvs get scipy through torch.

  • scipy>=1.10 is now declared in api/requirements.txt (the requirements hash change re-runs pip once on update).
  • repair_mesh no longer uses trimesh.repair.fix_winding (needs networkx, also undeclared); it re-orients with pymeshlab.
  • New guard test: runs every mesh-op family with networkx unimportable, including the re-orientation path, and checks scipy stays declared. Verified to fail on the networkx-based version.

The pre-push hook caught the networkx dependency: it failed on the system Python, which has no networkx.

Testing

  • npm test — 275 Python tests (4 skipped), 262 Node tests, all passing; also run on a Python without networkx.
  • 11 new repair_mesh tests: holed + non-manifold → watertight & manifold, large hole, flipped faces, inside-out, sound mesh untouched, fill_holes: false never removes faces, textured input, input untouched, no-triangle file rejected.
  • Khronos samples: Avocado → watertight; DamagedHelmet 83 holes / 7 non-manifold edges → 1 / 1, reported as remaining.
  • ~9 s per pass on a 1.2M-face holed mesh.

Known limits

  • Textures and UVs are dropped (as with the legacy repair); keeping them is a follow-up.
  • Self-intersections and disconnected parts are not handled.
  • Not yet run on real TripoSR / Hunyuan outputs.

repair_mesh diagnoses the mesh, runs only the steps the diagnostic calls
for (degenerate faces, non-manifold edges, hole filling without a size
limit, face re-orientation), then re-checks, for up to three passes or
until a pass makes no progress. A mesh left inside-out is flipped at the
end. Each pass is judged on position-merged geometry, exactly as
inspect_mesh sees it, so UV seams are not mistaken for holes and a
reported success holds once the file is re-inspected.

A sound mesh is left alone and no file is written. With fill_holes off,
only face-preserving fixes run, since removing faces would open holes.
The result reports the before/after topology, the remaining issues, and
whether textures were dropped (the output is geometry only).

inspect_mesh now flags inside-out meshes, and suggest_fixes proposes
repair_mesh, which also makes inconsistent winding fixable. The legacy
repair op is unchanged for the mesh-repair workflow node.

The generic /optimize/op route now writes its output next to the input
under a unique name, like the dedicated edit routes.

The agent and MCP server get a repair_mesh tool.

Closes #298
The shipped venv installs only api/requirements.txt, which never pulls
scipy: trimesh treats it as optional. inspect_mesh, suggest_fixes and
repair_mesh need it for connected parts, hole loops and vertex merging,
so they failed outside a dev environment, where torch brings it in.

repair_mesh re-oriented faces with trimesh.repair.fix_winding, which
needs networkx, also undeclared; it now uses pymeshlab, and skips the
step for a pass while the mesh is still non-manifold.

A guard test runs every mesh-op family with networkx unimportable,
including the re-orientation path, and checks scipy stays declared.
@lightningpixel lightningpixel added this to the Release 0.5 milestone Oct 19, 2026
@lightningpixel lightningpixel added the feature New feature label Oct 19, 2026
@lightningpixel lightningpixel self-assigned this Oct 19, 2026
@lightningpixel
lightningpixel merged commit f29b445 into dev Oct 19, 2026
5 checks passed
@lightningpixel
lightningpixel deleted the feat/298-repair-mesh branch October 19, 2026 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant