# gltf-transform recipes Headless GLB surgery with gltf-transform (donmccurdy, gltf-transform.dev) — inspect, merge, transplant, and shrink animation packs with no Blender required. Same Quaternius skeleton across all game GLBs, so node names line up 1:1 and clip moves are mechanical. ## Install / run CLI (no global install — `npx` pulls it on demand): ```bash npx @gltf-transform/cli --help npx @gltf-transform/cli inspect assets/quaternius/kevin/kevin_male_social.glb ``` Or as a JS SDK from a node script (mirror the layout of the existing `tools/bake_run_punch.mjs`): ```js import { NodeIO } from "@gltf-transform/core"; import { prune, dedup, resample } from "@gltf-transform/functions"; const io = new NodeIO(); const doc = io.read("path/to/file.glb"); ``` Install once into the repo's node tooling: `npm i -D @gltf-transform/core @gltf-transform/functions` (the orchestrator installs — you author the script only). ## Inspect clips ```bash # Full report: meshes, animations, channels, duration. npx @gltf-transform/cli inspect assets/quaternius/kevin/kevin_male_social.glb ``` For a quick clip-name list without the SDK, the pure-node JSON-chunk one-liner already in `SKILL.md` (Path 1) reads the GLB's JSON chunk and prints `animations[].name` — use it to confirm a clip exists before importing/transplanting. ## Clip transplant between same-skeleton GLBs Both files share the Quaternius skeleton, so every channel's target node matches by name. Sketch: read source + target, copy the source `Animation` into the target document, then re-point each channel's `targetNode` to the target-doc node with the same name. ```js // verify against gltf-transform v4 API before first use import { NodeIO } from "@gltf-transform/core"; import { copyToDocument } from "@gltf-transform/functions"; const io = new NodeIO(); const target = io.read("assets/quaternius/mixamo/northern_soul.glb"); // pack to extend const source = io.read("downloads/clip_to_add.glb"); // same skeleton const srcAnim = source.getRoot().listAnimations() .find(a => a.getName() === "NewClip"); const targetNodes = new Map(target.getRoot().listNodes().map(n => [n.getName(), n])); copyToDocument(target, source, [srcAnim]); // copy the Animation property const dstAnim = target.getRoot().listAnimations().at(-1); for (const ch of dstAnim.listChannels()) { const srcNode = ch.getTargetNode(); if (srcNode) ch.setTargetNode(targetNodes.get(srcNode.getName())); // re-point by name } io.write("assets/quaternius/mixamo/northern_soul.glb", target); // overwrites — back up first ``` Common variations: copy **all** source animations (iterate `listAnimations()`), rename on the way in (`dstAnim.setName("Wave01")`), or strip a clip (remove its channels then `prune`). If `copyToDocument` isn't the right entry point in your installed version, fall back to walking `srcAnim.listChannels()` and rebuilding each (sampler interpolation + input/output accessors + targetNode/path) on a fresh `target.createAnimation()`. ## Shrink / clean Standard transforms, applied in this order for pack size: ```bash npx @gltf-transform/cli optimize # resample + prune + dedup combined # or individually: npx @gltf-transform/cli resample # drop redundant keyframes npx @gltf-transform/cli prune # remove unreferenced data npx @gltf-transform/cli dedup # merge identical accessors/meshes ``` In the SDK: `await doc.transform(prune(), dedup(), resample())`. `resample` takes an optional `tolerance`/`ratio` to trade fidelity for size — retargeted clips have clean curves and tolerate aggressive resampling; mocap-sourced clips do not. ## When to prefer this over Blender Prefer gltf-transform when there's **no new motion** to author: - Same-skeleton clip moves (one clip from a downloaded GLB into a shipped pack). - Batch renames (fix a pack of `*_Loop` suffixes, normalize clip names). - Pack merges / splits (combine two `assets/quaternius/mixamo/` packs). - Headless size passes before shipping (resample/prune/dedup). Reach for **Blender (Path 2 retarget / Path 5 authoring)** when the source uses a *different* skeleton (needs the world-rotation-delta retarget), or when you must author, IK-solve, or layer new motion that exists nowhere.