init: animation pipeline hub — Mac↔PC bridge for converting and implementing animations
Skills (.claude/skills/): animation-creation, iclone-video-mocap, pose-estimation (MediaPipe models via LFS), retarget-animations (deprecated). Tools: cc/mixamo/kevin/mocap retargeters + composite baker. Plans: animation-gen-pipeline + skills-adoption. exchange/: incoming-fbx, converted-glb, reference-video (LFS for fbx/glb/mp4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# 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 <in.glb> <out.glb> # resample + prune + dedup combined
|
||||
# or individually:
|
||||
npx @gltf-transform/cli <in.glb> <out.glb> resample # drop redundant keyframes
|
||||
npx @gltf-transform/cli <in.glb> <out.glb> prune # remove unreferenced data
|
||||
npx @gltf-transform/cli <in.glb> <out.glb> 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.
|
||||
Reference in New Issue
Block a user