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
- Generate. Ask Tripo for a mesh and download the GLB.
- Validate against the target profile. For a PC title,
validate_model({ path, profile: "pc" }). - Gate on geometry, not just the rollup. Treat
GEO-002(holes) andGEO-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. - Retry or repair. Regenerate with different parameters, or route the asset to a manual cleanup queue with the exact failing check IDs attached.
- Inspect (optional). Once geometry passes, ask
/inspectwhether 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/inspectviarender_idinstead of re-rendering.