Skip to content
developerz-aiPublic

About

Free 3D asset generator for coding agents: a TypeScript DSL that compiles to game-ready glTF — LODs, named animatable parts, textures, QA gates, contact-sheet renders. No GPU, no credits.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ts23d

Free 3D asset generator for coding agents. Your agent writes TypeScript; ts23d compiles it to game-ready glTF — for Unity, Unreal, Godot, three.js, Babylon, or anything that reads glTF.

npm license

Use the LLM you already pay for to write the assets your project needs (vehicles, buildings, props, whole streets), optimised by construction. ts23d ends at the GLB, its textures and a manifest. The engine is yours.

Each asset is a TypeScript module. t3d build compiles it, deterministically, to GLB. Free: no GPU, no credits, no service. Browse the asset gallery: every recipe, compiled in your browser, with its source.

  • Named, pivoted, animatable parts. turret, gun, road_wheel_l_0: your engine code binds to node names.
  • Families. One typed build, many assets: family.variant({ name, params }) turns T-34 1941 into 1943 with one key; extend derives new families. Plain functions all the way down, no plugin API.
  • Sockets and surfaces. ctx.socket exports seats, mounts, cameras and spawn points as named nodes. A part can be opaque, cutout, glass or translucent; a GLB holds at most 4 materials over one texture set (a shampoo bottle is 2 draw calls).
  • Emitters and motion. ctx.emitter exports smoke, steam, dust, muzzle and fire sources as named nodes your engine's particles bind to; parts spin, slide or sway.
  • 3 LODs from one source. The recipe reads ctx.lod and drops detail. Nothing is decimated.
  • One shared material, batched geometry. Every asset maps into one trim sheet, and the exporter merges static geometry into its nearest pivot. Draw calls = the parts an engine moves: a static prop or building is 1; a tank with independently spinning road wheels is one per wheel plus turret, gun and hull.
  • A QA gate. Exit 1 on: a part not in surface contact with anything grounded (2 cm), a moving part that cuts through another anywhere in its motion, a blown triangle budget.
  • Code-level checks. The recipe states its physical intent and QA enforces it at every LOD: checks: [clear('track_*', 'hull_*'), noOverlap('road_wheel_l_*'), touches('turret_body', 'hull_top'), symmetric('_l', '_r', 'y')]. "The track hits the frame" becomes a failing build.
  • Textures from code. A procedural albedo / normal / ORM trim sheet (bricks, planks, plates, slate, foliage), written once and shared by every GLB, plus three.js TSL and Godot shader sources.
  • Spec-sheet fidelity. Declare real: { length, width, height, measures } with sources. QA holds the overall size to ±5% and each named dimension (driver diameter, gauge, wheelbase) to ±2%. Fix the model, never the spec.
  • A contact sheet. One PNG, six labelled orthographic views on a metre grid, rendered on the CPU. A vision-capable agent reads it once, sees what's wrong and fixes it.

The goal: high-definition detail up close, cheap to draw everywhere. One texture set, batched static geometry, LODs, compact core-glTF buffers (u16 indices and colours), and (planned) welded, cache-optimised meshes.

No Blender for most hard-surface asset production. For vehicles, buildings, props and whole scenes, the LLM writes TypeScript that does the modelling, LODs, UVs onto the trim sheet, batching, QA and export. Blender stays useful for organic sculpting, which comes in through the planned /neural adapter or importGlb.

Generate assets with your agent

1. Install

bun add @developerz-ai/ts23d

Then give your agent the loop:

Agent Copy into your project
Claude Code cp -r node_modules/@developerz-ai/ts23d/.claude/skills/author-recipe .claude/skills/ (the loop) and .claude/commands/new-recipe.md (the /new-recipe command), both shipped in the package
Codex, others Point your AGENTS.md at node_modules/@developerz-ai/ts23d/AGENTS.md and …/docs/ (details)

2. Ask

/new-recipe Tiger I, mid-production 1943, fully stowed
/new-recipe 4-4-0 steam loco with tender, wheels and rods animated on a loop
/new-recipe 1940s city street with townhouses and gas lamps    (townhouse, lamp, street recipes: landing)

