Compare commits
17 Commits
f468d148bc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 854dbb5ce4 | |||
| 28fdf940f7 | |||
| 06d950649b | |||
| 078b0b7a07 | |||
| 1219954fdc | |||
| 82266c97bb | |||
| b4d962167f | |||
| 998e306804 | |||
| 228f26e7dc | |||
| cd2e598354 | |||
| 7f4300eae6 | |||
| 9f3f5ac891 | |||
| 528bf1a905 | |||
| b68fa7b920 | |||
| c79d23549a | |||
| 19181157cc | |||
| 3b132ebe9e |
@@ -0,0 +1,11 @@
|
||||
# .agents/AGENTS.md — registry + delegation
|
||||
|
||||
Detailed layer behind root `AGENTS.md`. This repo has no repo-specific
|
||||
multi-agent registry beyond:
|
||||
|
||||
- **`loop-qc`** (`.claude/agents/loop-qc.md`) — sub-agent for the loop-QC pass
|
||||
on converted GLBs; use it instead of hand-running `tools/loop_qc.py` when
|
||||
checking a batch of clips.
|
||||
|
||||
For cross-repo agent roster, MCP services, and delegation rules, see the hub:
|
||||
`tinqs-ltd/docs/.agents/AGENTS.md`.
|
||||
@@ -0,0 +1,26 @@
|
||||
# SOUL.md — animation repo identity
|
||||
|
||||
Inherits the shared identity, values, and confidentiality rules from the hub:
|
||||
`tinqs-ltd/docs/.agents/SOUL.md`. Read that first if this is your first session
|
||||
on any Tinqs repo.
|
||||
|
||||
## What this repo is
|
||||
|
||||
The animation-pipeline hub and Mac↔PC bridge for Ariki. iClone 8 + Video Mocap
|
||||
run on Jeremy's Windows PC; Blender 5, the retarget tools, and `ariki-game` live
|
||||
on both machines. This repo carries the skills, tools, and files-in-flight that
|
||||
turn PC-side motion capture (iClone renders, video mocap) into game-ready
|
||||
Quaternius-rig GLB clips consumed by `ariki-game`.
|
||||
|
||||
## Scope
|
||||
|
||||
- **In scope:** FBX→GLB retargeting (Blender headless), loop QC and loop-fixing,
|
||||
dance/clip naming and the registry, boat/prop export, the exchange-folder
|
||||
bridge protocol, and the skills that encode iClone/mocap operator knowledge.
|
||||
- **Out of scope:** game-side rendering, ClipCatalog code, dance ceremony
|
||||
design (owned by `ariki-game`) — this repo only produces and ships the assets.
|
||||
|
||||
## Confidentiality
|
||||
|
||||
Same as the hub: this repo is private/internal. No special classified material
|
||||
lives here (Cosmos-related confidentiality does not apply to animation tooling).
|
||||
@@ -0,0 +1,7 @@
|
||||
# rules/ — always-on conventions for this repo
|
||||
|
||||
No repo-specific rules beyond what's already always-on in root `AGENTS.md`
|
||||
(git via `tinqs push`/`tinqs pull`, stage-by-explicit-path, naming law) and the
|
||||
shared hub rules at `tinqs-ltd/docs/.agents/rules/`. Add a file here when a
|
||||
convention needs to be enforced repo-wide and doesn't fit in `AGENTS.md`'s
|
||||
thin-entry budget.
|
||||
@@ -0,0 +1,18 @@
|
||||
# skills/ — playbooks
|
||||
|
||||
This repo's operator playbooks are Claude-Code-native skills and live at
|
||||
`.claude/skills/` (auto-discovered by Claude Code), not duplicated here:
|
||||
|
||||
| Skill | Covers |
|
||||
|---|---|
|
||||
| `animation` | Operator playbook — the batch workflow, naming, loop QC, gotchas |
|
||||
| `animation-creation` | Authoring new clips on the shared Quaternius skeleton |
|
||||
| `iclone-video-mocap` | iClone 8 + Video Mocap: filming, cleanup, FBX export |
|
||||
| `pose-estimation` | Video→pose-landmark extraction (MediaPipe) |
|
||||
| `retarget-animations` | Deprecated early retarget notes, kept for history |
|
||||
|
||||
An agent-level playbook (`loop-qc`) lives at `.claude/agents/loop-qc.md`. Follow
|
||||
the per-repo convention (`.agents/skills/` as the canonical location) once this
|
||||
repo's skills are ported off `.claude/`; until then this file is the pointer so
|
||||
there's a single place agents look, per the SoT rule (no second source of
|
||||
truth).
|
||||
@@ -0,0 +1,90 @@
|
||||
# Architecture — animation pipeline
|
||||
|
||||
Entry point to architecture law for this repo. Per-system detail lives in
|
||||
`.agents/wiki/architecture/`.
|
||||
|
||||
## The one-sentence shape
|
||||
|
||||
PC-side motion capture (iClone 8 + Video Mocap) → this repo (Mac↔PC bridge,
|
||||
Blender-headless retargeting, loop QC) → `ariki-game` (Quaternius-rig GLB
|
||||
consumption via ClipCatalog).
|
||||
|
||||
## Machines and where things live
|
||||
|
||||
| Machine | Owns |
|
||||
|---|---|
|
||||
| PC (Windows) | iClone 8 + Video Mocap plugin — authors motion, exports FBX |
|
||||
| Mac (and PC) | Blender 5.1.2, this repo's retarget tools, `ariki-game` checkout |
|
||||
|
||||
Files move machine-to-machine only through `exchange/` in this repo, synced
|
||||
with `tinqs push`/`tinqs pull` (Git LFS for `.fbx/.glb/.mp4/.task` — see
|
||||
`.gitattributes`). Raw `git pull` hangs on LFS in tinqs.com repos.
|
||||
|
||||
## Pipeline stages
|
||||
|
||||
1. **Capture (PC)** — iClone export, Blender preset, Range=All, Preserve Bone
|
||||
Names ON → `exchange/incoming-fbx/`.
|
||||
2. **Retarget (either machine)** — Blender-headless, source-specific retargeter
|
||||
in `tools/`, all bake onto the shared Quaternius rig and export a
|
||||
mesh-stripped GLB with one NLA track per clip:
|
||||
- `cc_retarget.py` — Reallusion CC/iClone `CC_Base_*` FBX (60fps; rejects
|
||||
2-frame `_TempMotion` exports as a guard against motion-less takes)
|
||||
- `mixamo_retarget.py` — Mixamo FBX (prefix auto-detect)
|
||||
- `kevin_retarget.py` — Kevin Iglesias Unity FBX (`B-*` rig; hardcoded
|
||||
target path, non-portable)
|
||||
- `mocap_retarget.py` — MediaPipe pose JSON → GLB (rotation solving, no
|
||||
source armature)
|
||||
→ `exchange/converted-glb/`.
|
||||
3. **Loop QC** — `tools/loop_qc.py` (pre-commit gate: pose/velocity/root-drift
|
||||
continuity across the seam, exit 0 = smooth). Fixers when it fails:
|
||||
`tools/loop_fix.py` (de-drift/trim/seam-blend → `exchange/looped-glb/`) and
|
||||
`tools/pingpong_bake.py` (palindrome bake, seam-perfect but motion reverses
|
||||
at the turn → `exchange/pingpong-glb/`, clip suffix `PP`). Every adopted
|
||||
dance ships as a base clip + `_pp` pair.
|
||||
4. **Ship** — commit + `tinqs push` the GLBs in this repo, then copy into
|
||||
`ariki-game/assets/quaternius/dancegen/` (auto-discovered by ClipCatalog as
|
||||
`<pack>/<ClipName>` — also watches `kevin/`, `mixamo/`), write one dance JSON
|
||||
per clip in `ariki-game/assets/dances/`, `game.sh import`, verify clip names
|
||||
match GLB `animations[].name` exactly, commit in ariki-game (push only when
|
||||
Jeremy asks).
|
||||
5. **Post-ingest QC (game side, out of this repo)** — `ariki-game/tools/anim_qc.py`
|
||||
re-checks world-space seam continuity + renders a seam-cross video for eye
|
||||
review. Two QC tools by design: this repo's gate is pre-commit/local-space,
|
||||
the game's is post-ingest/world-space.
|
||||
|
||||
## Naming law
|
||||
|
||||
Full detail: `.agents/wiki/dances/REGISTRY.md` (source of truth — do not
|
||||
duplicate its content elsewhere). Summary: new takes get a provisional `nd_##`
|
||||
id (never a content-derived name); a take is renamed to its ceremony code
|
||||
(snake_case of the sim's `DanceType` enum + index) only once Jeremy assigns it
|
||||
to a specific ceremony, via `tools/rename_clip.py`. Rejected/superseded takes
|
||||
are archived (`archive/`), never deleted; numbers are never reused.
|
||||
|
||||
## Canonical target rig
|
||||
|
||||
All retargeters bake onto ariki-game's
|
||||
`assets/quaternius/base-characters/.../Godot - UE/Superhero_Male_FullBody.gltf`.
|
||||
Run from this repo, `--target` must be passed explicitly (the tool's default
|
||||
path is ariki-relative and won't resolve here). The rig/pack standard itself
|
||||
(65-bone Quaternius UE-named skeleton, hero body `Ariki_Female_QuatSkin.glb`) is
|
||||
governed by `ariki-game/.humans/animation-bone-sources.html` and
|
||||
`ariki-game/.agents/wiki/quaternius-ual-packs.md` — this repo's iClone/mocap
|
||||
lane is explicitly a separate clip source (not UAL1/UAL2/Kevin/Mixamo), landing
|
||||
in `dancegen/` only.
|
||||
|
||||
## Tool provenance / mirroring
|
||||
|
||||
`tools/cc_retarget.py`, `mixamo_retarget.py`, `kevin_retarget.py`,
|
||||
`mocap_retarget.py` are **mirrors of `ariki-game/tools/`** — the game copies are
|
||||
authoritative; re-copy from ariki-game when they change (they have silently
|
||||
diverged before). `.claude/skills/` here mirror a subset of `ariki-game/.claude/skills/`
|
||||
plus `~/.claude/skills/pose-estimation` — see `README.md` Provenance section.
|
||||
|
||||
## See also
|
||||
|
||||
- `.agents/wiki/architecture/` — per-system detail (currently: this file covers
|
||||
the whole pipeline; split out if a subsystem grows its own doc).
|
||||
- `.agents/wiki/dances/REGISTRY.md` — naming/registry source of truth.
|
||||
- `.agents/wiki/iclone-bridge.md` — PC-lane routing stub.
|
||||
- `.agents/wiki/devops-reports/` — point-in-time audits and convergence reports.
|
||||
@@ -0,0 +1,12 @@
|
||||
# architecture/ — per-system detail
|
||||
|
||||
This repo's pipeline is small enough that `.agents/wiki/ARCHITECTURE.md` covers
|
||||
it end to end today; nothing has grown large enough yet to need its own
|
||||
per-system doc here. Split a topic out into this folder (and link it from
|
||||
`ARCHITECTURE.md`) when it does — likely candidates as the repo grows:
|
||||
|
||||
- Retarget-tool internals (per-source rig mapping, bone-name conventions).
|
||||
- Loop QC/fix algorithms (pose-gap, velocity-gap, root-drift math).
|
||||
- The exchange-folder state machine (`exchange/GUIDE.md` currently owns this —
|
||||
promote here if it needs architecture-level treatment beyond an operator
|
||||
guide).
|
||||
@@ -39,7 +39,7 @@ renamed once Jeremy says which ceremony it corresponds to. Rename with:
|
||||
|---|---|---|---|
|
||||
| `water_dance_01` | 1 | awaiting take | — |
|
||||
| `fire_dance_01` | 1 | awaiting take | — (the old "firedance1" was mislabeled dance1 choreography) |
|
||||
| `tide_dance_01` | 1 | awaiting take | — |
|
||||
| `tide_dance_01` | 1 | ⚠ **no enum entry** | — (no `TideDance` in the sim `DanceType` enum as of 2026-07-23 — confirm with Ozan whether the slot is real before filming a take) |
|
||||
| `fertility_dance_01` | 1 | **adopted** 2026-07-16 | aloha1 (hula) — shipped with `_pp` loop |
|
||||
| `food_dance_01` | 1 | awaiting take | — |
|
||||
| `taming_dance_01` | 1 | **adopted** 2026-07-16 | dance1 — shipped with `_pp` loop |
|
||||
@@ -49,12 +49,13 @@ renamed once Jeremy says which ceremony it corresponds to. Rename with:
|
||||
| `colony_shield_dance_01` | 2 | awaiting take | — |
|
||||
| `age_progression_dance_01` | 3 | awaiting take | — (gates Age advancement) |
|
||||
| `star_watching_dance_01` | 3 | awaiting take | — |
|
||||
| `rain_dance_01` | tech | awaiting take | — (tech-tree "Rain Maker"; not in DanceType enum yet) |
|
||||
| `rain_dance_01` | 1 | awaiting take | — (tech-tree "Rain Maker"; `RainDance` **is** in the DanceType enum, Age 1 — earlier "not in enum yet" note was wrong, corrected 2026-07-23) |
|
||||
|
||||
## Register
|
||||
|
||||
| id | status | pack(s) | clip | origin take | notes |
|
||||
|---|---|---|---|---|---|
|
||||
| nd_01 | **provisional** | `nd_01`, `nd_01_pp` | `ND01`, `ND01PP` | baraka1 (iClone, 2026-07-21) | 1800f@60; `_pp` loop passes QC (0.00 seam); awaiting ceremony assignment |
|
||||
| fertility_dance_01 | **adopted** | `fertility_dance_01`, `fertility_dance_01_pp` | `FertilityDance01`, `FertilityDance01PP` | aloha1 (iClone, 2026-07-15) | hula |
|
||||
| taming_dance_01 | **adopted** | `taming_dance_01`, `taming_dance_01_pp` | `TamingDance01`, `TamingDance01PP` | dance1 (iClone, 2026-07-15) | |
|
||||
| war_dance_01 | **adopted** | `war_dance_01`, `war_dance_01_pp` | `WarDance01`, `WarDance01PP` | hakadance1 (iClone, 2026-07-15) | haka |
|
||||
@@ -64,8 +65,7 @@ renamed once Jeremy says which ceremony it corresponds to. Rename with:
|
||||
| — | archived | `hakaOG` | `HakaOG` | hakaOG | near-identical to hakadance1 |
|
||||
| — | archived | `haka_mocap` | `HakaFull` | MediaPipe video mocap (2026-07-13) | pre-iClone experiment |
|
||||
|
||||
Next free provisional number: **nd_01** (none issued yet — the five keepers predate
|
||||
the system and keep legacy names until adopted).
|
||||
Next free provisional number: **nd_02** (nd_01 = baraka1, issued 2026-07-21).
|
||||
|
||||
## Boat actions (avatar action clips — direct names, no provisional stage)
|
||||
|
||||
@@ -82,3 +82,14 @@ Cyclic clips (`*_loop`, `*_idle`) must pass `loop_qc.py` before shipping; one-sh
|
||||
| `boat_row_loop` | `BoatRowLoop` | awaiting take | helm, stern-oar sweep ±18° |
|
||||
| `boat_fish_loop` | `BoatFishLoop` | awaiting take | Seat_Fisher; coexists with kevin Fishing01_Loop |
|
||||
| `boat_sail_idle` | `BoatSailIdle` | awaiting take | sway/line-hauling loop |
|
||||
| *(unregistered)* | `CanoeDismount`, `CanoeSitIdle` | **live in-game** since 2026-07-21 | shipped as `ariki-game/assets/quaternius/dancegen/canoedismount1.glb` (content-guessed name, predates this table) — register under a `boat_*` code at next adoption pass |
|
||||
| *(unregistered)* | `run_to_dive` | **live in-game** | Mixamo one-off at `ariki-game/assets/quaternius/mixamo/run_to_dive.glb` — outside this pipeline, listed for completeness |
|
||||
|
||||
## QC tool split (audit 2026-07-23)
|
||||
|
||||
Two QC tools exist and gate different stages — both stay:
|
||||
- **`tools/loop_qc.py` (this repo)** — the **pre-commit loop gate**: local-rotation pose gap,
|
||||
velocity gap, root drift; exit 0/1. Every `_pp` (and `*_loop`/`*_idle` boat clip) must pass
|
||||
it before adoption — this is the "must pass loop_qc" rule above.
|
||||
- **`ariki-game/tools/anim_qc.py`** — **post-ingest QC** on the game side: world-space seam
|
||||
position/velocity, mid-clip stutter scan, seam-cross video render for eye review.
|
||||
@@ -0,0 +1,87 @@
|
||||
# DevOps Report — Animation Standard Convergence (2026-07-23 → 24)
|
||||
|
||||
**Author:** Claude (Fable 5), Jeremy's session · **Repos:** `animation` + `ariki-game`
|
||||
**Trigger:** Ozan landed the animation standardization contract on ariki-game main
|
||||
(2026-07-23: `5409a82e0`, `3d7f3ef94`); Jeremy requested a full conformance assessment
|
||||
of our lane (dance pipeline, this bridge repo, test beds), then execution of the fixes.
|
||||
|
||||
---
|
||||
|
||||
## 1. What the standard says (one paragraph)
|
||||
|
||||
One runtime skeleton — the Quaternius 65-bone UE-named rig. Hero body =
|
||||
`Ariki_Female_QuatSkin.glb` (Mixamo weights on Quaternius rest orientations; the earlier
|
||||
MixamoSkin+msrig pairing is superseded). Clip policy: UAL1 Pro = locomotion, UAL2 Source =
|
||||
work (under `ariki-game/assets/quaternius/anim/`), Kevin packs gaps-only, Mixamo one-offs,
|
||||
and **our iClone/video-mocap lane is explicitly separate**: `cc_retarget.py` →
|
||||
`dancegen/` → dance JSON. Canonical docs: `ariki-game/.humans/animation-bone-sources.html`,
|
||||
wiki `player-quaternius-animator.md` + `quaternius-ual-packs.md`, skill
|
||||
`aaa-player-animation`.
|
||||
|
||||
## 2. The assessment (how it ran)
|
||||
|
||||
- 52 checkable rules extracted from the 7 standard docs.
|
||||
- 10 audit roles, model-routed: GLM-5.2 ×2 (inline-input tasks), DeepSeek v4-pro ×5
|
||||
(code/doc/bed sweeps; one run died and was completed by the orchestrator), Fable ×3
|
||||
(wiring, dance system, synthesis). Every CRITICAL/HIGH re-verified mechanically.
|
||||
- Bone-census probe over 21 GLBs (this repo's `exchange/{converted,pingpong}-glb/` +
|
||||
ariki `dancegen/` + the three body GLBs).
|
||||
- Full report: `ariki-game/.agents/audits/animation-standard-assessment-2026-07-23.md`;
|
||||
meeting page: `ariki-game/.humans/animation-standard-assessment.html`.
|
||||
|
||||
**Result:** the pipeline core was already conformant — 100% of runtime clip GLBs on the
|
||||
65-bone contract, 4/4 `_pp` loop pairs, all dance JSONs clean post-rename, 16/17 test
|
||||
beds compliant. The defects clustered in the superseded MixamoSkin/msrig mechanism and
|
||||
stale doc/tool mirrors.
|
||||
|
||||
## 3. What we shipped
|
||||
|
||||
### This repo (`b4d9621`, pushed 2026-07-23)
|
||||
| Change | Why |
|
||||
|---|---|
|
||||
| `tools/cc_retarget.py` + `mixamo_retarget.py` re-synced from ariki-game | They were stale forks (2026-07-15) silently missing `--despike`, `--fingers`, `--map ual` — PC batches would have lost QC |
|
||||
| Tool **mirroring rule** added to `exchange/GUIDE.md` | Game copies are authoritative; never edit mirrors here |
|
||||
| `tools/kevin_retarget.py` path fix (mirrored) | Was hardcoded to Ozan's home dir |
|
||||
| `exchange/INBOX/lena/Ariki_Female_QuatSkin.glb` added (LFS) + README rewrite | The drop shipped only the superseded MixamoSkin body; **import the QuatSkin one on the PC** |
|
||||
| `docs/dances/REGISTRY.md` corrections | `tide_dance_01` flagged (NO `TideDance` in the sim enum); rain note fixed (`RainDance` IS in the enum); boat table now records live `canoedismount1`/`run_to_dive`; QC split documented (loop_qc = pre-commit gate here, anim_qc = post-ingest game-side) |
|
||||
| `docs/iclone-bridge.md` created | GUIDE referenced it; it never existed |
|
||||
|
||||
Earlier same day (`998e306`): the original Lena INBOX drop (GLB + rigged/unrigged T-pose
|
||||
FBX + toned textures).
|
||||
|
||||
### ariki-game (`b70d5a11c` + `934b6d4dc`, pushed)
|
||||
- **Dance bed `LenaBodyPath` → QuatSkin** (the bed had gone Lena-only on the superseded
|
||||
MixamoSkin body whose msrig clip-reroute is dead — basename rot after the Pro rename).
|
||||
Build green.
|
||||
- Doc convergence: clip-inventory wiki corrected (23→30 wired UAL2; shield/sword set was
|
||||
mislabeled "unused"), UALPACKS "Standard removed" claim scoped, `animation-pipelines.md`
|
||||
coverage extended (+`anim-combined/ msrig/ derived-bodies/`), `pose-estimation` skill
|
||||
reference re-pointed to THIS repo.
|
||||
- Bug filed for Ozan: `.agents/bugs/2026-07-23-ninjajump-idle-missing-clip.md` —
|
||||
code requests `NinjaJump_Idle`, pack ships `NinjaJump_Idle_Loop`, resolver can't bridge.
|
||||
|
||||
## 4. Live verification (2026-07-24)
|
||||
|
||||
Screenshot-verified on a running dance bed (post-fix build): QuatSkin Lena dancers
|
||||
upright, phase-locked, `war_challenge` playing on the stock clip libraries. Engine log
|
||||
carries **zero** `MISSING clip` warnings → the NinjaJump bug is **latent** (current
|
||||
Godot import config strips `_Loop`), not active; hardening still recommended. Bug file
|
||||
updated with this evidence (`934b6d4dc`).
|
||||
|
||||
## 5. Open items (owners)
|
||||
|
||||
| Item | Owner |
|
||||
|---|---|
|
||||
| Import `INBOX/lena/Ariki_Female_QuatSkin.glb` on the PC (not the MixamoSkin one) | Jeremy |
|
||||
| Retire `msrig/` + MixamoSkin GLB outright (recommended) or re-retarget under new basenames | Ozan (negotiate) |
|
||||
| Amend the Kevin-swim keep-set rule to match the restored swim override in code | Ozan (negotiate) |
|
||||
| Add derived/import packs (Run_Punch, mixamo one-offs, canoe clips) to the BONES tables | Ozan (negotiate) |
|
||||
| NinjaJump 1-token fix + resolver `+_Loop` arm | Ozan (bug filed) |
|
||||
| Register `canoedismount1` under a `boat_*` code at next adoption pass | Jeremy (this repo) |
|
||||
|
||||
## 6. Ops notes
|
||||
|
||||
- All syncs via `tinqs pull`/`tinqs push` (LFS-safe); QuatSkin GLB verified as an LFS pointer.
|
||||
- Executor telemetry: GLM headless = 1 clean / 1 silent-empty; DeepSeek = 4 clean / 1 SIGTERM;
|
||||
Fable tracks found both deep bugs (dead msrig reroute, NinjaJump). Full disclosure table
|
||||
in the audit report.
|
||||
@@ -0,0 +1,18 @@
|
||||
# iClone bridge — PC-lane overview
|
||||
|
||||
(Stub created 2026-07-23 — this path was referenced by `exchange/GUIDE.md` but never
|
||||
written. The full operational detail lives elsewhere; this page just routes you.)
|
||||
|
||||
The Mac↔PC animation bridge: iClone 8 + Video Mocap on Jeremy's Windows machine authors
|
||||
motion; this repo carries files both directions; ariki-game consumes 65-bone Quaternius
|
||||
GLBs.
|
||||
|
||||
| Need | Go to |
|
||||
|---|---|
|
||||
| Which exchange/ folder to use, when files move/archive | `exchange/GUIDE.md` |
|
||||
| Naming (nd_## → ceremony codes), loop-pair rule, registry | `.agents/wiki/dances/REGISTRY.md` |
|
||||
| Filming rules, iClone/AccuFACE settings, mocap cleanup, FBX export | `iclone-video-mocap` skill (`.claude/skills/` here and in ariki-game) |
|
||||
| CC FBX → Quaternius GLB conversion | `tools/cc_retarget.py` (mirror of ariki-game — game copy is authoritative) |
|
||||
| Looping (`_pp`) + QC gates | `tools/pingpong_bake.py` + `tools/loop_qc.py`; game-side `anim_qc.py` (split documented in REGISTRY.md) |
|
||||
| Game-side landing zone | `ariki-game/assets/quaternius/dancegen/` + `assets/dances/*.json` |
|
||||
| The skeleton/pack standard itself | `ariki-game/.humans/animation-bone-sources.html` + `.agents/wiki/quaternius-ual-packs.md` |
|
||||
@@ -0,0 +1,56 @@
|
||||
# Master plan — animation repo
|
||||
|
||||
What's in flight and recently shipped. See `.agents/plans/` for the closed-out
|
||||
plan/results docs behind these items; see `.agents/wiki/dances/REGISTRY.md` for
|
||||
the live per-clip status table (source of truth, not duplicated here).
|
||||
|
||||
## Recently shipped (closed plans, folded into wiki)
|
||||
|
||||
- **Animation-gen pipeline** (2026-07-13) — the FBX→Blender-retarget→GLB→dance-JSON
|
||||
bridge itself; core of `.agents/wiki/ARCHITECTURE.md` today.
|
||||
- **Animation skills adoption** (2026-07-13) — `.claude/skills/animation*`,
|
||||
`iclone-video-mocap`, `pose-estimation` brought in as repo skills.
|
||||
- **Loop fix** (2026-07-16, plan + results) — `tools/loop_fix.py` (de-drift/trim/
|
||||
seam-blend) and `tools/pingpong_bake.py` shipped; every adopted dance now ships
|
||||
as a base clip + `_pp` pair, gated by `tools/loop_qc.py`.
|
||||
- **Boat prop export** (2026-07-17, plan + fixes + results) — `tools/export_boat_prop.py`
|
||||
staging pipeline for the boat action-clip set (see "Boat actions" below).
|
||||
- **Animation-standard convergence** (2026-07-23/24, `.agents/wiki/devops-reports/`) —
|
||||
full audit against ariki-game's animation-standardization contract. Result:
|
||||
pipeline core already conformant (100% of runtime clip GLBs on the 65-bone
|
||||
Quaternius rig, all `_pp` pairs present, dance JSONs clean). Fixes landed:
|
||||
registry corrections (`tide_dance_01` has no sim enum entry — flag before
|
||||
filming; `rain_dance_01` confirmed in-enum), `iclone-bridge.md` written
|
||||
(previously a dead reference), QC-tool split documented (`loop_qc.py` here =
|
||||
pre-commit gate, `ariki-game/tools/anim_qc.py` = post-ingest gate).
|
||||
- **agents.md migration** (2026-07-24, this change) — repo restructured onto the
|
||||
open agent protocol: root `AGENTS.md` (thin) + `.agents/` (SOUL, wiki, plans,
|
||||
skills-pointer). `docs/` and root `plans/`/`devops-reports/` folded into
|
||||
`.agents/wiki/`.
|
||||
|
||||
## In flight — ceremony dance slots awaiting a take
|
||||
|
||||
Per `.agents/wiki/dances/REGISTRY.md`, adopted so far: `fertility_dance_01`
|
||||
(hula), `taming_dance_01`, `war_dance_01` (haka) — each shipped with its `_pp`
|
||||
loop. One provisional take (`nd_01`, baraka1) is filmed and QC-passed but not
|
||||
yet assigned to a ceremony. Still **awaiting a take**: `water_dance_01`,
|
||||
`fire_dance_01`, `food_dance_01`, `sailing_dance_01`, `building_dance_01`,
|
||||
`colony_shield_dance_01`, `age_progression_dance_01`, `star_watching_dance_01`,
|
||||
`rain_dance_01`. `tide_dance_01` has no confirmed sim `DanceType` entry — resolve
|
||||
with Ozan before filming.
|
||||
|
||||
## In flight — boat action clips
|
||||
|
||||
Staging tooling (`tools/export_boat_prop.py`) is built and the boat prop rig
|
||||
verified; the action-clip set itself is **awaiting takes**: `boat_dive`,
|
||||
`boat_climb`, `boat_collect`, `boat_row_loop`, `boat_fish_loop`,
|
||||
`boat_sail_idle`. Two boat-adjacent clips are already live in-game
|
||||
(`CanoeDismount`/`CanoeSitIdle`, `run_to_dive`) but predate this registry and
|
||||
need a `boat_*` code assigned at the next adoption pass.
|
||||
|
||||
## Open threads
|
||||
|
||||
- `tools/kevin_retarget.py` has a hardcoded, non-portable target path — fine for
|
||||
now, flagged in `AGENTS.md` gotchas.
|
||||
- `.claude/skills/retarget-animations/` is kept only for history (deprecated,
|
||||
superseded by the per-source retargeters in `tools/`).
|
||||
@@ -33,7 +33,7 @@ This skill is the accumulated **do-it-this-way** knowledge from real runs.
|
||||
```
|
||||
Blender here is **5.1.2**. When running from this repo the `--target` is **required** — the tool's default target path is ariki-relative and won't resolve.
|
||||
|
||||
## Naming system (registry: `docs/dances/REGISTRY.md` — read it, it is the source of truth)
|
||||
## Naming system (registry: `.agents/wiki/dances/REGISTRY.md` — read it, it is the source of truth)
|
||||
Two-stage names, following the **game's item-id convention (lowercase snake_case)**.
|
||||
**New dances get a provisional id, never a content name**: at conversion, assign the
|
||||
next free `nd_##` from the registry — pack `nd_08`, clip `ND08`, JSON `nd_08.json`,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# AGENTS.md — animation repo
|
||||
|
||||
Entry point for AI agents working in `local.repo/animation` (all tools: pi,
|
||||
Claude Code, Cursor). This repo is Jeremy's animation-pipeline hub **and** the
|
||||
Mac↔PC bridge that carries iClone/mocap exports into `ariki-game`. iClone 8 +
|
||||
Video Mocap live on the PC; Blender 5, the retarget tools, and the game repo
|
||||
live on both machines.
|
||||
|
||||
Read `.agents/SOUL.md` at session start, then use the knowledge map below —
|
||||
this file stays thin; the operational detail lives in `.claude/skills/` and
|
||||
`.agents/`.
|
||||
|
||||
## Knowledge map
|
||||
|
||||
| Category | Path | When |
|
||||
|---|---|---|
|
||||
| Identity | `.agents/SOUL.md` | always-on |
|
||||
| Batch workflow, canonical retarget command, naming, loop QC, gotchas, repo etiquette | `.claude/skills/animation/SKILL.md` | always-on — read before any batch or clip work |
|
||||
| Architecture (pipeline stages, canonical rig, tool provenance) | `.agents/wiki/ARCHITECTURE.md` | consult when touching the pipeline |
|
||||
| Per-system architecture detail | `.agents/wiki/architecture/` | consult-when |
|
||||
| In-flight work + recently shipped | `.agents/wiki/master-plan.md` | check before starting new work |
|
||||
| Dance/clip naming registry (source of truth for names) | `.agents/wiki/dances/REGISTRY.md` | always-on when naming or shipping a clip |
|
||||
| PC-lane routing stub | `.agents/wiki/iclone-bridge.md` | consult-when |
|
||||
| Point-in-time audits / convergence reports | `.agents/wiki/devops-reports/` | consult-when |
|
||||
| Open/executing plans | `.agents/plans/` | consult-when |
|
||||
| Other operator playbooks (iClone/mocap, clip authoring, pose estimation) | `.claude/skills/` (index: `.agents/skills/README.md`) | consult-when |
|
||||
| Loop-QC sub-agent | `.claude/agents/loop-qc.md` | consult-when |
|
||||
| Repo-wide conventions | `.agents/rules/` | always-on |
|
||||
| Exchange-folder operator guide (which folder, when files move/archive) | `exchange/GUIDE.md` | consult-when doing a batch |
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
AGENTS.md ← you are here
|
||||
.agents/ ← agent context: SOUL, wiki (architecture/master-plan/registry/
|
||||
devops-reports), plans, rules, skills-pointer
|
||||
.claude/ ← Claude-Code-native skills + agents (auto-discovered)
|
||||
tools/ ← Blender-headless retargeters, loop tools, prop export
|
||||
exchange/ ← files in flight between Mac and PC (see exchange/GUIDE.md)
|
||||
archive/ ← retired takes — never deleted, never renumbered
|
||||
```
|
||||
|
||||
## Git
|
||||
|
||||
- Branch is **`main`**. Sync via **`tinqs push`/`tinqs pull`** (not raw git —
|
||||
this repo uses Git LFS for `.fbx/.glb/.mp4/.task`; a plain `git pull` hangs).
|
||||
Report the commit hash after pushing.
|
||||
- **Stage explicit paths, never `git add -A`** — ariki-game and this repo often
|
||||
carry unrelated in-flight files.
|
||||
- Never commit secrets.
|
||||
|
||||
## The rule that bites most often
|
||||
|
||||
**Green verify ≠ correct.** An agent-authored gate once passed an upside-down
|
||||
boat export. Always render and eyeball a new visual asset before shipping —
|
||||
full gotcha list in `.claude/skills/animation/SKILL.md`.
|
||||
@@ -41,7 +41,7 @@ into the game repo.
|
||||
- `kevin_retarget.py` — Kevin Iglesias Unity FBX (`B-*` rig; hardcoded target path — non-portable)
|
||||
- `mocap_retarget.py` — MediaPipe pose JSON → GLB (rotation solving, no source armature)
|
||||
- `bake_run_punch.mjs` — composite upper/lower clip baking reference
|
||||
- `plans/` — pipeline design docs (animation generation pipeline, skills adoption)
|
||||
- `AGENTS.md` / `.agents/` — agent context (identity, wiki, plans, rules); see `AGENTS.md` for the knowledge map
|
||||
|
||||
## Canonical target rig
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# exchange/ — Folder guide for agents
|
||||
|
||||
This folder is the Mac↔PC bridge for the animation pipeline (iClone on the PC ↔
|
||||
ariki-game on the Mac). Every file in here is **in transit** on one of the tracks
|
||||
below. If you are an agent dropping files into this repo, or processing a batch,
|
||||
this file tells you which folder to use, when a file moves to the next stage, and
|
||||
when the previous version gets archived.
|
||||
|
||||
Related docs: `.agents/wiki/dances/REGISTRY.md` (naming — the source of truth),
|
||||
`.agents/wiki/iclone-bridge.md` (PC-lane overview), and the `animation` skill (batch workflow details).
|
||||
|
||||
**Tool mirroring rule (audit 2026-07-23):** the retargeters in `tools/`
|
||||
(`cc_retarget.py`, `mixamo_retarget.py`, `kevin_retarget.py`, `mocap_retarget.py`) are
|
||||
**mirrors of `ariki-game/tools/`** — the game copies are authoritative. Never edit them
|
||||
here; re-copy from ariki-game when they change (they silently diverged once already:
|
||||
2026-07-15 → 2026-07-23, losing `--despike`/`--fingers`/`--map ual`).
|
||||
|
||||
---
|
||||
|
||||
## Folder map — EDIT PATHS HERE ONLY
|
||||
|
||||
Folder locations may change. Every rule in this guide refers to folders by the
|
||||
**KEY** below, never by a hardcoded path. If a folder moves, update this table
|
||||
(and nothing else in this file).
|
||||
|
||||
| KEY | Current path | One-liner |
|
||||
|---|---|---|
|
||||
| `INBOX` | `exchange/INBOX/` | Untriaged drop zone for anything sent to Jeremy |
|
||||
| `INCOMING_FBX` | `exchange/incoming-fbx/` | Accepted FBX takes queued for retargeting |
|
||||
| `CONVERTED_GLB` | `exchange/converted-glb/` | Retargeted game-ready GLBs (base clips) |
|
||||
| `LOOPED_GLB` | `exchange/looped-glb/` | Seam-fixed loop variants from `tools/loop_fix.py` (created on demand) |
|
||||
| `PINGPONG_GLB` | `exchange/pingpong-glb/` | Palindrome `_pp` loop variants from `tools/pingpong_bake.py` |
|
||||
| `OUTGOING_PROPS` | `exchange/outgoing-props/` | Game → iClone direction: staging props + source motions for iClone editing |
|
||||
| `REFERENCE_VIDEO` | `exchange/reference-video/` | Choreography/mocap reference footage |
|
||||
| `ICLONE_PROJECTS` | `exchange/workingfiles-iclone/` | iClone `.iProject` working-file backups |
|
||||
| `ARCHIVE` | `archive/` (repo root) | Rejected/superseded files — mirrors the exchange subfolder names |
|
||||
| `GAME_DANCEGEN` | `<ariki-game>/assets/quaternius/dancegen/` | Final GLB destination in the game repo |
|
||||
| `GAME_DANCES` | `<ariki-game>/assets/dances/` | Dance JSON destination in the game repo |
|
||||
|
||||
Notes that apply everywhere:
|
||||
- Binary formats (`.fbx`, `.glb`, `.mp4`, `.task`) go through **Git LFS**; push with `tinqs push`.
|
||||
- **Move, never delete.** Anything obsolete goes to `ARCHIVE` (see archiving rules at the bottom).
|
||||
- Stage files **by explicit path** — never `git add -A` (this repo often has unrelated in-flight work).
|
||||
|
||||
---
|
||||
|
||||
## Track 1 — INBOX triage (anyone → Jeremy)
|
||||
|
||||
**Folder:** `INBOX`
|
||||
|
||||
The only folder outside agents should ever write to. Drop whatever you're sending
|
||||
(FBX, textures, JSON, video, etc.), commit, push. Do **not** place files directly
|
||||
into the pipeline folders and do not reorganize the rest of `exchange/`.
|
||||
|
||||
| Event | Action |
|
||||
|---|---|
|
||||
| File arrives | Sits in `INBOX` untouched until Jeremy (or a session he directs) triages it |
|
||||
| Triaged as an animation take | Move to `INCOMING_FBX` → enters Track 2 |
|
||||
| Triaged as reference footage | Move to `REFERENCE_VIDEO` → Track 4 |
|
||||
| Triaged as anything else | Jeremy decides; ask, don't guess |
|
||||
| Junk / duplicate | Move to the matching `ARCHIVE` subfolder |
|
||||
|
||||
`INBOX` should trend toward **empty** — a file living there long-term means triage
|
||||
was missed.
|
||||
|
||||
## Track 2 — Animation intake (iClone/PC → game) — the main track
|
||||
|
||||
**Stages:** `INBOX` → `INCOMING_FBX` → `CONVERTED_GLB` (+ loop variant in
|
||||
`PINGPONG_GLB` or `LOOPED_GLB`) → copied to `GAME_DANCEGEN` + JSON in `GAME_DANCES`.
|
||||
|
||||
A "take" is a **file set that always travels together**: `<take>.fbx` +
|
||||
`<take>.json` + `<take>.fbm/` texture folder (when present). Move all three,
|
||||
always.
|
||||
|
||||
| Stage | What lives here | Moves forward when |
|
||||
|---|---|---|
|
||||
| `INCOMING_FBX` | Raw iClone FBX exports (1800f @ 60fps typical) awaiting retarget | Retargeted via `tools/cc_retarget.py` → GLB written to `CONVERTED_GLB` |
|
||||
| `CONVERTED_GLB` | Game-ready GLB, named per the registry (`nd_##` provisional, ceremony code once adopted) | Copied (not moved) into `GAME_DANCEGEN` with a dance JSON |
|
||||
| `PINGPONG_GLB` / `LOOPED_GLB` | The loop variant (`<pack>_pp` / seam-fixed). **Every shipped dance is a pair: base + loop.** Must pass `tools/loop_qc.py` (exit 0) | Copied into `GAME_DANCEGEN` alongside the base clip |
|
||||
|
||||
Rules on this track:
|
||||
- **Naming:** new takes get the next free `nd_##` from `.agents/wiki/dances/REGISTRY.md`
|
||||
at conversion — never a content name. Update the registry row at every
|
||||
conversion/adoption/archive.
|
||||
- **FBX stays in `INCOMING_FBX` after conversion** (it's the re-export source)
|
||||
until its take is adopted or rejected.
|
||||
- The GLB is **copied** to the game repo, not moved — `CONVERTED_GLB` keeps the
|
||||
canonical copy.
|
||||
|
||||
**Archive triggers (move old version → matching `ARCHIVE` subfolder):**
|
||||
|
||||
| Trigger | What to archive |
|
||||
|---|---|
|
||||
| A **re-export of the same take** arrives (same choreography, new FBX) | The old FBX set from `INCOMING_FBX` and its old GLBs from `CONVERTED_GLB`/`PINGPONG_GLB`/`LOOPED_GLB` |
|
||||
| Take is **rejected** | Its entire file set across all stage folders (and `archive/dances/` on the game side) |
|
||||
| Take is **adopted** (renamed `nd_##` → ceremony code via `tools/rename_clip.py`) | The old `nd_##`-named files, once the renamed set is verified in-game |
|
||||
|
||||
Never archive mid-batch — wait until Jeremy confirms the batch looks right in-game.
|
||||
|
||||
## Track 3 — Outgoing to iClone (game/Mac → PC)
|
||||
|
||||
**Folder:** `OUTGOING_PROPS`
|
||||
|
||||
The reverse direction: anything exported from the game side for use inside
|
||||
iClone. Two kinds live here today:
|
||||
- **Staging props** (e.g. `boat/` — built by `tools/export_boat_prop.py`, with
|
||||
renders for eyeball QA). Each prop gets its own subfolder with a README.
|
||||
- **Source motion FBX** (Mixamo/Kevin-Iglesias clips like `Swimming.fbx`) sent
|
||||
over as raw material for iClone editing. Note: some earlier source-motion drops
|
||||
(the `HumanM@Mining*` set) landed in `INCOMING_FBX`; going forward,
|
||||
game→PC source motions belong here.
|
||||
|
||||
| Event | Action |
|
||||
|---|---|
|
||||
| New version of a prop | Overwrite in place (git history is the archive for props) — but **render and eyeball before shipping**; a green verify gate has passed a visually-wrong prop before |
|
||||
| PC has consumed the files and the resulting take shipped | Leave in place until Jeremy says to clean up |
|
||||
|
||||
## Track 4 — Reference footage
|
||||
|
||||
**Folder:** `REFERENCE_VIDEO`
|
||||
|
||||
Choreography/mocap reference videos (inputs for iClone Video Mocap and for
|
||||
`pose-estimation` scoring). These are **inputs, not pipeline items** — they don't
|
||||
advance through stages.
|
||||
|
||||
| Event | Action |
|
||||
|---|---|
|
||||
| New reference arrives (usually via `INBOX`) | Move here |
|
||||
| The dance it references is adopted and shipped | Video becomes archivable — ask Jeremy before moving (these are large LFS objects; he may want them kept for re-scoring) |
|
||||
|
||||
## Track 5 — iClone working files
|
||||
|
||||
**Folder:** `ICLONE_PROJECTS`
|
||||
|
||||
`.iProject` backups from the PC so iClone sessions can be resumed/recovered.
|
||||
These are big (100 MB+) LFS blobs.
|
||||
|
||||
| Event | Action |
|
||||
|---|---|
|
||||
| Newer save of the same project | Overwrite in place — git history keeps priors |
|
||||
| Project's takes all adopted or rejected | Archivable on Jeremy's call only |
|
||||
|
||||
---
|
||||
|
||||
## Archiving rules (all tracks)
|
||||
|
||||
1. `ARCHIVE` **mirrors the exchange subfolder names** — a file from
|
||||
`INCOMING_FBX` goes to `ARCHIVE/incoming-fbx/`, a GLB from `CONVERTED_GLB` to
|
||||
`ARCHIVE/converted-glb/`, etc. If a mirror subfolder doesn't exist yet, create it.
|
||||
2. **`git mv`, never delete.** Archived files keep their names; nothing in
|
||||
`ARCHIVE` is ever renamed or reused.
|
||||
3. **Registry numbers are never reused.** An archived `nd_03` retires the number
|
||||
forever.
|
||||
4. Every archive action gets a **registry update** (`.agents/wiki/dances/REGISTRY.md`)
|
||||
when it involves a tracked dance.
|
||||
5. Game-side counterparts (GLBs/JSON already copied into ariki-game) archive to
|
||||
`<ariki-game>/archive/dances/` (which is `.gdignore`d), in the same commit
|
||||
cycle.
|
||||
|
||||
## Quick decision table
|
||||
|
||||
| You are… | You should… |
|
||||
|---|---|
|
||||
| An outside agent sending files | Drop in `INBOX`, commit, push. Touch nothing else. |
|
||||
| Processing a new batch | Follow Track 2 top to bottom; read the `animation` skill first |
|
||||
| Delivering game assets to the PC/iClone | Put them in `OUTGOING_PROPS` (subfolder per prop) |
|
||||
| Holding a newer version of something already here | Same-name working files: overwrite. Pipeline takes: move the old set to `ARCHIVE` first (Track 2 triggers) |
|
||||
| Unsure which track applies | Leave it in `INBOX` and say so — wrong-folder moves are worse than no move |
|
||||
@@ -0,0 +1,13 @@
|
||||
# INBOX
|
||||
|
||||
Drop zone for anyone sending files to Jeremy through this repo.
|
||||
|
||||
Put whatever you're sending in here (FBX takes, textures, reference video, JSON, etc.),
|
||||
commit, and push. Jeremy will pull, triage, and move things into the proper pipeline
|
||||
folders (`incoming-fbx/`, etc.) from there.
|
||||
|
||||
Notes:
|
||||
- Binary formats (`.fbx`, `.glb`, `.mp4`, `.task`) go through Git LFS automatically.
|
||||
- Don't reorganize other folders in `exchange/` — just drop your files here.
|
||||
- Full folder guide (what each `exchange/` folder is for, and how files move
|
||||
between them): see [`../GUIDE.md`](../GUIDE.md).
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.3 MiB |
@@ -0,0 +1,13 @@
|
||||
# Lena character (from ariki-game)
|
||||
|
||||
The new Lena character, exported from `ariki-game/assets/quaternius/derived-bodies/`
|
||||
(Ozan's standard-rig work, 2026-07-21 → standardized 2026-07-23).
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `Ariki_Female_QuatSkin.glb` | **⭐ THE canonical game body (standard 2026-07-23)** — Mixamo weights on Quaternius rest orientations, exact 65-bone UE-named contract, plays the stock clip libraries natively. Import THIS one. |
|
||||
| `Ariki_Female_MixamoSkin.glb` | **SUPERSEDED** by QuatSkin (kept for reference) — Mixamo-convention rig (no `root` bone, Mixamo leaf names); needed the now-stale msrig clip reroute. |
|
||||
| `lena_tpose_legs5_mixamo_rigged.fbx` | Mixamo-rigged T-pose FBX (legs5 variant) — the rigged source; best starting point for importing into iClone/CC or Mixamo on the PC. |
|
||||
| `lena_tpose_for_mixamo.fbx` | Unrigged T-pose mesh, the file that was uploaded to Mixamo's auto-rigger. |
|
||||
| `Lena_Body_Toned.png` / `Lena_Head_Toned.png` | Toned base-color textures (FBX files don't embed them). |
|
||||
| `Lena_Head_Detail.png` | High-res head detail texture. |
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+183
-183
@@ -21,23 +21,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
-5.458902359008789,
|
||||
1.885887622833252,
|
||||
11.434629440307618
|
||||
-5.45889139175415,
|
||||
1.8858885765075684,
|
||||
11.434627532958985
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.7455047369003296,
|
||||
0.09864884614944458,
|
||||
-0.13664770126342774,
|
||||
0.09864882379770279,
|
||||
-0.13664767146110536,
|
||||
0.6448400020599365
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0,
|
||||
1.0000001192092896,
|
||||
1.0,
|
||||
1.0000001192092896
|
||||
],
|
||||
"Radius": 8.839690208435059,
|
||||
"Capsule Length": 21.98419761657715
|
||||
"Radius": 8.83968734741211,
|
||||
"Capsule Length": 21.984195709228517
|
||||
},
|
||||
"Capsule(0)": {
|
||||
"Bone Active": true,
|
||||
@@ -47,23 +47,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
6.060687065124512,
|
||||
2.153285026550293,
|
||||
10.500402450561524
|
||||
6.060699462890625,
|
||||
2.153285503387451,
|
||||
10.500398635864258
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.7561908960342407,
|
||||
-0.02894490584731102,
|
||||
0.09083329886198044,
|
||||
-0.02894493006169796,
|
||||
0.09083331376314163,
|
||||
0.6473692059516907
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0,
|
||||
0.9999998807907105,
|
||||
1.0000001192092896,
|
||||
1.0000001192092896
|
||||
],
|
||||
"Radius": 8.839690208435059,
|
||||
"Capsule Length": 21.98419761657715
|
||||
"Radius": 8.83968734741211,
|
||||
"Capsule Length": 21.984195709228517
|
||||
}
|
||||
},
|
||||
"CC_Base_Pelvis": {},
|
||||
@@ -76,23 +76,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
21.905628204345704,
|
||||
-15.19033145904541,
|
||||
16.22507667541504
|
||||
21.905879974365236,
|
||||
-15.190494537353516,
|
||||
16.225290298461915
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.4892211854457855,
|
||||
0.36015355587005618,
|
||||
0.7936497926712036,
|
||||
-0.03274179995059967
|
||||
0.48922115564346316,
|
||||
0.36015594005584719,
|
||||
0.7936487197875977,
|
||||
-0.03274335712194443
|
||||
],
|
||||
"WorldScale": [
|
||||
0.9999998807907105,
|
||||
1.0000001192092896,
|
||||
0.9999997615814209
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"Radius": 7.760237693786621,
|
||||
"Capsule Length": 41.69866943359375
|
||||
"Radius": 7.760236740112305,
|
||||
"Capsule Length": 41.69938278198242
|
||||
}
|
||||
},
|
||||
"CC_Base_L_Calf": {
|
||||
@@ -104,23 +104,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
12.306705474853516,
|
||||
-36.45222473144531,
|
||||
17.107437133789064
|
||||
12.306894302368164,
|
||||
-36.451805114746097,
|
||||
17.107423782348634
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.30900031328201296,
|
||||
-0.4523050785064697,
|
||||
0.734568178653717,
|
||||
0.4004354178905487
|
||||
0.30899837613105776,
|
||||
-0.4523083567619324,
|
||||
0.7345657348632813,
|
||||
0.4004375636577606
|
||||
],
|
||||
"WorldScale": [
|
||||
1.000000238418579,
|
||||
0.9999998807907105,
|
||||
0.9999998211860657,
|
||||
0.9999998807907105
|
||||
],
|
||||
"Radius": 6.271899223327637,
|
||||
"Capsule Length": 35.36064910888672
|
||||
"Radius": 6.27189826965332,
|
||||
"Capsule Length": 35.36063766479492
|
||||
}
|
||||
},
|
||||
"CC_Base_L_Foot": {},
|
||||
@@ -145,23 +145,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
-18.041404724121095,
|
||||
-19.541664123535158,
|
||||
15.545527458190918
|
||||
-18.041444778442384,
|
||||
-19.5419921875,
|
||||
15.545700073242188
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
-0.5814463496208191,
|
||||
0.25630292296409609,
|
||||
0.7701704502105713,
|
||||
-0.05537568777799606
|
||||
-0.5814465880393982,
|
||||
0.2563026249408722,
|
||||
0.7701703310012817,
|
||||
-0.05537635087966919
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0000001192092896,
|
||||
1.0,
|
||||
1.0
|
||||
1.000000238418579,
|
||||
1.0000001192092896
|
||||
],
|
||||
"Radius": 7.726479530334473,
|
||||
"Capsule Length": 41.760841369628909
|
||||
"Radius": 7.726480484008789,
|
||||
"Capsule Length": 41.761573791503909
|
||||
}
|
||||
},
|
||||
"CC_Base_R_Calf": {
|
||||
@@ -173,23 +173,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
-3.432270050048828,
|
||||
-33.92041778564453,
|
||||
15.819944381713868
|
||||
-3.432130813598633,
|
||||
-33.92060852050781,
|
||||
15.820029258728028
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.2851530909538269,
|
||||
0.5644364953041077,
|
||||
-0.5839793086051941,
|
||||
0.5089865326881409
|
||||
0.2851554751396179,
|
||||
0.5644335746765137,
|
||||
-0.5839812159538269,
|
||||
0.5089861750602722
|
||||
],
|
||||
"WorldScale": [
|
||||
0.9999999403953552,
|
||||
1.0000003576278687,
|
||||
1.0,
|
||||
0.9999998807907105
|
||||
1.0000001192092896
|
||||
],
|
||||
"Radius": 6.261124134063721,
|
||||
"Capsule Length": 35.38218307495117
|
||||
"Radius": 6.26112174987793,
|
||||
"Capsule Length": 35.38218688964844
|
||||
}
|
||||
},
|
||||
"CC_Base_R_KneeShareBone": {},
|
||||
@@ -215,23 +215,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
1.27018404006958,
|
||||
-2.6311538219451906,
|
||||
35.06704330444336
|
||||
1.2701942920684815,
|
||||
-2.6311514377593996,
|
||||
35.067047119140628
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.48143476247787478,
|
||||
0.5294565558433533,
|
||||
0.5101337432861328,
|
||||
0.47713717818260195
|
||||
0.4771372973918915
|
||||
],
|
||||
"WorldScale": [
|
||||
0.9999999403953552,
|
||||
1.000000238418579,
|
||||
1.000000238418579
|
||||
],
|
||||
"Radius": 12.289390563964844,
|
||||
"Capsule Length": 6.548582077026367
|
||||
"Radius": 12.289389610290528,
|
||||
"Capsule Length": 6.548585891723633
|
||||
}
|
||||
},
|
||||
"CC_Base_Spine02": {},
|
||||
@@ -244,22 +244,22 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
2.773695707321167,
|
||||
-10.61038875579834,
|
||||
62.22718811035156
|
||||
2.773693561553955,
|
||||
-10.610408782958985,
|
||||
62.2271728515625
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.9082421064376831,
|
||||
0.059698693454265597,
|
||||
0.026712516322731973,
|
||||
0.4133022725582123
|
||||
0.9082458019256592,
|
||||
0.05969582125544548,
|
||||
0.026713328436017038,
|
||||
0.41329458355903628
|
||||
],
|
||||
"WorldScale": [
|
||||
0.9999998807907105,
|
||||
1.0000001192092896,
|
||||
1.0000001192092896,
|
||||
1.0000001192092896
|
||||
],
|
||||
"Radius": 6.567502975463867,
|
||||
"Radius": 6.567503929138184,
|
||||
"Capsule Length": 4.080015182495117
|
||||
},
|
||||
"Capsule(0)": {
|
||||
@@ -270,23 +270,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
1.9828077554702759,
|
||||
-4.656966686248779,
|
||||
49.99135971069336
|
||||
1.9828659296035767,
|
||||
-4.656769275665283,
|
||||
49.9914436340332
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.6233355402946472,
|
||||
0.3344622254371643,
|
||||
0.6611127257347107,
|
||||
0.25003552436828616
|
||||
0.6233376264572144,
|
||||
0.3344547152519226,
|
||||
0.6611158847808838,
|
||||
0.25003209710121157
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0,
|
||||
1.0000001192092896,
|
||||
0.9999998211860657
|
||||
0.9999999403953552
|
||||
],
|
||||
"Radius": 11.890731811523438,
|
||||
"Capsule Length": 20.625282287597658
|
||||
"Radius": 11.89073371887207,
|
||||
"Capsule Length": 20.625289916992189
|
||||
},
|
||||
"Capsule(1)": {
|
||||
"Bone Active": true,
|
||||
@@ -296,23 +296,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
-4.63240385055542,
|
||||
-8.07916259765625,
|
||||
45.7261962890625
|
||||
-4.632350921630859,
|
||||
-8.078861236572266,
|
||||
45.7262077331543
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.42426103353500368,
|
||||
0.5660519599914551,
|
||||
0.4941067099571228,
|
||||
0.5054170489311218
|
||||
0.4242660105228424,
|
||||
0.566045880317688,
|
||||
0.49411097168922427,
|
||||
0.5054153203964233
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0000001192092896,
|
||||
1.0000001192092896,
|
||||
0.9999999403953552
|
||||
1.000000238418579,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"Radius": 9.062602043151856,
|
||||
"Capsule Length": 12.078479766845704
|
||||
"Radius": 9.062603950500489,
|
||||
"Capsule Length": 12.07847785949707
|
||||
},
|
||||
"Capsule(2)": {
|
||||
"Bone Active": true,
|
||||
@@ -322,23 +322,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
9.247823715209961,
|
||||
-6.251848220825195,
|
||||
45.71465301513672
|
||||
9.247888565063477,
|
||||
-6.251609802246094,
|
||||
45.7147331237793
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.42426103353500368,
|
||||
0.5660519599914551,
|
||||
0.4941067099571228,
|
||||
0.5054170489311218
|
||||
0.4242660105228424,
|
||||
0.566045880317688,
|
||||
0.49411097168922427,
|
||||
0.5054153203964233
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0000001192092896,
|
||||
1.0000001192092896,
|
||||
0.9999999403953552
|
||||
1.000000238418579,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"Radius": 9.062602043151856,
|
||||
"Capsule Length": 12.078479766845704
|
||||
"Radius": 9.062603950500489,
|
||||
"Capsule Length": 12.07847785949707
|
||||
}
|
||||
},
|
||||
"CC_Base_NeckTwist02": {},
|
||||
@@ -351,23 +351,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
4.20651912689209,
|
||||
-19.62607192993164,
|
||||
72.10423278808594
|
||||
4.204580783843994,
|
||||
-19.628067016601564,
|
||||
72.10245513916016
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.8500508069992065,
|
||||
0.12625616788864137,
|
||||
0.2570338249206543,
|
||||
0.4420482814311981
|
||||
0.8501198887825012,
|
||||
0.12608231604099275,
|
||||
0.25707510113716128,
|
||||
0.44194096326828005
|
||||
],
|
||||
"WorldScale": [
|
||||
1.000000238418579,
|
||||
1.000000238418579,
|
||||
1.0000003576278687,
|
||||
1.0
|
||||
],
|
||||
"Radius": 7.839794158935547,
|
||||
"Capsule Length": 7.367927551269531
|
||||
"Radius": 7.8397932052612309,
|
||||
"Capsule Length": 7.367923736572266
|
||||
}
|
||||
},
|
||||
"CC_Base_FacialBone": {},
|
||||
@@ -390,23 +390,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
19.72686004638672,
|
||||
-14.700096130371094,
|
||||
71.44534301757813
|
||||
19.72115135192871,
|
||||
-14.701730728149414,
|
||||
71.44925689697266
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.8417485356330872,
|
||||
0.043582357466220859,
|
||||
-0.43513593077659609,
|
||||
0.31657037138938906
|
||||
0.8415506482124329,
|
||||
0.04330812022089958,
|
||||
-0.4354802668094635,
|
||||
0.31666040420532229
|
||||
],
|
||||
"WorldScale": [
|
||||
1.000000238418579,
|
||||
0.9999999403953552,
|
||||
0.9999996423721314,
|
||||
0.9999998807907105,
|
||||
0.9999998211860657
|
||||
],
|
||||
"Radius": 5.991597652435303,
|
||||
"Capsule Length": 22.73735809326172
|
||||
"Radius": 5.991599082946777,
|
||||
"Capsule Length": 22.737348556518556
|
||||
}
|
||||
},
|
||||
"CC_Base_L_Forearm": {
|
||||
@@ -418,22 +418,22 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
19.22115707397461,
|
||||
-38.13105773925781,
|
||||
84.58039855957031
|
||||
19.206022262573243,
|
||||
-38.1240234375,
|
||||
84.59626770019531
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.8469575047492981,
|
||||
-0.2822112739086151,
|
||||
-0.44747236371040347,
|
||||
0.05280330777168274
|
||||
0.8467274308204651,
|
||||
-0.28267309069633486,
|
||||
-0.4476063549518585,
|
||||
0.052887994796037677
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0000001192092896,
|
||||
1.000000238418579,
|
||||
0.9999999403953552,
|
||||
0.9999999403953552
|
||||
1.0
|
||||
],
|
||||
"Radius": 4.798511981964111,
|
||||
"Radius": 4.798511505126953,
|
||||
"Capsule Length": 22.90265655517578
|
||||
}
|
||||
},
|
||||
@@ -449,25 +449,25 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
8.864543914794922,
|
||||
-52.39018630981445,
|
||||
89.68778228759766
|
||||
8.839406967163086,
|
||||
-52.372005462646487,
|
||||
89.71028137207031
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
-0.6036764979362488,
|
||||
0.26066043972969057,
|
||||
0.7010456323623657,
|
||||
0.27598172426223757
|
||||
-0.6039369106292725,
|
||||
0.2609888017177582,
|
||||
0.7006175518035889,
|
||||
0.2761886715888977
|
||||
],
|
||||
"WorldScale": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
0.9999998211860657,
|
||||
0.9999998211860657
|
||||
],
|
||||
"Extent": [
|
||||
5.184257984161377,
|
||||
9.812103271484375,
|
||||
9.250751495361329
|
||||
9.812101364135743,
|
||||
9.250753402709961
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -502,23 +502,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
-26.21698570251465,
|
||||
-3.091898202896118,
|
||||
62.5140495300293
|
||||
-26.21482276916504,
|
||||
-3.0838232040405275,
|
||||
62.51490020751953
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.5701809525489807,
|
||||
-0.5581733584403992,
|
||||
0.30875152349472048,
|
||||
0.5176956653594971
|
||||
0.5700636506080627,
|
||||
-0.5584656000137329,
|
||||
0.30844253301620486,
|
||||
0.5176938772201538
|
||||
],
|
||||
"WorldScale": [
|
||||
0.9999997019767761,
|
||||
1.0,
|
||||
1.0000001192092896,
|
||||
0.9999998807907105
|
||||
1.0000001192092896
|
||||
],
|
||||
"Radius": 5.9913129806518559,
|
||||
"Capsule Length": 22.752225875854493
|
||||
"Radius": 5.991312026977539,
|
||||
"Capsule Length": 22.752227783203126
|
||||
}
|
||||
},
|
||||
"CC_Base_R_Forearm": {
|
||||
@@ -530,23 +530,23 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
-53.685977935791019,
|
||||
-1.1645277738571168,
|
||||
70.9034423828125
|
||||
-53.682525634765628,
|
||||
-1.1382777690887452,
|
||||
70.90436553955078
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.5800181031227112,
|
||||
-0.45852217078208926,
|
||||
0.3950117230415344,
|
||||
0.5452542901039124
|
||||
0.5799415707588196,
|
||||
-0.45883747935295107,
|
||||
0.3947125971317291,
|
||||
0.5452869534492493
|
||||
],
|
||||
"WorldScale": [
|
||||
1.000000238418579,
|
||||
1.0000003576278687,
|
||||
1.0,
|
||||
1.000000238418579
|
||||
],
|
||||
"Radius": 4.798357009887695,
|
||||
"Capsule Length": 22.886791229248048
|
||||
"Capsule Length": 22.88680648803711
|
||||
}
|
||||
},
|
||||
"CC_Base_R_ElbowShareBone": {},
|
||||
@@ -561,25 +561,25 @@
|
||||
"Friction": 0.05000000074505806,
|
||||
"Elasticity": 0.5,
|
||||
"WorldTranslate": [
|
||||
-72.01438903808594,
|
||||
0.3300783634185791,
|
||||
75.00787353515625
|
||||
-72.00968170166016,
|
||||
0.3695257902145386,
|
||||
75.01060485839844
|
||||
],
|
||||
"WorldRotationQ": [
|
||||
0.43611618876457217,
|
||||
-0.6424853801727295,
|
||||
0.28400716185569765,
|
||||
0.5624545812606812
|
||||
0.4359774887561798,
|
||||
-0.6426182985305786,
|
||||
0.28357943892478945,
|
||||
0.5626259446144104
|
||||
],
|
||||
"WorldScale": [
|
||||
0.9999997019767761,
|
||||
0.9999998807907105,
|
||||
0.9999999403953552
|
||||
0.9999998211860657,
|
||||
0.9999999403953552,
|
||||
0.9999997615814209
|
||||
],
|
||||
"Extent": [
|
||||
6.091938018798828,
|
||||
9.813437461853028,
|
||||
9.393933296203614
|
||||
6.09193754196167,
|
||||
9.813445091247559,
|
||||
9.393935203552246
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.3 MiB |
Binary file not shown.
Binary file not shown.
+118
-1
@@ -12,6 +12,20 @@
|
||||
# Usage:
|
||||
# blender --background --python tools/cc_retarget.py -- \
|
||||
# --src "<dir-or-fbx>[,<more>…]" --out <out.glb> [--name <ClipName>] [--target <gltf>]
|
||||
# [--fingers track|static] static = ignore captured finger articulation; fingers
|
||||
# hold the target rig's rest pose (open hand) and the
|
||||
# hand moves as one unit. Use for noisy AI-mocap fingers.
|
||||
# [--despike] heal short AI-mocap glitches: frames whose world-space
|
||||
# acceleration spikes far above the clip's median get
|
||||
# re-interpolated from the good frames around them.
|
||||
# Only short windows are touched, so intentional sharp
|
||||
# moves survive. Extra important for ping-pong clips —
|
||||
# a glitch plays twice (forward + reversed).
|
||||
#
|
||||
# Looping note: seamless looping is owned by the ping-pong bake (animation repo
|
||||
# pingpong_bake.py → <pack>_pp), which loops and returns to start by construction.
|
||||
# The former --inplace / --loop-blend passes were removed 2026-07-17; resurrect from
|
||||
# git history (commit 535719601) if a forward-loop clip ever needs them again.
|
||||
|
||||
import bpy, sys, os, re
|
||||
from mathutils import Matrix, Quaternion, Vector
|
||||
@@ -190,12 +204,113 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, unit):
|
||||
return new_act
|
||||
|
||||
|
||||
DESPIKE_FACTOR = 200.0 # world accel must exceed FACTOR x the clip's median accel
|
||||
DESPIKE_MAX_WIN = 12 # frames; longer regions are real motion, leave them alone
|
||||
DESPIKE_PAD = 3
|
||||
|
||||
|
||||
def _hermite_patch(kps, a, b):
|
||||
"""Replace kps[a+1..b-1] with a cubic that matches value AND slope at both edges."""
|
||||
v = [k.co.y for k in kps]
|
||||
n = b - a
|
||||
m0 = (v[a] - v[a - 1]) * n
|
||||
m1 = (v[b + 1] - v[b]) * n
|
||||
for i in range(a + 1, b):
|
||||
t = (i - a) / n
|
||||
h00 = 2 * t ** 3 - 3 * t ** 2 + 1
|
||||
h10 = t ** 3 - 2 * t ** 2 + t
|
||||
h01 = -2 * t ** 3 + 3 * t ** 2
|
||||
h11 = t ** 3 - t ** 2
|
||||
kps[i].co.y = h00 * v[a] + h10 * m0 + h01 * v[b] + h11 * m1
|
||||
|
||||
|
||||
def despike(act, tgt_arm, scn):
|
||||
"""Heal short AI-mocap glitches. Detection runs in WORLD space (same metric as
|
||||
tools/anim_qc.py): a bone whose world acceleration towers over the clip's median
|
||||
for a few frames is a tracking error, not choreography. The flagged bone's local
|
||||
curves — and its parent's, where the glitch usually originates — are re-interpolated
|
||||
across the window with slope-matched cubics."""
|
||||
bones = sorted(set(BONE_MAP.values()))
|
||||
prev = tgt_arm.animation_data.action
|
||||
tgt_arm.animation_data.action = act
|
||||
if getattr(act, "slots", None) and tgt_arm.animation_data.action_slot is None:
|
||||
tgt_arm.animation_data.action_slot = act.slots[0]
|
||||
f0, f1 = (int(x) for x in act.frame_range)
|
||||
|
||||
pos = {n: [] for n in bones}
|
||||
for f in range(f0, f1 + 1):
|
||||
scn.frame_set(f)
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
te = tgt_arm.evaluated_get(dg)
|
||||
for n in bones:
|
||||
pos[n].append((tgt_arm.matrix_world @ te.pose.bones[n].matrix).translation.copy())
|
||||
tgt_arm.animation_data.action = prev
|
||||
|
||||
acc = {n: [(p[i + 1] - 2 * p[i] + p[i - 1]).length for i in range(1, len(p) - 1)]
|
||||
for n, p in pos.items()}
|
||||
flat = sorted(x for arr in acc.values() for x in arr)
|
||||
thr = DESPIKE_FACTOR * (flat[len(flat) // 2] or 1e-9)
|
||||
|
||||
by_bone = {}
|
||||
for fc in action_fcurves(act):
|
||||
if fc.data_path.startswith('pose.bones["'):
|
||||
by_bone.setdefault(fc.data_path.split('"')[1], []).append(fc)
|
||||
|
||||
repaired = 0
|
||||
for n, arr in acc.items():
|
||||
bad = [i + 1 for i, a in enumerate(arr) if a > thr] # -> pos/key index
|
||||
if not bad:
|
||||
continue
|
||||
spans, s, e = [], bad[0], bad[0]
|
||||
for i in bad[1:]:
|
||||
if i - e <= DESPIKE_PAD * 2:
|
||||
e = i
|
||||
else:
|
||||
spans.append((s, e))
|
||||
s = e = i
|
||||
spans.append((s, e))
|
||||
parent = tgt_arm.data.bones[n].parent
|
||||
chain = [n] + ([parent.name] if parent else [])
|
||||
for s, e in spans:
|
||||
a = max(1, s - DESPIKE_PAD)
|
||||
b = min(len(pos[n]) - 2, e + DESPIKE_PAD)
|
||||
if b - a > DESPIKE_MAX_WIN or b - a < 2:
|
||||
continue
|
||||
for bone in chain:
|
||||
for fc in by_bone.get(bone, []):
|
||||
kps = sorted(fc.keyframe_points, key=lambda k: k.co.x)
|
||||
_hermite_patch(kps, a, b)
|
||||
fc.update()
|
||||
repaired += 1
|
||||
print(f"[cc] --despike: {n} @ frame {f0 + s}"
|
||||
f" ({(s) / 60:.2f}s) window {b - a}f via {'+'.join(chain)}")
|
||||
print(f"[cc] --despike: repaired {repaired} glitch window(s), threshold {thr:.4f}")
|
||||
|
||||
|
||||
def main():
|
||||
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||
args = dict(zip(argv[::2], argv[1::2]))
|
||||
args, flags, i = {}, set(), 0
|
||||
while i < len(argv):
|
||||
if argv[i] == "--despike":
|
||||
flags.add("despike")
|
||||
i += 1
|
||||
else:
|
||||
args[argv[i]] = argv[i + 1]
|
||||
i += 2
|
||||
srcs, out = args["--src"].split(","), args["--out"]
|
||||
target = args.get("--target", DEFAULT_TARGET_GLTF)
|
||||
forced_name = args.get("--name")
|
||||
fingers = args.get("--fingers", "track")
|
||||
if fingers not in ("track", "static"):
|
||||
raise SystemExit(f"--fingers must be 'track' or 'static', got '{fingers}'")
|
||||
if fingers == "static":
|
||||
# Unmapped bones ride their parent at rest offset, so dropping the finger
|
||||
# entries makes each hand move as one rigid unit in the rig's rest hand pose.
|
||||
dropped = [k for k, v in BONE_MAP.items()
|
||||
if re.match(r"(thumb|index|middle|ring|pinky)_", v)]
|
||||
for k in dropped:
|
||||
del BONE_MAP[k]
|
||||
print(f"[cc] --fingers static: ignoring {len(dropped)} finger bone mappings")
|
||||
|
||||
fbx_files = []
|
||||
for s in srcs:
|
||||
@@ -241,6 +356,8 @@ def main():
|
||||
print(f"[cc] SKIP (no armature take): {f}")
|
||||
else:
|
||||
baked = retarget_one(src_arm, tgt_arm, act, name, scn, unit)
|
||||
if "despike" in flags:
|
||||
despike(baked, tgt_arm, scn)
|
||||
done.append(baked)
|
||||
print(f"[cc] {i + 1}/{len(fbx_files)} {name} "
|
||||
f"({int(baked.frame_range[1])}f @60, from take '{act.name}')")
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
import bpy, sys, os, re
|
||||
from mathutils import Matrix, Quaternion, Vector
|
||||
|
||||
TARGET_GLTF = "/Users/behcetozanbozkurt/Documents/tinqs-ltd/ariki-game/assets/quaternius/base-characters/Universal Base Characters[Standard]/Base Characters/Godot - UE/Superhero_Male_FullBody.gltf"
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
TARGET_GLTF = os.path.join(REPO_ROOT, "assets/quaternius/base-characters/Universal Base Characters[Standard]/Base Characters/Godot - UE/Superhero_Male_FullBody.gltf")
|
||||
|
||||
# Kevin "B-*" → Quaternius UE names. Fingers map 1:1 (both rigs have 3-segment fingers).
|
||||
BONE_MAP = {
|
||||
|
||||
@@ -57,6 +57,22 @@ def detect_prefix(arm):
|
||||
raise RuntimeError("no *Hips bone found — not a Mixamo rig?")
|
||||
|
||||
|
||||
# ── UAL/Quaternius-source mode (2026-07-21): retarget stock-skeleton clips (UAL,
|
||||
# Kevin) onto a DIFFERENT-convention skeleton (e.g. the renamed Mixamo rig — its
|
||||
# bone local frames differ, so raw clip playback tips her face-down). Identity
|
||||
# bone map (names already match), source frame bones are the Quaternius names.
|
||||
QUAT_BONES = ["pelvis", "spine_01", "spine_02", "spine_03", "neck_01", "Head"]
|
||||
for _T in ("l", "r"):
|
||||
QUAT_BONES += [f"clavicle_{_T}", f"upperarm_{_T}", f"lowerarm_{_T}", f"hand_{_T}",
|
||||
f"thigh_{_T}", f"calf_{_T}", f"foot_{_T}", f"ball_{_T}"]
|
||||
for _i in (1, 2, 3):
|
||||
QUAT_BONES += [f"index_0{_i}_{_T}", f"middle_0{_i}_{_T}", f"ring_0{_i}_{_T}",
|
||||
f"pinky_0{_i}_{_T}", f"thumb_0{_i}_{_T}"]
|
||||
UAL_MAP = {n: n for n in QUAT_BONES}
|
||||
# Per-mode source-frame bones (see retarget_one): mixamo | ual
|
||||
SRC_FRAME_BONES = ("Hips", "Head", "LeftArm")
|
||||
|
||||
|
||||
def apply_object_transform(obj):
|
||||
"""Bake importer object-level rotation/scale into the armature data (meters, Z-up)."""
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
@@ -109,11 +125,12 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, prefix, unit):
|
||||
tgt_rest_q = {n: m.to_quaternion().normalized() for n, m in tgt_rest.items()}
|
||||
|
||||
C = (char_frame(tgt_rest, "pelvis", "Head", "upperarm_l")
|
||||
@ char_frame(src_rest, prefix + "Hips", prefix + "Head", prefix + "LeftArm").inverted()).normalized()
|
||||
@ char_frame(src_rest, prefix + SRC_FRAME_BONES[0], prefix + SRC_FRAME_BONES[1],
|
||||
prefix + SRC_FRAME_BONES[2]).inverted()).normalized()
|
||||
Cinv = C.inverted()
|
||||
|
||||
# Uniform size ratio from hips→head world distance (unit/scale agnostic).
|
||||
h_src = (src_rest[prefix + "Head"].translation - src_rest[prefix + "Hips"].translation).length
|
||||
h_src = (src_rest[prefix + SRC_FRAME_BONES[1]].translation - src_rest[prefix + SRC_FRAME_BONES[0]].translation).length
|
||||
h_tgt = (tgt_rest["Head"].translation - tgt_rest["pelvis"].translation).length
|
||||
tscale = h_tgt / h_src if h_src > 1e-5 else 1.0
|
||||
|
||||
@@ -127,7 +144,7 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, prefix, unit):
|
||||
for pb in tgt_arm.pose.bones:
|
||||
pb.rotation_mode = "QUATERNION"
|
||||
|
||||
src_hips_rest_t = src_rest[prefix + "Hips"].translation
|
||||
src_hips_rest_t = src_rest[prefix + SRC_FRAME_BONES[0]].translation
|
||||
|
||||
for f in range(f0, f1 + 1):
|
||||
scn.frame_set(f)
|
||||
@@ -181,18 +198,23 @@ def retarget_one(src_arm, tgt_arm, action, name, scn, prefix, unit):
|
||||
|
||||
|
||||
def main():
|
||||
global BONE_MAP, SRC_FRAME_BONES
|
||||
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||
args = dict(zip(argv[::2], argv[1::2]))
|
||||
srcs, out = args["--src"].split(","), args["--out"]
|
||||
limit = int(args.get("--limit", 0))
|
||||
target = args.get("--target", DEFAULT_TARGET_GLTF)
|
||||
clips_filter = [c.strip().lower() for c in args.get("--clips", "").split(",") if c.strip()]
|
||||
if args.get("--map") == "ual":
|
||||
BONE_MAP = UAL_MAP # identity: source bones already quat-named
|
||||
SRC_FRAME_BONES = ("pelvis", "Head", "upperarm_l")
|
||||
|
||||
fbx_files = []
|
||||
for s in srcs:
|
||||
s = s.strip()
|
||||
if os.path.isdir(s):
|
||||
for root, _, files in os.walk(s):
|
||||
fbx_files += [os.path.join(root, x) for x in sorted(files) if x.lower().endswith(".fbx")]
|
||||
fbx_files += [os.path.join(root, x) for x in sorted(files) if x.lower().endswith((".fbx", ".glb", ".gltf"))]
|
||||
else:
|
||||
fbx_files.append(s)
|
||||
if limit:
|
||||
@@ -218,22 +240,31 @@ def main():
|
||||
name = clip_name(f)
|
||||
pre = set(bpy.data.objects)
|
||||
pre_actions = set(bpy.data.actions)
|
||||
if f.lower().endswith((".glb", ".gltf")):
|
||||
bpy.ops.import_scene.gltf(filepath=f)
|
||||
else:
|
||||
bpy.ops.import_scene.fbx(filepath=f)
|
||||
new_objs = [o for o in bpy.data.objects if o not in pre]
|
||||
src_arm = next((o for o in new_objs if o.type == "ARMATURE"), None)
|
||||
new_acts = [a for a in bpy.data.actions if a not in pre_actions]
|
||||
if clips_filter:
|
||||
new_acts = [a for a in new_acts if any(c in a.name.lower() for c in clips_filter)]
|
||||
if src_arm is None or not new_acts:
|
||||
print(f"[mixamo] SKIP (no armature/action): {f}")
|
||||
else:
|
||||
bpy.context.view_layer.update()
|
||||
prefix = detect_prefix(src_arm)
|
||||
prefix = "" if args.get("--map") == "ual" else detect_prefix(src_arm)
|
||||
# Importer unit scale (cm→m 0.01) — capture BEFORE transform_apply wipes it;
|
||||
# the hips translation math needs it (fcurves stay in source units).
|
||||
unit = src_arm.scale.x
|
||||
apply_object_transform(src_arm)
|
||||
act = retarget_one(src_arm, tgt_arm, new_acts[0], name, scn, prefix, unit)
|
||||
for act_src in new_acts:
|
||||
out_name = act_src.name if args.get("--map") == "ual" else name
|
||||
if args.get("--map") == "ual":
|
||||
act_src.name += "__src" # free the clean name — Blender dedupes the output to .001 otherwise
|
||||
act = retarget_one(src_arm, tgt_arm, act_src, out_name, scn, prefix, unit)
|
||||
done.append(act)
|
||||
print(f"[mixamo] {i + 1}/{len(fbx_files)} {name} ({int(act.frame_range[1])}f, prefix='{prefix}')")
|
||||
print(f"[mixamo] {i + 1}/{len(fbx_files)} {out_name} ({int(act.frame_range[1])}f, prefix='{prefix}')")
|
||||
for o in new_objs:
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
for a in new_acts:
|
||||
|
||||
Reference in New Issue
Block a user