| name | meshy-auto-rig |
| description | Auto-rig 3D character models using the Meshy AI API. Converts static GLB/FBX models into rigged characters with walk and run animations. Use when you need to prepare 3D models for animation, add skeletal rigging, or generate walk/run cycles automatically. |
Meshy Auto-Rigging Skill
Auto-rig 3D character models using the Meshy AI API. Converts static GLB/FBX models into rigged characters with walk and run animations.
When to Use This Skill
- Converting static 3D models to animated characters
- Adding skeletal rigging to humanoid models
- Generating walk/run animation cycles
- Preparing models for Three.js/React Three Fiber animation
- Batch processing multiple character models
Prerequisites
- Meshy API Key: Get one at https://www.meshy.ai/api (free tier available)
- Input model: GLB or FBX format, ideally a humanoid character in T-pose
Environment Setup
Set your API key before running:
export MESHY_API_KEY="your_key_here"
API Reference
Base URL
https://api.meshy.ai/openapi/v1
Create Rigging Task
Endpoint: POST /rigging
const response = await fetch("https://api.meshy.ai/openapi/v1/rigging", {
method: "POST",
headers: {
Authorization: `Bearer ${MESHY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model_url: modelUrl,
height_meters: 1.7,
}),
});
const { result: taskId } = await response.json();
Parameters:
| Parameter | Type | Required | Description |
|---|
model_url | string | Yes | URL to model or base64 data URI |
height_meters | number | No | Target height in meters (normalizes scale) |
Poll for Completion
Endpoint: GET /rigging/{taskId}
const status = await fetch(`https://api.meshy.ai/openapi/v1/rigging/${taskId}`, {
headers: { Authorization: `Bearer ${MESHY_API_KEY}` },
}).then(r => r.json());
Response Fields:
| Field | Type | Description |
|---|
status | string | PENDING, IN_PROGRESS, SUCCEEDED, FAILED, EXPIRED |
progress | number | 0-100 completion percentage |
result | object | Contains output URLs on success |
Result Structure
On success, status.result contains:
{
rigged_character_glb_url: string,
rigged_character_fbx_url: string,
basic_animations: {
walking_glb_url: string,
walking_fbx_url: string,
running_glb_url: string,
running_fbx_url: string,
}
}
Local File Handling
For local files, convert to base64 data URI:
import * as fs from "fs";
const fileBuffer = fs.readFileSync(inputPath);
const base64Data = fileBuffer.toString("base64");
const dataUri = `data:model/gltf-binary;base64,${base64Data}`;
Complete Example
const MESHY_API_KEY = process.env.MESHY_API_KEY;
async function rigModel(inputPath: string, outputDir: string) {
const fileBuffer = fs.readFileSync(inputPath);
const base64Data = fileBuffer.toString("base64");
const dataUri = `data:model/gltf-binary;base64,${base64Data}`;
const response = await fetch("https://api.meshy.ai/openapi/v1/rigging", {
method: "POST",
headers: {
Authorization: `Bearer ${MESHY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model_url: dataUri,
height_meters: 1.7,
}),
});
const { result: taskId } = await response.json();
console.log(`Task created: ${taskId}`);
let status;
while (true) {
await new Promise(r => setTimeout(r, 5000));
const statusRes = await fetch(
`https://api.meshy.ai/openapi/v1/rigging/${taskId}`,
{ headers: { Authorization: `Bearer ${MESHY_API_KEY}` } }
);
status = await statusRes.json();
console.log(`Status: ${status.status} (${status.progress}%)`);
if (status.status === "SUCCEEDED") break;
if (status.status === "FAILED" || status.status === "EXPIRED") {
throw new Error(status.task_error?.message || "Task failed");
}
}
fs.mkdirSync(outputDir, { recursive: true });
const riggedRes = await fetch(status.result.rigged_character_glb_url);
fs.writeFileSync(
`${outputDir}/model_rigged.glb`,
Buffer.from(await riggedRes.arrayBuffer())
);
const walkRes = await fetch(status.result.basic_animations.walking_glb_url);
fs.writeFileSync(
`${outputDir}/model_walk.glb`,
Buffer.from(await walkRes.arrayBuffer())
);
const runRes = await fetch(status.result.basic_animations.running_glb_url);
fs.writeFileSync(
`${outputDir}/model_run.glb`,
Buffer.from(await runRes.arrayBuffer())
);
console.log(`Downloaded to: ${outputDir}`);
}
Batch Processing Script
A full batch processing script is available at:
packages/claude-workflow/src/lib/dashboard/frontend/scripts/meshy-auto-rig.ts
Run it:
cd packages/claude-workflow/src/lib/dashboard/frontend
MESHY_API_KEY=your_key npx tsx scripts/meshy-auto-rig.ts
Output Structure
Rigged models are saved to:
public/models/medieval/rigged/
├── {character}/
│ ├── {character}_rigged.glb # Rigged character with skeleton
│ ├── {character}_walk.glb # Walk animation cycle
│ └── {character}_run.glb # Run animation cycle
Configuration
Update rigged-models.json after processing:
{
"characters": {
"character_name": {
"rigged": "rigged/character_name/character_name_rigged.glb",
"walk": "rigged/character_name/character_name_walk.glb",
"run": "rigged/character_name/character_name_run.glb",
"scale": 1.0,
"description": "Character description"
}
},
"agentTypeMapping": {
"frontend-engineer": "wizard",
"backend-engineer": "blacksmith",
"default": "yakub"
}
}
Using Rigged Models in React Three Fiber
import { useGLTF, useAnimations } from "@react-three/drei";
import { useRef, useEffect } from "react";
function AnimatedCharacter({ animationType = "walk" }) {
const group = useRef();
const { scene } = useGLTF("/models/medieval/rigged/knight/knight_rigged.glb");
const { animations } = useGLTF(`/models/medieval/rigged/knight/knight_${animationType}.glb`);
const { actions } = useAnimations(animations, group);
useEffect(() => {
if (actions && animations[0]) {
actions[animations[0].name]?.play();
}
}, [actions, animations]);
return (
<group ref={group}>
<primitive object={scene} />
</group>
);
}
Important Notes
- Processing time: ~10-30 seconds per model
- Height normalization: Always specify
height_meters for consistent scaling
- T-pose recommended: Models in T-pose rig better
- Rate limits: Process models sequentially to avoid rate limiting
- URL expiration: Download URLs expire after ~3 days
- File size: Base64 encoding increases size by ~33%, consider hosting for large models
Pricing
As of 2025:
Troubleshooting
| Issue | Solution |
|---|
| Task fails immediately | Model may not be a valid humanoid, try T-pose |
| URLs return 403 | URLs expired, re-run the rigging task |
| Model too large | Host model at URL instead of base64 encoding |
| Animations look wrong | Model may have unusual proportions, adjust height_meters |
Integration with Dashboard Visualization
The claude-workflow dashboard uses rigged models for the medieval village visualization. Agent characters are mapped to different character types via agentTypeMapping in rigged-models.json:
{
"agentTypeMapping": {
"frontend-engineer": "wizard",
"backend-engineer": "blacksmith",
"devops-engineer": "guard",
"qa-engineer": "knight",
"research": "monk",
"cto-architect": "merchant",
"debugger": "bard",
"default": "yakub"
}
}