The agent looks up real dimensions, writes recipes/<name>.ts, builds until QA PASS, reads the contact sheet, and fixes the worst thing until two shots in a row find nothing (the loop).

3. Use the GLB in your engine

out/<asset>/lod0.glb (and lod1, lod2) are plain glTF 2.0, core extensions only. The hooks your engine code can use:

Hook In the GLB
Movable parts Named nodes with pivots at the hinge: turret, gun, wheel_*, door_*
Attachment points Socket nodes with extras.ts23d.socket (seat, mount, camera, …)
Particle sources Emitter nodes with extras.ts23d.emitter (smoke, steam, dust, muzzle, …)
Baked loop Clip run (after a static rest clip) when built with t3d build
Everything else out/<asset>/manifest.json: pivots, sockets, emitters, triangles, draw calls

three.js, as an example:

const gltf = await new GLTFLoader().loadAsync('out/t34/lod0.glb');
const turret = gltf.scene.getObjectByName('turret');      // rotate about its local Z
const run = THREE.AnimationClip.findByName(gltf.animations, 'run');
const emitters = [];
gltf.scene.traverse((o) => o.userData.ts23d?.emitter && emitters.push(o));

Godot: node names import unchanged, and extras are available as node metadata when that import option is on. Unity (glTFast), Unreal and Babylon read the same names. ts23d ships no engine code; per-engine notes are in glb-production.md.

What you get that a generated mesh doesn't

Each ts23d row names how it's verified. The image-to-3D column cites only what vendors' own pages state, as collected in docs/landscape.md (as of Oct 2026). Image-to-3D is the better tool for organic and sculpted shapes; for those, see the planned /neural adapter.

Property ts23d How it's verified Typical image-to-3D output (per vendor docs)
Draw calls per asset Static assets: 1. Vehicles: one per movable part (one shared material; static geometry batched into its pivot) toGlb merges static parts by default (mergeToPivots); counts measured by bun run bench One PBR material per generated model (e.g. Meshy: albedo / normal / metal / rough)
LODs 3 from the same source t3d build writes lod0/1/2, each under its own triangle budget One mesh per generation; polycount presets or a low-poly mode (Rodin, Meshy)
Named, pivoted, animatable parts Yes part-names gate; clashes gate sweeps every moving part Usually one mesh; part-based output reported for CSM (3rd-party source)
Deterministic rebuild Byte-identical scripts/recipes.ts builds every recipe twice and compares hashes, in CI Not stated
Real-world dimensions Gated: ±5% overall, ±2% per measure proportions and measures gates Not stated
Parts that float or clash Gated floating-parts (surface contact, 2 cm), clashes (1 cm) Not stated
Cost per asset or variant $0 MIT, runs locally, a variant is a new seed Credits, e.g. Meshy ~20–25 credits per generation; Rodin $1.50/credit pay-per-result
GPU needed No CPU compile and CPU contact sheet Hosted: no. Self-hosted open weights: yes (TRELLIS.2 ≥ 24 GB)
Editable by an LLM A TypeScript diff Code review, bun run verify A binary mesh; edit by regenerating or in a DCC tool
Repair / retopo step None: authored with pivots, units and budgets QA gates fail the build instead Remesh / retopology tools offered (Meshy Smart Topology, quad remesh; CSM AI retopology, 3rd-party)
Texture memory per asset Shares one trim sheet: with --sheet external every GLB references the same three PNGs out/trim_sheet_*.png written once per build Own texture set per asset, up to 4K (TRELLIS.2)

Benchmarks

To be measured: bun run bench --write fills the block below (triangles per LOD, compile + QA time, GLB size, parts, draw calls per gallery recipe) from real runs. Nothing in it is typed by hand; empty means not yet run.

Measured by bun run bench on 1.4.3 (Bun).

