Names lie and anatomy does not — the four VRoid imports each broke role resolution differently, and the lesson generalises: anything guessing anatomy from a bone name needs a structural fallback. Also lists what is shipping and which characters have chains that do not simulate. Co-Authored-By: Claude Opus 5 <[email protected]>
128 lines
5.6 KiB
Markdown
128 lines
5.6 KiB
Markdown
# Rigging and retargeting
|
|
|
|
## Roles, not names
|
|
|
|
`tools/rig_map.py` resolves a skeleton to ROLES — `hips`, `spine[]`, `neck`,
|
|
`head`, and `limb[(role, side)]` for `thigh/shin/foot/toe/shoulder/upper_arm/
|
|
forearm/hand`. Matching is by whole tokens plus anatomy (chain length, position,
|
|
which bone is a child of which), so a Rigify `DEF-thigh.L`, a Mixamo
|
|
`mixamorig:LeftUpLeg` and a bespoke `Bip01_L_Thigh` all land on the same role.
|
|
|
|
This is what removed the need to destroy foreign skeletons. `roles.missing_core()`
|
|
is the gate: if the core roles cannot be found the pipeline stops rather than
|
|
guessing.
|
|
|
|
The resolved roles are written to `<model>.rig.json` and read at runtime by
|
|
`ShooterPoseModifier._resolve`, which aliases its library-flavoured names
|
|
(`DEF-hips`, `DEF-spine.001`…) onto whatever this rig calls them. Taila's hips
|
|
are `DEF-spine`, her head is `DEF-spine.006`, and she has **no bone with "neck"
|
|
in its name at all** — unresolved, every lean, aim pitch and slide head-lift
|
|
silently did nothing.
|
|
|
|
### Names lie. Anatomy does not.
|
|
|
|
Four sources that were not authored against the library's spelling each broke
|
|
role resolution in a different way. All four fixes are in; the lesson is that
|
|
**anything guessing anatomy from a name needs a structural fallback.**
|
|
|
|
- A bare `leg` is the SHIN on Mixamo (`LeftUpLeg` is the thigh) and the THIGH on
|
|
a rig whose shin is called `knee`. Same token, opposite bones, both common. So
|
|
`RigRoles` walks the leg upward from the foot and fills in whatever the names
|
|
could not, stepping over twist bones.
|
|
- Claims are granted **longest-stem first**. Taking the first role in
|
|
`LIMB_ORDER` that matched at all let shin's catch-all `"leg"` beat thigh's
|
|
exact `"upperleg"`, and the outcome depended on bone iteration order.
|
|
- Cosmetic and spring classes accept a **two-character positional suffix**:
|
|
`HairFL`, `HairFR`, `HairF_Top` tokenise to `hairfl` and matched nothing, so a
|
|
character imported with no hair chains at all. Two characters is short enough
|
|
that `forearm` and `earring` are still not swept in.
|
|
- VRoid spells legs `UpperLeg`/`LowerLeg`. Any CHECK that name-matches
|
|
`thigh`/`shin` will silently pass or silently fail on it — see
|
|
`verification.md`.
|
|
|
|
If a new source fails with `could not identify these bones`, dump the joint names
|
|
first (`separation.md` has a no-Blender snippet) and decide whether it is a
|
|
missing stem or a case only anatomy can settle.
|
|
|
|
## Rebuilding the hierarchy
|
|
|
|
A Rigify DEF-rig exports its chain roots parented straight to the armature root,
|
|
because Rigify drives them by constraint rather than by hierarchy. Left that way,
|
|
rotating the hips leaves the legs, skirt and hair floating in place.
|
|
|
|
`rebuild_hierarchy` re-attaches orphans: by anatomy where the role is known, and
|
|
by rest geometry (nearest plausible parent) otherwise. **Cloth may only attach to
|
|
the trunk.**
|
|
|
|
## Subdividing cloth panels
|
|
|
|
`subdivide_cloth_panels(arm, meshes, roles, segments=4)`.
|
|
|
|
A skirt panel that is a single bone from the waist is a rigid flap: it can only
|
|
rotate about its own head, and a contact near that head is unreachable at any
|
|
angle. Splitting each panel into a chain is what lets it bend, and it is why the
|
|
ZZZ-convention skirt is a grid rather than a fan.
|
|
|
|
On Taila this turns 21 panel bones into 21 chains of 4. The segment lengths come
|
|
out uneven (49/49/49/141 mm) because the last segment runs on to the hem.
|
|
|
|
Weights are redistributed along the panel as it is split, so the mesh follows the
|
|
new chain.
|
|
|
|
## Twist bones
|
|
|
|
A forearm or thigh twist bone takes half the roll of its parent so the skin does
|
|
not candy-wrap. They are detected (`is_segment_of`) and recorded in the sidecar's
|
|
`twist` list. They are also folded into the limb when measuring collider radii:
|
|
most of a thigh's surface belongs to `DEF-thigh.L.001`, and what is left
|
|
dominated by `DEF-thigh.L` is mostly hip flare, which fitted a 0.154 m radius —
|
|
a 30 cm thigh.
|
|
|
|
## Joint helpers
|
|
|
|
`SkinJointHelper.install` runs for EVERY model however it was rigged. Linear-blend
|
|
skinning collapses any joint by cos(angle/2) no matter how good the weights are;
|
|
measured at the knee, 0.77 without helpers against 0.99 with. They are updated
|
|
LAST, inside the modification pass, so each helper tracks whatever final rotation
|
|
its child bone ended up with.
|
|
|
|
## The retarget maths
|
|
|
|
Bake each clip as a **rest-relative delta**:
|
|
|
|
```
|
|
R_world = src_pose_rot * src_rest_rot⁻¹ what the clip does
|
|
tgt_rot = R_world * tgt_rest_rot done to THIS rig
|
|
```
|
|
|
|
Copying absolute world orientation instead — which is what a constraint bake does
|
|
— forces the library's bone ROLL onto a mesh bound with a different one, and
|
|
twists every limb by a constant offset.
|
|
|
|
Also handled: a facing correction (`facing_correction`) when the library and the
|
|
character face different ways, and a hips-height scale so a short character does
|
|
not float.
|
|
|
|
## Export flags that matter
|
|
|
|
```python
|
|
export_bake_animation=False,
|
|
export_optimize_animation_keep_anim_armature=False,
|
|
```
|
|
|
|
`keep_anim_armature` forces a track onto every bone whether or not the clip
|
|
touches it. Off, the skirt and hair export with **no tracks at all** and belong
|
|
entirely to the spring solver. This one flag is the animation/physics split.
|
|
|
|
## Height normalisation
|
|
|
|
`flatten_and_scale(arm, meshes, TARGET_HEIGHT)` — default 1.75 m. Applied before
|
|
the retarget so the library's stride matches the character's legs.
|
|
|
|
## When a model has no skeleton
|
|
|
|
`tools/autorig.py` will fit one, and the pipeline accepts the quality loss:
|
|
nearest-bone weights, cross-leg bleed, no cloth chains. `weights_authored` comes
|
|
out false, `SkinLegRepair` runs at load to snap the worst of it, and the
|
|
character will have no secondary motion. Prefer finding a rigged source.
|