Docs / Cookbooks

The Tripo → meshcheck loop

Generate with Tripo, validate with meshcheck, and gate on geometry integrity before an asset enters your pipeline.

Same closed loop as the Meshy cookbook, tuned for a pipeline that cares first about geometry integrity — holes, flipped winding, and floating debris that break downstream baking and physics. The generator changes; the gate does not.

The report blocks below are real meshcheck output. The generator step stands in for your Tripo call — swap in your own GLB and the meshcheck calls are identical. Each report is reproducible against the named corpus fixture with meshcheck-corpus check <file> --profile pc.

The loop

  1. Generate. Ask Tripo for a mesh and download the GLB.
  2. Validate against the target profile. For a PC title, validate_model({ path, profile: "pc" }).
  3. Gate on geometry, not just the rollup. Treat GEO-002 (holes) and GEO-003 (flipped winding) as hard stops for your pipeline even when the profile only warns on them — a watertight, correctly-wound mesh is a precondition for baking and physics.
  4. Retry or repair. Regenerate with different parameters, or route the asset to a manual cleanup queue with the exact failing check IDs attached.
  5. Inspect (optional). Once geometry passes, ask /inspect whether the model reads as what you asked for. Non-deterministic; kept separate from the gate.

Reading geometry failures

Failing check What it means downstream Action
GEO-002 Open boundaries — normal baking and watertight physics misbehave Regenerate; escalate if it persists
GEO-003 Inconsistent winding — backface culling and lighting break Recompute normals on export
GEO-007 Floating debris — stray components inflate bounds and draw calls Strip debris, or regenerate
GEO-004 Degenerate triangles — zero-area faces confuse tangent generation Clean or regenerate

Worked example: flipped winding

Attempt one comes back with reversed face winding on part of the mesh — legal glTF that renders inside-out under backface culling. This is real output for the corpus fixture Duck__flip_faces.glb on the pc profile:

validate_model({ path: "./attempt-1.glb", profile: "pc" })
→ verdict: "fail"
  GEO-003  fail   10.018993% of faces disagree with their shell's winding (> 5%)
  UV-002   warn   worst material overlaps 94.373989% of its covered texels (> 5%)
  UV-004   warn   p95 texel stretch is 28.860683x the model median (> 2x)
  UV-005   warn   smallest island gap is 0px (< 4px minimum padding)
# reproduce
meshcheck-corpus check corpus/broken/Duck__flip_faces.glb --profile pc

GEO-003’s measured block carries the raw numbers the agent acts on — face_count: 4212, flipped_face_count: 422, flipped_pct: 10.018993 — never the prose. The agent recomputes normals on export and regenerates. Attempt two has consistent winding — real output for the clean Duck.glb:

validate_model({ path: "./attempt-2.glb", profile: "pc" })
→ verdict: "warn"
  GEO-003  pass
  UV-002   warn   worst material overlaps 94.373989% of its covered texels (> 5%)
  UV-004   warn   p95 texel stretch is 28.860695x the model median (> 2x)
  UV-005   warn   smallest island gap is 0px (< 4px minimum padding)
# reproduce
meshcheck-corpus check corpus/clean/Duck.glb --profile pc

The rollup is still warn — this is the Khronos reference Duck, and its authored UVs genuinely overlap and stretch. That is the whole reason meshcheck exists alongside the spec validator: a spec-perfect asset can still carry real UV defects. For a pipeline that gates on geometry, the load-bearing fact is GEO-003: pass; the UV warnings are recorded and accepted. Your accept/reject policy decides which measurements are hard stops — meshcheck measures and rolls up; it does not decide for you.

A hole failure reads the same way. Real output for Avocado.glb on the pc profile:

validate_model({ path: "./shell.glb", profile: "pc" })
→ verdict: "warn"
  GEO-002  warn   1 hole(s), 38 boundary edge(s), length 0.073026

If watertightness is a precondition, the agent treats this warn as a stop and regenerates, even though the profile only warns.

Add a semantic check with /inspect

After geometry passes, /inspect asks a vision model whether the mesh reads as intended — useful for catching a correctly-built model of the wrong thing:

inspect_model({
  path: "./attempt-2.glb",
  check: "VIS-001",
  prompt: "rubber duck, toy"
})

The answer is schema-constrained and flagged non-deterministic (5 credits, the only surface where the same input can produce a different answer):

{
  "inspect_id": "ins_...",
  "deterministic": false,
  "model": "claude-haiku-4-5",
  "answer": {
    "match": true,
    "confidence": "high",
    "mismatches": [],
    "notes": "..."
  }
}

Confidence is categorical (low | medium | high), never a number. Keep it out of the geometry gate: the gate is deterministic, the inspection is advisory.

Notes

  • Attach the failing check IDs to any asset you escalate — a human picking up the cleanup gets the exact defects, not a vague “bad mesh”.
  • The full report (mode: "full") includes screenshots from the six standard render angles; reuse them in /inspect via render_id instead of re-rendering.