asset tris LOD0 / 1 / 2 GLB KiB LOD0 / 1 / 2 parts animated draw calls (1 material) compile + QA, 3 LODs QA
eiffel_tower 188016 / 21860 / 1062 16863 / 1819 / 85 28 0 1 1543 ms PASS
loco_440 23868 / 13552 / 1400 1035 / 614 / 104 80 14 17 521 ms PASS
material_swatches 10926 / 226 / 226 921 / 23 / 23 52 0 3 71 ms PASS
oak 12408 / 2536 / 180 649 / 135 / 8 5 1 2 297 ms PASS
palm 5032 / 1986 / 211 371 / 174 / 19 5 1 2 103 ms PASS
pine 6496 / 1212 / 180 322 / 64 / 8 5 1 2 95 ms PASS
t34 36452 / 9960 / 556 1761 / 415 / 35 28 16 17 360 ms PASS
t34_1941 35060 / 9460 / 500 1694 / 391 / 33 28 16 17 315 ms PASS
tiger_1 88176 / 16304 / 648 10709 / 1975 / 41 37 22 23 1827 ms PASS

Draw calls column: static assets (the Eiffel Tower) are 1; vehicles are one per movable part. Trim-sheet painting: 512² ≈ 0.22 s, 1024² ≈ 0.8 s, 2048² ≈ 2.95 s. The predecessor's reference loco (below) is about 16k triangles at LOD0.

One asset pipeline, no other libraries

Asset production needs nothing else between "describe the asset" and "a GLB your engine imports". Honest status per stage:

Stage Module Status (2026-10) Replaces
Geometry primitives, kinds Shipped: box, lathe, prism, beam, planar, sweep, loft, heightfield, facades, vehicles, Weber–Penn trees. Planned: sdf, roofs, hulls, creatures Blender modelling
Materials and textures material (generator) Shipped: procedural albedo / normal / ORM trim sheet, embedded or shared PNGs, palettes, grime. No KTX2 yet Substance / texture tools
Optimisation passes/optimize, export Shipped: u16 indices, u16 colours, batching. Planned, M2: weld, vertex-cache reorder, ACMR; opt-in web profile (quantisation + meshopt) meshoptimizer / gltfpack
LODs ctx.lod Shipped Decimators
QA qa Shipped: budget, surface-contact floating parts, motion clashes, names, proportions, spec measures. Silhouette IoU and ACMR planned Manual review
Renders and photo match render Contact sheet shipped. Photo match planned, M2 —
GLB export export Shipped: core glTF only, faces +Z, seamless loops, trim-sheet textures, 0 glTF-Validator errors (tested) —
Engine import adapters threeMaterialSource (TSL), godotShaderSource Shipped: shader sources with wind, window glow, wear. Planned, M3: import settings and collision naming per engine, as hooks, not runtime —

Every stage has zero runtime dependencies (ADR 005).

Status: early (0.x). The core loop works: write, build, QA, shot, with textures, trees and nine passing recipes. Still declared and throwing T3D_NOT_IMPLEMENTED: tiled, cell, sdf, roofs, hulls, creatures, photo match, mesh optimize, neural adapters. See the roadmap. Expect breaking changes.

A recipe

import { axle, box, defineAsset, palettes } from '@developerz-ai/ts23d';

const { paint, iron, trim } = palettes.victorianRail;

export const handcart = defineAsset({
  name: 'handcart',
  kind: 'vehicle',
  budget: { triangles: [2000, 800, 300] }, // LOD0 / LOD1 / LOD2 ceilings, enforced by QA
  build(ctx) {
    for (const [i, x] of [0.8, -0.8].entries()) {
      axle(ctx, { name: `axle_${i}`, at: [x, 0, 0.35], radius: 0.35, gauge: 1.2,
                  width: 0.08, color: iron, rps: 1 }); // wheels spin in the baked clip
    }
    ctx.part('bed', box({ size: [2.4, 1.3, 0.12], at: [0, 0, 0.62], color: trim, strip: 'wood' }));
    const posts = ctx.lod === 2 ? 2 : 6; // LOD drops detail; it never decimates
    for (let i = 0; i < posts; i++) {
      const x = -1.1 + (2.2 * i) / (posts - 1);
      ctx.part(`post_${i}`, box({ size: [0.08, 0.08, 0.5], at: [x, 0.6, 0.74], color: paint }));
    }
  },
});

Metres, Z up, +X is the front. Loops, functions and types, because a recipe is code, not data (why).

What the agent sees

t3d shot recipes/tiger_1.ts → out/tiger_1/sheet.png, from recipes/tiger_1.ts. CI publishes every recipe's sheet to the gallery under /renders/:

Tiger I contact sheet: six orthographic views on a metre grid

