Tier 2 · Art and 3D asset production
AI mesh cleanup
Repair, reduce and re-UV generated meshes before import.
Repair, reduce and re-UV generated meshes before import.
Load when you repair, reduce, re-UV, rebake or retexture a generated model. Generation rules (concepts, rerolls, rigging) live in the ai modelgen page; asset conventions and the FBX recipe in the blender page; how to drive Blender in the blender mcp page. Surveyed.
Fix it at the generator first (AI-MESH-FIX-UPSTREAM)
A cleanup step you do not need is the cheapest one. Every generate call pins its model version
(ai_model / model_version) and asks for:
- Meshy
POST /openapi/v1/image-to-3d: enable_pbr=trueremove_lighting=true(meshy-6 only)pose_mode="a-pose"for characters- either
should_remesh=truewithtopologyandtarget_polycount, or a laterPOST /openapi/v1/remesh(5 credits; 100–300k faces; exports glb, fbx, obj, blend and others). - Tripo
POST /v2/openapi/task,image_to_model: pbr=truesmart_low_polywithface_limit(1000–20000), orquad(500–10000)generate_partswhen the model has separable partscompressoff: the meshopt or Draco default needs an importer extension Blender may lack.- Or run the separate smart-low-poly task with
bake=true, so Tripo bakes the detail onto the low-poly mesh itself. - Fused limbs, a weapon fused to the body, and lighting baked into the albedo have no geometric repair. Regenerate (the ai modelgen page WEAPON-FUSED-REGENERATE-NEVER-CUT, GEN-REF-NO-SHADOW-BLEED) before paying for a manual fix.
- Read back the consumed credits on every call.
Measure before and after (AI-MESH-MEASURE-FIRST)
Run the numbers gate first and last:
blender -b --factory-startup --python-exit-code 1 --python tools/blender/ai_mesh_report.py -- <model> --budget <tris>
It prints one AI_MESH_REPORT {json} line with:
- triangle count
- shells, counted after joining vertices at the same position
- open and non-manifold edges, counted by position
- zero-area faces, UV layers, textures and their colour spaces, and size in metres
- a verdict: solid or island-soup, automated_repair_allowed, and which steps apply
- GLTF-SEAM-SPLIT-SHELLS: a glTF stores a separate vertex on every UV seam and hard edge, so an imported mesh looks like hundreds of loose pieces when it is one solid piece.
- Measured on three Meshy building parts: 133, 423 and 3812 pieces as stored, each 1 after joining vertices at the same position.
- Any island count, island cull or "largest component" check that runs on the as-stored mesh is measuring UV seams. Count by position (the report does) before calling a mesh island-soup.
island-soup(largest positional shell under 90% of the vertices) follows the ai modelgen page: no automated cull, weld or remesh. Regenerate, or repair by hand.solidmeshes may take the automated steps below.
The pass, in order (AI-MESH-CLEANUP-PIPELINE)
Heavy steps run headless on a copy (the blender mcp page BLENDER-HEADLESS-RECIPE). The mesh
libraries run in their own environment (uv run --project tools/blender/cleanup python ...: pymeshlab,
pymeshfix, xatlas, trimesh, all pinned), never inside Blender's Python. Hand meshes between the two
as .ply, .obj or .glb.
| # | Step | Tool | Guard |
|---|---|---|---|
| 1 | Measure | ai_mesh_report.py |
Decides which of the steps below run. |
| 2 | Weld | Merge by Distance, threshold about 1e-5 × largest dimension |
A fixed threshold collapses fingers and straps on small models. UVs are stored per face corner, so welding keeps the UV seams. |
| 3 | Normals | clear custom split normals, normals_make_consistent(inside=False), smooth by angle |
the blender page has the full normals step. |
| 4 | Holes, non-manifold, self-intersection | pymeshlab meshing_repair_non_manifold_edges / _vertices, meshing_close_holes(maxholesize=...); self-intersecting faces removed, then holes closed. Last resort: pymeshfix. |
pymeshfix and "remove connected component" keep only the largest piece by default, which deletes eyes, teeth and accessories. Cull by relative size (under 1–2% of the bounding box), never "keep largest". |
| 5 | Surface noise | pymeshlab apply_coord_taubin_smoothing (keeps volume) |
Laplacian smoothing shrinks the mesh. Any smoothing moves vertices, so plan the rebake in step 8. |
| 6 | Reduce to budget | Decimate COLLAPSE, or pymeshlab meshing_decimation_quadric_edge_collapse_with_texture |
Decimation keeps UVs. Every remesher throws them away: voxel, QuadriFlow, Instant Meshes, Quad Remesher and vendor quad remesh. A remesh commits you to steps 7 and 8. |
| 7 | UVs, only if lost | xatlas (segments, unwraps and packs in one call; texels_per_unit sets texel density) |
SLIM or Minimum Stretch needs seams, and AI meshes have none, so it returns one distorted island. Smart UV Project is the in-Blender fallback. |
| 8 | Bake high to low | Cycles Selected to Active: diffuse colour, then tangent-space normal, then AO. Extrusion 0.5–2% of the bounding box, margin 16 px at 2k. | Every material needs an active Image Texture node. The image exists only in memory until image.save. A cage too small leaves holes; too large projects from the wrong surface (armpits, between fingers). |
| 9 | Albedo lighting | Regenerate with remove_lighting, or retexture (step 10) |
No reliable headless de-lighter exists for UV atlases. Substance Sampler's delight is 2D and GUI-only. ArtEngine was discontinued in 2022. |
| 10 | Retexture | Meshy POST /openapi/v1/retexture with enable_original_uv=true and enable_pbr=true (10–15 credits, refunded on failure; recipe in the blender page) |
Tripo texture_model accepts only a Tripo task id, and whether it keeps UVs is undocumented. Test one model before relying on it. |
| 11 | Lightmap UV2 | Unity importer "Generate Lightmap UVs"; a separate xatlas channel if that fails | Never reuse a tiled UV0. No UV2 on skinned meshes. |
| 12 | Size, pivot, export | the blender page prep pass and FBX recipe, or GLB | Re-run step 1 as the exit gate, then check in Unity. |
- Live fixes in the GUI: blend-ai's
analyze_mesh_quality, its sculpt tools, and a screenshot after each change, for one local defect. the ai modelgen page LOCALIZED-REPAIR-PROJECTION covers repairing one region. - Paid tools are the developer's call:
- Quad Remesher gives the best quads, but it needs a GUI Blender and does not finish reliably under
-b. - UVPackmaster 3 (from $44) only packs; it does not unwrap.
- Neither is required by this pass.
- Skills on skills.sh (
meshy-dev/meshy-3d-agent, tripo and retopology skills) mostly wrap the generation APIs. None implements this measure-repair-rebake-export loop, so treat them as references.
BAKE-TRAPS-ON-GENERATED-MESHES
Measured while baking generated props to a budget mesh:
- A mirrored half keeps its winding: 15,049 of 30,666 faces rendered inside out until the mirror ran
reverse_faces. Gate on an inverted-face count. - Keep the generator's maps on a mirrored piece; re-baking from the mirrored source blurred edge detail (edge energy 9 vs 27). Shade mirrored donor copies flat: smoothed normals lean across a flat panel and the mirror flips the lean into a light seam.
- Cap occlusion reach at half the thinnest side: a 6 cm door baked 17% of its texels black at a reach of 5% of its 2.14 m size (8% after). Bake occlusion from the high-poly source; the budget mesh lacks the relief (median 255/255). Skip occlusion on flush sheets, where rays start under the face (37% black).
- Pin hole-fill face UVs to one point: a fill face copies neighbour UVs and stretches visible texture across the surface. Remove pinch edges (shared by three faces) before filling; 36 open edges survived otherwise.
- Cuts through a generated mesh: a per-vertex squash folded 349 faces, a boolean took 26 GB and dropped posts, EXACT left 294 islands; Blender's MANIFOLD boolean solver closed it (4 islands).
- Render review boards with ACES, not Blender's default AgX, to match HDRP, and review a lit/clay/albedo sheet: a flat Workbench board hid missing occlusion, roughness and normals.
- Held first-person items need their own texel density: 12k tris at 512 px read soft next to the source; a held pass (LOD0 40k tris at 4096 px/m) fixed it.