@three-ws/forge
v0.1.0
Published
Text/image/sketch → textured, rig-ready 3D GLB in one call. The three.ws Forge generation SDK — free TRELLIS lane, paid high-detail tiers, and auto-rigging.
Maintainers
Readme
@three-ws/forgeis the official client for the three.ws Forge — the generation engine behind three.ws/forge. It turns a prompt, a photo, or a sketch into a watertight, textured GLB, and can auto-rig that GLB into an animation-ready humanoid. It wraps the public, auth-free/api/forgeendpoint: a free TRELLIS lane on NVIDIA NIM, paid high-detail tiers billed in USDC over x402, and bring-your-own-key geometry backends (Meshy, Tripo). It pairs with@three-ws/avatar— Forge makes the model,@three-ws/avatarrenders it.
Why
Every "text-to-3D" you find is either a closed playground with no API, or a raw model endpoint that hands you an untextured mesh and leaves rigging, polygon budgets, provider fallback, job polling, and billing to you. Forge is the whole pipeline, done once:
- One call, a real GLB.
forge('a chrome robot')resolves to a hosted, durable.glbURL plus a three.ws viewer link. - Free first. Text prompts default to the free NVIDIA NIM / TRELLIS lane — no key, no wallet, no card.
- Scales with the asset. Three quality tiers (draft → standard → high) map to real polygon budgets and PBR texturing. Pay per call in USDC only when you reach for the paid tiers.
- Rig-ready. One flag chains generation into auto-rigging, so the output drops straight into the three.ws animation runtime — idle, walk, emotes.
This is the SDK twin of the 3D Studio MCP server — the same engine, exposed as plain functions instead of MCP tools.
Install
npm install @three-ws/forgeZero runtime dependencies. Works in Node 18+ and the browser (uses fetch).
For rendering the result, add @three-ws/avatar.
Quick start
The free lane needs no key:
import { forge } from '@three-ws/forge';
const model = await forge('a chrome robot with neon trim');
console.log(model.glbUrl); // → https://cdn.three.ws/forge/….glb (durable)
console.log(model.viewerUrl); // → https://three.ws/forge?share=…A fuller run — high tier, geometry-first, then auto-rig:
import { forge, rig } from '@three-ws/forge';
const model = await forge('a stylized fox, full body, T-pose', {
tier: 'high', // draft | standard | high
path: 'geometry', // image | geometry | sketch
providerKey: process.env.MESHY_KEY, // BYOK for the geometry path
});
const rigged = await rig(model.glbUrl); // animation-ready humanoid GLB
console.log(rigged.glbUrl);From an image or a sketch:
// Photo → 3D
await forge({ images: ['https://example.com/sneaker.png'], prompt: 'a sneaker' });
// Drawing + a name → geometry (no textures)
await forge({ images: ['data:image/png;base64,…'], prompt: 'a sword', path: 'sketch' });API
forge(promptOrInput, options?) → Promise<ForgeResult>
Generate a GLB from text, image(s), or a sketch. Accepts a bare prompt string, or an input object.
Input
| Field | Type | Notes |
|---|---|---|
| prompt | string | Text description. Required for text + sketch paths. |
| images | string[] | One or more image URLs / data URIs. Switches to image→3D. |
| aspectRatio | string | Reference-image aspect for the image path, e.g. "1:1". |
Options
| Option | Type | Default | Notes |
|---|---|---|---|
| path | 'image' \| 'geometry' \| 'sketch' | 'image' | How geometry is produced — see How it works. |
| tier | 'draft' \| 'standard' \| 'high' | 'standard' | Polygon budget + texture richness. |
| backend | string | auto | Force a generation backend (nvidia, huggingface, meshy, tripo). |
| providerKey | string | — | BYOK key for the geometry path (Meshy/Tripo). |
| payWith | 'x402' \| 'credits' | 'x402' | Billing lane for paid tiers (see Pricing). |
| signal | AbortSignal | — | Cancel an in-flight generation. |
| onProgress | (job) => void | — | Called on each poll tick with the latest job state. |
Returns ForgeResult
| Field | Type | Notes |
|---|---|---|
| glbUrl | string | Durable hosted GLB URL. |
| viewerUrl | string | Shareable three.ws viewer link. |
| jobId | string \| null | null when the backend returned synchronously. |
| status | 'done' | Resolved jobs are always done; failures throw. |
| path / tier / backend | string | What actually produced the mesh. |
| etaSeconds | number | Backend ETA at submit time. |
forge() submits to POST /api/forge, then polls GET /api/forge?job=<id>
until the job is done (the free NVIDIA lane often returns synchronously, with
no polling). Failures reject with a typed ForgeError.
rig(glbUrl, options?) → Promise<ForgeResult>
Auto-rig an existing GLB into an animation-ready humanoid. Wraps
POST /api/forge?action=rig { glb_url }. Returns the same ForgeResult shape
with a rigged glbUrl.
catalog() → Promise<Catalog>
Fetch the live tier / backend / cost matrix (GET /api/forge?catalog) — the
single source of truth for prices, ETAs, which backends are configured, and
which paths each serves. Use it to render a picker before the user commits.
How it works
Two orthogonal axes describe every request — path (how geometry is produced)
and tier (how much budget to spend):
prompt / image / sketch
│
▼
┌──────────┐ image ┌───────────────────────────────┐
│ path = ├────────────▶ FLUX/Imagen → TRELLIS·Hunyuan3D │ fast default, free lane
│ ├─ geometry ─▶ Meshy / Tripo native text→mesh │ BYOK, higher detail ceiling
│ ├─ sketch ───▶ TripoSG-scribble │ drawing + name → raw geometry
└──────────┘ └───────────────┬───────────────┘
▼
textured / untextured GLB
│ (action=rig)
▼
rigged humanoid GLBimage(default) — text is painted into a reference image, then reconstructed to a mesh. Fast, and the free lane lives here.geometry— a native 3D model emits mesh geometry directly, so detail isn't capped by a single synthesized view. Bring your own Meshy/Tripo key.sketch— a drawing plus a prompt naming it drives TripoSG-scribble to raw geometry (no textures).
Backends declare which paths they serve and whether they need a key. If a selected backend isn't configured, Forge returns a clean error state — it never fabricates a model.
Pricing
The free lane is genuinely free. Paid tiers are flat per-call prices, quoted in USDC (6-decimal atomics) and settled over x402 or from a prepaid credit balance:
| Tier | Polygons (target) | PBR | Price | Lane | |---|---|---|---|---| | draft | ~12k | — | free on NVIDIA NIM (text) | TRELLIS | | standard | ~30k | — | $0.15 (free on the NIM text lane) | TRELLIS / paid | | high | ~200k | yes | $0.50 | paid |
Set payWith: 'x402' (default) to pay per call with USDC, or payWith:
'credits' to draw from a signed-in prepaid balance. Pair with
@three-ws/x402-fetch to
automate the 402 settlement. Prices are authoritative in catalog() — read
them at runtime rather than hardcoding.
Errors & edge cases
forge() and rig() reject with a typed ForgeError carrying a code:
| code | HTTP | Meaning | Recovery |
|---|---|---|---|
| needs_key | 501 | The geometry path needs a BYOK Meshy/Tripo key. | Pass providerKey. |
| backend_unavailable | 503 | The selected backend isn't configured. | Omit backend to auto-route, or pick another. |
| payment_required | 402 | Paid tier with no payment. | Provide an x402 payer or top up credits. |
| insufficient_credits | 402 | payWith: 'credits' balance too low. | Top up at /credits. |
| unauthorized | 401 | Credit lane needs a signed-in account. | Authenticate, or use payWith: 'x402'. |
| rate_limited | 429 | Too many submissions. | Honour retryAfter on the error. |
| generation_failed | — | The backend produced no usable mesh. | Retry, or change path/backend. |
Every state is designed: a missing key returns needs_key (not a crash), an
unconfigured backend returns 503 (not a fake model). Mirror that in your UI.
Examples
Agent tool (free, zero-config) — the same capability is exposed as the
forge_free MCP tool, so an agent can generate 3D with no wallet:
const { glbUrl } = await forge('a low-poly treasure chest', { tier: 'draft' });Browser → render inline with the sibling viewer:
<script type="module">
import { forge } from '@three-ws/forge';
import '@three-ws/avatar/viewer';
const { glbUrl } = await forge('a friendly robot');
const el = document.createElement('three-ws-viewer');
el.setAttribute('src', glbUrl);
document.body.append(el);
</script>Generate → rig → animate in one chain, ready for the walk companion:
const base = await forge('a cartoon astronaut, full body', { tier: 'standard' });
const rigged = await rig(base.glbUrl);
// drop rigged.glbUrl into @three-ws/walk or @three-ws/avatarRelated
@three-ws/avatar— render and animate the GLB Forge produces.@three-ws/walk— a rigged Forge model as a page companion.@three-ws/x402-fetch— auto-pay the paid tiers.@three-ws/avatar-schema— validate on-chain avatar manifests.
