File size: 7,297 Bytes
56972c4
792cc2f
56972c4
 
 
 
 
792cc2f
56972c4
 
 
792cc2f
56972c4
792cc2f
 
 
 
 
56972c4
792cc2f
 
 
56972c4
792cc2f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56972c4
 
792cc2f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56972c4
 
792cc2f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
---
library_name: onnxruntime
tags:
  - animation
  - character-animation
  - inverse-kinematics
  - pose-estimation
  - onnx
license: other
---

# Animatica Autoposer (SOMA-30)

A neural autoposer for full-body character rigs. Give it a handful of handles β€” where a hand
should be, where the feet are planted, which way the head looks β€” and it returns a complete,
natural pose for a 30-joint skeleton. It is what makes posing in the
[Animatica](https://animatica.ai) Blender add-on feel like moving a character rather than
rotating thirty bones.

It is a **poser, not an animator**: one pose per call, a few milliseconds each, no motion model
and no temporal state. Ask it once per frame and you have an editable animation; ask it once and
you have a pose.

## What you give it

Any number of effectors, in any combination. Each carries a **tolerance** β€” metres of slack β€” so
a handle can be an exact pin or a loose suggestion the model may overrule to keep the body
plausible.

| Effector | Type id | Value | Meaning |
|---|---|---|---|
| Position | `0` | xyz | put this joint here |
| Rotation | `1` | 6D rotation | orient this joint like this |
| Look-at | `2` | xyz + direction | aim this joint at this point |

Plus a **body descriptor**: 29 bone lengths, z-scored. The same effectors solve correctly on a
child, a heavyset adult, or a giant, because proportion is an input rather than an assumption.

Three position effectors is the sparsest input it was trained to answer well; one or two will
return something, but the body below them is mostly prior.

## What you get back

- **draft joint positions** β€” `[B, 90]`, the 30 joints as xyz, root first
- **6D rotations** β€” `[B, 180]`, per joint, for FK on your own skeleton

Run the rotations through SOMA-30 forward kinematics with your body's neutral joints to get world
positions. The bundled IK pass (below) then pins the effectors exactly and keeps the feet out of
the floor.

## Use it in Blender

The Animatica add-on downloads this repository on first use and needs no token or manual setup β€”
handles appear on your rig and you drag them. That is the shortest path to using this model, and
the one it is tuned for.

## Use it directly (ONNX)

`onnx/` is the runtime bundle: two graphs and the metadata that describes them.

```python
import json, numpy as np, onnxruntime as ort
from huggingface_hub import snapshot_download

d = snapshot_download("Animatica-ai/autoposer", allow_patterns=["onnx/*"])
meta = json.load(open(f"{d}/onnx/meta.json"))
poser = ort.InferenceSession(f"{d}/onnx/poser.onnx")

E = 3                                             # three handles: hips and both hands
val = np.zeros((1, E, 7), np.float32)             # 6 value channels + 1 tolerance
val[0, 0, :3], val[0, 0, 6] = (0.0, 0.95, 0.0), 0.005
val[0, 1, :3], val[0, 1, 6] = (0.35, 1.20, 0.20), 0.005
val[0, 2, :3], val[0, 2, 6] = (-0.35, 1.20, 0.20), 0.005

draft, rot6d = poser.run(None, {
    "val":       val,
    "weights":   np.ones((1, E), np.float32),                        # encoder blending weights
    "joint_id":  np.array([[0, 13, 19]], np.int64),                  # Hips, LeftHand, RightHand
    "eff_type":  np.zeros((1, E), np.int64),                         # all positions
    "cond":      np.zeros((1, 29), np.float32),                      # z-scored bone lengths
})
```

Two details the graph will not tell you, and which decide whether the output is a pose or a heap:

- **Reference frame.** Position and look-at values are expressed relative to the centroid of the
  position targets. This checkpoint is floor-referenced (`floor_ref: true`), so **y stays
  absolute** β€” the ground is a real height, not something relative β€” and the centroid is zeroed
  on y before subtracting.
- **Conditioning.** `cond` is the 29 bone lengths, z-scored with `blen_mean` / `blen_std` from
  `meta.json` and clamped to Β±4.5. Zeros mean "the average body the statistics describe", which
  is why the snippet above works at all.

### The IK pass

`onnx/ik.onnx` is a damped least-squares refinement that runs after the network: it pins
effectors to the tolerance you asked for, holds a floor plane, and rolls the toes rather than
sinking the feet. Twelve iterations over a damping ladder, accepting per step. The network gets
the pose right; this makes the handles exact.

`meta.json` carries a **selftest** β€” recorded inputs and expected outputs β€” so a miscompiled
graph is detectable. It matters more than it sounds: a bad kernel fusion does not raise, it
quietly returns a wrong pose. Run the IK graph on `selftest.inputs` and compare against
`selftest.expect`; agreement should be well under half a centimetre.

## Use it directly (PyTorch)

`model.safetensors` + `config.json` are the training weights, for research use and for anyone who
wants to fine-tune. `config.json` records the skeleton (`njoints`, `ux`/`ux_names`), the
conditioning width (`cond_dim: 29`), floor referencing, and the training step.

## The skeleton

30 joints, root first:

```
Hips  Spine1  Spine2  Chest  Neck1  Neck2  Head  Jaw  LeftEye  RightEye
LeftShoulder  LeftArm  LeftForeArm  LeftHand  LeftHandThumbEnd  LeftHandMiddleEnd
RightShoulder RightArm RightForeArm RightHand RightHandThumbEnd RightHandMiddleEnd
LeftLeg  LeftShin  LeftFoot  LeftToeBase
RightLeg RightShin RightFoot RightToeBase
```

`meta.json` has the parent list, the neutral (rest) joint positions, the four foot joints used by
the floor term, and the effector joint ids.

## How it works

Each effector is embedded from its value, tolerance, joint id and type, and the set is blended by
weight into a single pose code β€” so the model takes any number of handles in any order, rather
than a fixed rig of them. Two-stage decoding follows: draft joint positions first, then per-joint
6D rotations, which is what keeps limbs attached to a body of the right proportions instead of
merely near the handles. The body descriptor is injected twice, at the encoder alongside each
effector and again at the decoder embedding (zero-initialised, so it starts as a no-op and is
learned into).

## Files

| File | Size | What |
|---|---|---|
| `onnx/poser.onnx` | 142 MB | the network |
| `onnx/ik.onnx` | 1.9 MB | the IK refinement pass |
| `onnx/meta.json` | 19 KB | skeleton, normalisation stats, checksums, selftest |
| `model.safetensors` | β€” | PyTorch weights |
| `config.json` | β€” | training configuration |

## Limitations

- **One pose at a time.** There is no temporal model: consecutive frames are consistent because
  consecutive effectors are, not because the model remembers anything.
- **SOMA-30 only.** Other skeletons need retargeting onto these 30 joints first.
- **Hands and face are along for the ride.** Fingers are a thumb and middle tip per hand; jaw and
  eyes are in the skeleton but are not meaningfully posed.
- **Fewer than three position effectors** leaves most of the body to the prior. It will answer;
  it just has little to answer from.
- **Floor-referenced.** Feed it absolute heights. A pose built in a different vertical frame will
  come back crouching or floating.

---

Made by [Animatica](https://animatica.ai). Found something odd? Tell us β€” the model and the
add-on move together, and a pose that comes out wrong is usually interesting.