Header: name, bounding box in metres, grid step, LOD. Row 1: 3/4 front, front (+X), side (−Y). Row 2: top, 3/4 rear, LOD2 at distance. Layout: docs/idea/05-agent-loop.md.

The quality bar

4-4-0 locomotive: reference photo, procedural model, overlay

Not output of this library yet. This is from the in-house predecessor (a JSON recipe compiler for railroads): a 4-4-0 with tender. Top: reference photo. Middle: the procedural model. Bottom: overlay. That GLB is ~16k triangles, 17 named nodes (axles, side and main rods, crossheads, tender axles, track), one trim_sheet material with embedded albedo / normal / ORM textures, and a baked run animation. ts23d now has its own loco_440 (the Jupiter, drivers 1.626 m sourced) and the trim-sheet painter; photo match is next (M2, roadmap).

Quickstart

Needs Bun ≥ 1.4.4 (Node ≥ 22 for the compiled dist/).

bun add @developerz-ai/ts23d
bunx t3d build recipes/*.ts         # GLB × 3 LODs + sheet + manifest per asset; exit 1 on a QA finding
bunx t3d shot  recipes/*.ts         # sheet + manifest only: the fast look
bunx t3d list  recipes/*.ts --json  # the assets each module exports
Flag Effect
--out <dir> Output directory (default out)
--no-shot build without rendering the contact sheet
--sheet external|embed|none Trim-sheet textures. external (default): out/trim_sheet_{albedo,normal,orm}.png written once, every GLB references ../trim_sheet_*.png. embed: inside each GLB. none: vertex colour only
--sheet-size 512|1024|2048 Sheet resolution (default 1024)
--lods 0,1,2 Which detail levels to build (default all three). --lods 0 = hero mesh only, --lods 0,2 = near + far. QA and the manifest cover exactly what was built
--no-animations Skip the baked run clip. t3d build bakes it by default so viewers (e.g. VS Code glTF Tools) show play controls
--json Machine-readable { ok, data } / { ok: false, error: { code, message, fix } }

Output, one folder per asset (stable paths for agents and pipelines):

out/
├── index.json              every asset built in this run, with its QA verdict
└── <asset>/
    ├── lod0.glb lod1.glb lod2.glb
    ├── sheet.png           the contact sheet
    └── manifest.json       files; triangles + draw calls per LOD; pivots, emitters, sockets,
                            surfaces; real + measures with measured values; sources; QA

Library calls differ on one default: toGlb(asset) exports no animation unless { animations: true }.

Every exported asset in a module is built (defineAsset or family.variant). Every error prints a stable T3D_* code and its fix (errors).

Programmatic: no CLI, just Bun

t3d is a convenience. Everything it does is a function call: compile, QA, export, render. A plain script builds exactly what you want, and the same code runs in Node ≥ 22 (against the built dist/), in a browser, or in a Web Worker (the asset gallery compiles every recipe in a worker).

// build.ts → bun build.ts
import { compile, contactSheet, DEFAULT_SHEET, paintTrimSheet, runQa, toGlb } from '@developerz-ai/ts23d';
import { tiger_1 } from './recipes/tiger_1.ts';

const sheet = paintTrimSheet({ ...DEFAULT_SHEET, size: 512 }); // textures are optional

for (const lod of [0, 2] as const) {                // only the detail levels you need
  const asset = compile(tiger_1, { lod });
  const findings = runQa(asset);                    // gate it your way, or skip it
  if (findings.length) throw new Error(findings.map((f) => `${f.message} — fix: ${f.fix}`).join('\n'));
  await Bun.write(`out/tiger_1.lod${lod}.glb`, toGlb(asset, { sheet, animations: true }));
}
await Bun.write('out/tiger_1.png', contactSheet(compile(tiger_1, { lod: 0 }))); // for your agent's eyes

Runnable copy: examples/build-with-bun.ts — executed in CI, so this path can't rot.

Building block What it gives you
compile(asset, { lod, seed }) the compiled parts for one LOD (deterministic)
runQa(compiled) / single gates / inspect(compiled) findings with fixes; geometry queries for your own checks
toGlb(compiled, { sheet, animations, keepParts }) GLB bytes (Uint8Array) — write them anywhere
mergeToPivots(compiled) the batched part list the GLB uses (draw calls = movable parts)
contactSheet(compiled, { far, cell }) six-view PNG bytes
paintTrimSheet(recipe) + sheetPngs(sheet) the texture set (albedo / normal / ORM) as RGBA or PNG
threeMaterialSource(hints) / godotShaderSource(hints) shader source strings for your engine
manifest data build it from compiled.parts (pivots, emitters, sockets) — see src/cli/manifest.ts

Use it with any agent

The docs ship inside the package, versioned with the code, readable offline. After bun add, everything an agent needs is at node_modules/@developerz-ai/ts23d/: AGENTS.md, CLAUDE.md, llms.txt, docs/, recipes/ (worked examples), CHANGELOG.md and .claude/{skills,agents,commands}.

Point your agent at them. Add to your project's CLAUDE.md or AGENTS.md:

3D assets: @developerz-ai/ts23d. Read node_modules/@developerz-ai/ts23d/AGENTS.md first;
docs in node_modules/@developerz-ai/ts23d/docs/, example recipes in …/recipes/.

Claude Code skill (and the /new-recipe command):

mkdir -p .claude/skills .claude/commands
cp -r node_modules/@developerz-ai/ts23d/.claude/skills/author-recipe .claude/skills/
cp node_modules/@developerz-ai/ts23d/.claude/commands/new-recipe.md .claude/commands/

Works with any agent that writes TypeScript and can run a shell: Claude Code, Codex, Cursor, Aider, a local model. If it can read images, it closes the loop on its own. No skill files? Paste this:

Using @developerz-ai/ts23d, write recipes/<name>.ts: a <thing>, real dimensions in metres,
Z up, +X front. Put the sources in `sources` and the key dimensions in `real.measures`.
Block out the masses, then details. Name every part that moves (axle_*, door_*, rotor_*)
and pivot it where it hinges. Loop until done:
  1. bunx t3d build recipes/<name>.ts   (fix every finding until it prints QA PASS;
     a measures finding means rebuild the part, never edit the spec)
  2. Read out/<name>/sheet.png (build writes it).
  3. Name the single biggest error, fix it, go to 1.
Stop when two consecutive shots show nothing worth fixing. Never use Math.random; use ctx.rng.

What fits, what doesn't

As of 2026-10. Full matrix: docs/idea/07-what-fits.md.

Fit Asset categories
Strong Hard-surface props, architecture (facades, towers), vehicles (tracked, wheeled, rail), modular kits, anything with real dimensions or repeated parts
Shipped, early Vegetation: Weber–Penn trees (oak, pine, palm, birch, willow); LOD2 impostors are crude
Planned Boats (lofted hulls, M2), stylised creatures (SDF over a skeleton, M4)
Weak Realistic people and animals, sculpted organic forms, cloth. Use an image-to-3D tool, or the planned /neural adapter that runs open-weight models on your own GPU and caches the result

Not a replacement for AI mesh generators: a different approach with different strengths. Neutral comparison: docs/landscape.md.

Gallery

Every recipe in recipes/, compiled in the browser, with source and LOD switcher: https://developerz-ai.github.io/ts23d/. Recipes today: tiger_1, t34 + t34_1941 (a family), loco_440 (a 4-4-0, the Jupiter, family), eiffel_tower, oak, pine, palm, material_swatches.

Docs

Read For
docs/README.md The index
docs/idea/ Vision, DSL, IR, materials, agent loop, QA, kinds
docs/decisions/ Why: ADRs with the rejected options
docs/architecture/ Module tiers, adding a primitive or kind, error codes
docs/plans/roadmap.md M0–M6
CONTRIBUTING.md bun run setup, bun run verify

Pairs well with

kubeez.com Partner. Generate reference and concept images, decals and texture source images. The sheet painter's image-stamped cells will consume them (planned) (materials)
developerz.ai 100% AI-managed software engineering. ts23d is built and maintained by AI agents

License

MIT. Outputs are yours: no attribution, no licence conditions on generated assets.

About

Free 3D asset generator for coding agents: a TypeScript DSL that compiles to game-ready glTF — LODs, named animatable parts, textures, QA gates, contact-sheet renders. No GPU, no credits.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages