@kieransimkin/dance-rudiments 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +72 -0
- package/CONTRIBUTING.md +8 -0
- package/README.md +261 -45
- package/dist/recording/recording.wasm +0 -0
- package/dist/recording/recording.wasm.json +5 -0
- package/dist/typescript/index.d.ts +2 -1
- package/dist/typescript/index.js +1 -0
- package/dist/typescript/motion-packages.d.ts +125 -0
- package/dist/typescript/motion-packages.js +625 -0
- package/docs/blender-motion-import.md +228 -0
- package/docs/mesh-demos.md +163 -0
- package/docs/midi-harness.md +180 -0
- package/docs/motion-packages.md +324 -0
- package/docs/release-demo.md +197 -0
- package/docs/rig-motion.md +285 -0
- package/docs/rigify-catalogue.md +246 -0
- package/examples/motion-packages/inspect_package.py +21 -0
- package/examples/motion-packages/page-motion.mjs +168 -0
- package/package.json +25 -4
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Rigify character motion on the DanceRudiments clock
|
|
2
|
+
|
|
3
|
+
This extension binds the existing native **N-dimensional Recording** to a
|
|
4
|
+
versioned Blender Rigify control profile. Each model has a semantic type URL;
|
|
5
|
+
each physical parameter has a fixed, one-based channel number. A compiled motion
|
|
6
|
+
retains the profile, source timing, provenance and normalized samples together.
|
|
7
|
+
|
|
8
|
+
The core authoring code is standard-library Python. Live playback still uses
|
|
9
|
+
C++ `SampledRecording` at **64 integer pips per quarter-note beat**. Blender is
|
|
10
|
+
the optional environment for importing, evaluating and driving a rigged mesh.
|
|
11
|
+
Existing XYZ patterns, the 1,731-pattern catalogue and their looping semantics
|
|
12
|
+
are unchanged. Rig motions are **non-looping** and reject out-of-range pips.
|
|
13
|
+
|
|
14
|
+
## Models and types
|
|
15
|
+
|
|
16
|
+
The checked-in inventory was generated using the actual **Blender 5.2.2 LTS**
|
|
17
|
+
Rigify implementation. It covers eight metarigs and 28 component types. The
|
|
18
|
+
[Rigify catalogue guide](rigify-catalogue.md) records the exact build, source
|
|
19
|
+
hashes, channel counts, component contexts and regeneration command.
|
|
20
|
+
|
|
21
|
+
| Rigify preset | Motion type |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| Human | `syn://type/danceflow/motion/rigify/metarig/human` |
|
|
24
|
+
| Basic human | `syn://type/danceflow/motion/rigify/metarig/basic/basic_human` |
|
|
25
|
+
| Basic quadruped | `syn://type/danceflow/motion/rigify/metarig/basic/basic_quadruped` |
|
|
26
|
+
| Bird | `syn://type/danceflow/motion/rigify/metarig/animals/bird` |
|
|
27
|
+
| Cat | `syn://type/danceflow/motion/rigify/metarig/animals/cat` |
|
|
28
|
+
| Horse | `syn://type/danceflow/motion/rigify/metarig/animals/horse` |
|
|
29
|
+
| Shark | `syn://type/danceflow/motion/rigify/metarig/animals/shark` |
|
|
30
|
+
| Wolf | `syn://type/danceflow/motion/rigify/metarig/animals/wolf` |
|
|
31
|
+
|
|
32
|
+
Component types have a parallel hierarchy, for example
|
|
33
|
+
`syn://type/danceflow/motion/rigify/component/limbs/arm` and
|
|
34
|
+
`syn://type/danceflow/motion/rigify/component/spines/super_spine`.
|
|
35
|
+
`synny://` URLs resolve to the **same identity** as their `syn://` equivalents.
|
|
36
|
+
The tree is an interchange convention; it does not add a Potbot dependency or
|
|
37
|
+
modify another repository's type registry.
|
|
38
|
+
|
|
39
|
+
Rigify's presets are configurable. A rig with edited proportions, extra bones,
|
|
40
|
+
different generation settings or another Blender build needs its **own exported
|
|
41
|
+
profile**. The model type can remain the same while its profile fingerprint
|
|
42
|
+
changes. Neither bone count nor matching names establishes rig compatibility.
|
|
43
|
+
|
|
44
|
+
## Numbered channel contract
|
|
45
|
+
|
|
46
|
+
A `dancerudiments.rig-profile` document declares:
|
|
47
|
+
|
|
48
|
+
| Field | Meaning |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `type_uri`, `model`, `version` | Semantic model identity and explicit profile version |
|
|
51
|
+
| `channels[].number` | Persistent one-based parameter number; column index is `number - 1` |
|
|
52
|
+
| `name` | Unique Recording dimension name |
|
|
53
|
+
| `bone`, `data_path`, `array_index` | Exact Blender control and component to drive |
|
|
54
|
+
| `unit` | Blender units, radians, unitless values or other explicitly declared units |
|
|
55
|
+
| `minimum`, `maximum`, `neutral` | Physical range and neutral value |
|
|
56
|
+
| `value_type`, `interpolation` | Float, integer or Boolean; linear, quaternion or step behavior |
|
|
57
|
+
| `enum_items` | Valid numeric choices and labels for discrete enum controls, when present |
|
|
58
|
+
| `rotation_mode` | The actual generated control's Euler order, quaternion or axis-angle mode |
|
|
59
|
+
| `source` | Blender/Rigify identity, rest matrices, parents, locks and scene-unit metadata |
|
|
60
|
+
|
|
61
|
+
The initial export enumerates actual controls deterministically. When extending
|
|
62
|
+
an exported profile, `--previous` preserves the old channel numbers and appends
|
|
63
|
+
new parameters. Removed or changed parameter definitions fail rather than
|
|
64
|
+
silently reusing an old number. The explicit version and SHA-256 fingerprint
|
|
65
|
+
accompany every capture and motion. A fingerprint detects mismatched data; it is
|
|
66
|
+
not a digital signature or a guarantee that a source is trustworthy.
|
|
67
|
+
|
|
68
|
+
The generated manifests cover writable **animator control transforms and numeric
|
|
69
|
+
custom properties**, including IK/FK blends and parent selectors where present.
|
|
70
|
+
Rigify's internal `ORG-`, `DEF-`, `MCH-` and `VIS-` bones are excluded from direct
|
|
71
|
+
control. The existing rig evaluates its constraints and drives the bound mesh.
|
|
72
|
+
Static metarig-generation parameters are inventoried separately: segment counts,
|
|
73
|
+
widget choices and similar settings are not time-varying motion channels.
|
|
74
|
+
|
|
75
|
+
Local transforms remain in Blender's coordinate convention. Exported presets use
|
|
76
|
+
explicit bounds: location ±10 Blender units, Euler angles ±4π radians, scale
|
|
77
|
+
0..4, quaternion components −1..1, and custom properties' declared ranges.
|
|
78
|
+
These are encoding limits, not anatomical joint limits. Set suitable bounds when
|
|
79
|
+
exporting a real character, especially one with large root translation. Values
|
|
80
|
+
outside the chosen range fail; they are never silently clamped or independently
|
|
81
|
+
renormalized per clip.
|
|
82
|
+
|
|
83
|
+
Physical values encode into the existing bipolar Recording contract:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
normalized = 2 * (physical - minimum) / (maximum - minimum) - 1
|
|
87
|
+
physical = minimum + (normalized + 1) * (maximum - minimum) / 2
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Every stored scalar remains finite float64 within `[-1,1]`, rounded to twelve
|
|
91
|
+
decimals. Quaternions are complete `w,x,y,z` groups and must have unit magnitude.
|
|
92
|
+
Boolean/integer selectors use held values, never fractional intermediate choices.
|
|
93
|
+
Quaternion interpolation uses shortest-path SLERP during compilation. Existing
|
|
94
|
+
Euler controls keep their actual rotation order and sampled winding; unwrap
|
|
95
|
+
source discontinuities before capture. The profile does not force Euler-based
|
|
96
|
+
Rigify controls into quaternion mode.
|
|
97
|
+
|
|
98
|
+
## Inspect or export the mappings
|
|
99
|
+
|
|
100
|
+
From a source checkout, the CLI works without installing a native extension for
|
|
101
|
+
inspection and authoring:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
python tools/rig_motion.py catalogue
|
|
105
|
+
python tools/rig_motion.py profile human --output generated/human.profile.json
|
|
106
|
+
python tools/rig_motion.py channels generated/human.profile.json --output generated/human.channels.csv
|
|
107
|
+
python tools/rig_motion.py profile limbs.arm --kind component --output generated/arm.profile.json
|
|
108
|
+
python tools/rig_motion.py sources
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
After installing this source revision, `dancerudiments-rig` is the same command.
|
|
112
|
+
The catalogue JSON and all profile JSON files are shipped inside the Python
|
|
113
|
+
package. The CSV includes every numbered parameter; no XYZ truncation occurs.
|
|
114
|
+
Existing outputs are preserved unless `--overwrite` is explicit. Conversion
|
|
115
|
+
commands refuse output paths that are also their inputs.
|
|
116
|
+
|
|
117
|
+
## Import and capture real animation
|
|
118
|
+
|
|
119
|
+
The Blender bridge is available as the packaged
|
|
120
|
+
`dancerudiments_authoring.blender_bridge` module and through
|
|
121
|
+
`tools/blender_rigify_bridge.py`. The latter wrapper is useful with Blender's
|
|
122
|
+
`--python` command. Playback requires the native DanceRudiments extension for
|
|
123
|
+
the **Python ABI used by that Blender**. Authoring-only import/profile/capture
|
|
124
|
+
operations do not require the native extension.
|
|
125
|
+
|
|
126
|
+
First import a local file using Blender's supported interchange importers:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
blender --background --python tools/blender_rigify_bridge.py -- import-source dance.bvh --save imported-source.blend
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
FBX and glTF/GLB are also accepted. This imports the recording's skeleton and
|
|
133
|
+
animation. Use an explicit retargeting workflow in Blender to map it onto the
|
|
134
|
+
chosen generated Rigify rig, correct the rest-pose/bone bases and root scale,
|
|
135
|
+
choose the appropriate FK/IK controls, and **bake the control animation**. Save
|
|
136
|
+
that result as, for example, `dancer-retargeted.blend`.
|
|
137
|
+
|
|
138
|
+
Export the exact character profile and capture its controls:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
blender --background dancer-retargeted.blend --python tools/blender_rigify_bridge.py -- export-profile --armature rig --model my_dancer --output generated/dancer.profile.json
|
|
142
|
+
blender --background dancer-retargeted.blend --python tools/blender_rigify_bridge.py -- capture-action --armature rig --profile generated/dancer.profile.json --frame-start 1 --frame-end 121 --output generated/dance.capture.json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`--model` is your rig instance's stable descriptive identifier. `--kind metarig`
|
|
146
|
+
with the original model identifier is appropriate when preserving a preset's
|
|
147
|
+
model type. `--location-range`, `--rotation-range` and `--scale-max` choose the
|
|
148
|
+
export contract; modifying these later requires a new capture/profile identity.
|
|
149
|
+
|
|
150
|
+
Capture times are in seconds relative to the first selected Blender frame. The
|
|
151
|
+
source musical beat-zero offset must use **that same origin**. FPS and captured
|
|
152
|
+
frame range are retained in provenance. The bridge restores the previous scene
|
|
153
|
+
frame after capture. BVH import preserves its source FPS. Fractional frame bounds
|
|
154
|
+
allow precise imported endpoints; see `capture-action --help` for frame-step and
|
|
155
|
+
sample-rate controls.
|
|
156
|
+
|
|
157
|
+
The bridge reads baked control values. It does not infer a foreign skeleton's
|
|
158
|
+
mapping, embed arbitrary Python in motion JSON, or replace Rigify's constraints.
|
|
159
|
+
Object placement, mesh shape keys and third-party feature-set controls require
|
|
160
|
+
an explicit appropriate rig/profile integration; they are not silently inferred
|
|
161
|
+
from humanoid motion.
|
|
162
|
+
|
|
163
|
+
## Beat matching and compilation
|
|
164
|
+
|
|
165
|
+
A `dancerudiments.rig-capture` stores physical frame values and timestamps without
|
|
166
|
+
binding them to music yet. A source `BeatMap` supplies strictly increasing
|
|
167
|
+
`(seconds, beat)` anchors, or a constant BPM plus a separately supplied beat-zero
|
|
168
|
+
time. There is no automatic tempo estimate or first-beat guess.
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
python tools/rig_motion.py compile generated/dance.capture.json generated/dance.motion.json --name dance_phrase --source-bpm 100 --source-beat-zero 0.18 --bpm 124 --samples 512
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
That example creates an eight-beat grid (512 pip rows). It requires sufficient
|
|
175
|
+
source motion after its 0.18-second beat origin. At every absolute output pip `p`:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
source_beat = source_beat_offset + motion_rate * p / 64
|
|
179
|
+
source_seconds = source_beat_map.inverse(source_beat)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`--source-beat-offset` selects a source excerpt. `--motion-rate 1/2` uses half a
|
|
183
|
+
source beat per playback beat; `2` uses two. `--start-pip` is an absolute output
|
|
184
|
+
beat position, including negative pickups; it is not an independent clip-local
|
|
185
|
+
offset. For a desired source beat `s` at start pip `p0`, use
|
|
186
|
+
`source_beat_offset = s - motion_rate * p0 / 64`.
|
|
187
|
+
|
|
188
|
+
For drifting tempo or manually marked motion accents, use
|
|
189
|
+
`--source-beat-map beats.json` instead of `--source-bpm`/`--source-beat-zero`.
|
|
190
|
+
Anchors interpolate linearly in accumulated beat position; the inverse maps back
|
|
191
|
+
to source time. A measured map is bounded unless extrapolation is explicitly
|
|
192
|
+
enabled. Source frame bounds are always enforced. There is no automatic looping,
|
|
193
|
+
endpoint extension, foot-plant correction or seam repair.
|
|
194
|
+
|
|
195
|
+
`--bpm` records the **nominal target tempo** for standard Recording compatibility.
|
|
196
|
+
The compiled samples are indexed by musical position and can subsequently play
|
|
197
|
+
under a different fixed or variable target tempo. The original source tempo map
|
|
198
|
+
is retained separately in provenance; it is not replaced by the target BPM.
|
|
199
|
+
|
|
200
|
+
## Native playback and Blender baking
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from dancerudiments_authoring import BeatMap, load_rig_motion
|
|
204
|
+
|
|
205
|
+
motion = load_rig_motion("generated/dance.motion.json")
|
|
206
|
+
player = motion.player() # actual C++ SampledRecording
|
|
207
|
+
values = player.sample(64) # physical values in channel order
|
|
208
|
+
numbered = player.by_channel(64) # {one_based_channel_number: value}
|
|
209
|
+
target = BeatMap.from_bpm(124, beat_zero_seconds=0, source="song tempo")
|
|
210
|
+
values_at_time = player.sample_time(0.5, target)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The target transport determines each pip from **absolute** audio time. A variable
|
|
214
|
+
tempo map integrates successive beat intervals instead of multiplying all
|
|
215
|
+
elapsed time by the latest BPM. Repeated seeks do not leave an accumulated phase.
|
|
216
|
+
`pip_at()` uses floor for negative positions. There is no Python playback fallback
|
|
217
|
+
when the native extension is missing.
|
|
218
|
+
|
|
219
|
+
To apply a native sample or bake the whole motion into a new Blender Action:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
blender --background dancer-retargeted.blend --python tools/blender_rigify_bridge.py -- apply --armature rig --motion generated/dance.motion.json --pip 64 --save posed.blend
|
|
223
|
+
blender --background dancer-retargeted.blend --python tools/blender_rigify_bridge.py -- bake --armature rig --motion generated/dance.motion.json --action-name DanceRudimentsDance --save danced.blend
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`bake --target-beat-map beats.json` supplies a variable target tempo. The output
|
|
227
|
+
frame start anchors the recording's first pip; subsequent pip times use the
|
|
228
|
+
target map's inverse. Without a target map, the recording's nominal fixed tempo
|
|
229
|
+
sets spacing. This places a clip at `--frame-start`, including for negative or
|
|
230
|
+
nonzero starting pips, rather than preserving the original transport's absolute
|
|
231
|
+
beat-zero time. Baked keyframes use constant interpolation to retain the exact
|
|
232
|
+
integer-pip contract. Any smoother rendering is an explicit consumer choice.
|
|
233
|
+
`apply` creates a one-pose Action so the requested pose survives reopening the
|
|
234
|
+
saved scene. Both output operations use full-influence replacement and disable
|
|
235
|
+
NLA evaluation for that armature; previous Actions and NLA data are retained.
|
|
236
|
+
Output `.blend` files must be new paths. Rig/profile mismatches fail before
|
|
237
|
+
applying values.
|
|
238
|
+
|
|
239
|
+
## Interchange and existing consumers
|
|
240
|
+
|
|
241
|
+
`dancerudiments.rig-motion`, schema 1, contains `type_uri`, `profile_sha256`,
|
|
242
|
+
`profile` and an unchanged `dancerudiments.recording` schema-1 document. Loaders
|
|
243
|
+
reject unknown fields, duplicate JSON keys, non-finite values, invalid discrete
|
|
244
|
+
controls/quaternions and mismatched columns or profile hashes.
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
python tools/rig_motion.py inspect generated/dance.motion.json
|
|
248
|
+
python tools/rig_motion.py recording generated/dance.motion.json generated/dance.recording.json
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The extracted recording works with the existing native C++/Python N-dimensional
|
|
252
|
+
sampler and shared recording harness. Its bar view can inspect all normalized
|
|
253
|
+
channels. It is not a mesh viewer. The typed Python layer performs physical-unit
|
|
254
|
+
decoding and target-profile checking. Existing C#/TypeScript XYZ wrappers remain
|
|
255
|
+
unchanged; this update does not claim new typed mesh renderers in DanceMoves or
|
|
256
|
+
FrameRudiments.
|
|
257
|
+
|
|
258
|
+
The existing recording bounds apply: at most **8,388,608 scalar values** and
|
|
259
|
+
**64 MiB JSON** per document. Long full-body recordings may require explicit
|
|
260
|
+
excerpts. No channel is discarded to make a file fit. Native sampling can run
|
|
261
|
+
without Blender; evaluating a full Rigify constraint rig requires Blender or a
|
|
262
|
+
separately implemented consumer capable of the same rig semantics.
|
|
263
|
+
|
|
264
|
+
## Examples, tests and next imports
|
|
265
|
+
|
|
266
|
+
The [synthetic quickstart](../examples/rig-motion/README.md) produces a capture,
|
|
267
|
+
source beat map and typed motion, then optionally samples it through C++ under a
|
|
268
|
+
variable tempo. It contains no third-party choreography or music.
|
|
269
|
+
|
|
270
|
+
```sh
|
|
271
|
+
python -m pip install .
|
|
272
|
+
python -m unittest discover -s tests/python -p 'test_motion*.py' -v
|
|
273
|
+
python -m unittest discover -s tests/python -p 'test_rig*.py' -v
|
|
274
|
+
python examples/rig-motion/quickstart.py --output generated/rig-example --native
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The dedicated Rigify workflow installs the pinned official `bpy` runtime and
|
|
278
|
+
checks an actual generated armature, native sampling, skin deformation and
|
|
279
|
+
save/reopen persistence. Its synthetic BVH, FBX and GLB importer fixtures also
|
|
280
|
+
verify source timing, fractional capture endpoints and retained skin bindings.
|
|
281
|
+
See [motion-source research](motion-sources.md) for 100STYLE, CMU, Mixamo,
|
|
282
|
+
Rokoko, AIST++, AMASS, LaFAN1, Motorica and animal-motion candidates, with separate
|
|
283
|
+
format/access/licence notes. Dataset-specific SMPL conversion, general automatic
|
|
284
|
+
retargeting, automatic beat inference, contacts and loop/transition repair remain
|
|
285
|
+
future adapters rather than implied capabilities of this first implementation.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Rigify model types and numbered control channels
|
|
2
|
+
|
|
3
|
+
DanceRudiments can describe and drive a generated Blender Rigify armature through
|
|
4
|
+
a versioned `dancerudiments.rig-profile`. A mesh bound to that armature deforms
|
|
5
|
+
through Blender's normal Armature modifier. The profile describes animator
|
|
6
|
+
controls; it does not replace skin weights, rig constraints or inverse kinematics.
|
|
7
|
+
|
|
8
|
+
The checked-in catalogue was extracted by **running Blender 5.2.2 LTS**, build
|
|
9
|
+
`d13f752e3b9c`, and generating its actual built-in rigs. It contains **8 metarig
|
|
10
|
+
presets and 28 component sample profiles**. Every entry has a hierarchical type
|
|
11
|
+
URL, a concrete numbered channel manifest and a SHA-256 fingerprint. The Blender
|
|
12
|
+
build, normalized Rigify Python-source digest and source URL are retained in each
|
|
13
|
+
profile. No third-party character mesh, motion recording or music is bundled.
|
|
14
|
+
|
|
15
|
+
## Complete metarig presets
|
|
16
|
+
|
|
17
|
+
| Blender model | Semantic type suffix after `syn://type/danceflow/motion/rigify/metarig/` | Channels |
|
|
18
|
+
| --- | --- | ---: |
|
|
19
|
+
| `human` | `human` | 1,967 |
|
|
20
|
+
| `Basic.basic_human` | `basic/basic_human` | 653 |
|
|
21
|
+
| `Basic.basic_quadruped` | `basic/basic_quadruped` | 799 |
|
|
22
|
+
| `Animals.bird` | `animals/bird` | 1,327 |
|
|
23
|
+
| `Animals.cat` | `animals/cat` | 2,379 |
|
|
24
|
+
| `Animals.horse` | `animals/horse` | 1,286 |
|
|
25
|
+
| `Animals.shark` | `animals/shark` | 579 |
|
|
26
|
+
| `Animals.wolf` | `animals/wolf` | 2,669 |
|
|
27
|
+
|
|
28
|
+
There are **11,659 channels across these eight preset manifests**. Numbers are
|
|
29
|
+
local to a profile: channel 265 in one model need not be the same parameter as
|
|
30
|
+
channel 265 in another. The profile type and fingerprint accompany the data.
|
|
31
|
+
`synny://` and `syn://` resolve to the same type identity; serialization uses
|
|
32
|
+
`syn://`. They are not two separate registries.
|
|
33
|
+
|
|
34
|
+
## Component samples
|
|
35
|
+
|
|
36
|
+
Rigify constructs complete metarigs from reusable component rig types. The
|
|
37
|
+
component profiles use `syn://type/danceflow/motion/rigify/component/` followed
|
|
38
|
+
by the dotted Blender name expressed as a path, for example `limbs/arm`.
|
|
39
|
+
|
|
40
|
+
| Component | Channels in the generated sample |
|
|
41
|
+
| --- | ---: |
|
|
42
|
+
| `basic.copy_chain` | 40 |
|
|
43
|
+
| `basic.pivot` | 20 |
|
|
44
|
+
| `basic.raw_copy` | 10 |
|
|
45
|
+
| `basic.super_copy` | 20 |
|
|
46
|
+
| `experimental.super_chain` | 70 |
|
|
47
|
+
| `face.basic_tongue` | 41 |
|
|
48
|
+
| `face.skin_eye` | 84 |
|
|
49
|
+
| `face.skin_jaw` | 132 |
|
|
50
|
+
| `faces.super_face` | 892 |
|
|
51
|
+
| `limbs.arm` | 113 |
|
|
52
|
+
| `limbs.front_paw` | 144 |
|
|
53
|
+
| `limbs.leg` | 146 |
|
|
54
|
+
| `limbs.paw` | 144 |
|
|
55
|
+
| `limbs.rear_paw` | 144 |
|
|
56
|
+
| `limbs.simple_tentacle` | 70 |
|
|
57
|
+
| `limbs.spline_tentacle` | 115 |
|
|
58
|
+
| `limbs.super_finger` | 52 |
|
|
59
|
+
| `limbs.super_limb` | 113 |
|
|
60
|
+
| `limbs.super_palm` | 14 |
|
|
61
|
+
| `skin.anchor` | 20 |
|
|
62
|
+
| `skin.basic_chain` | 38 |
|
|
63
|
+
| `skin.glue` | 20, in the explicitly declared containing-rig fixture |
|
|
64
|
+
| `skin.stretchy_chain` | 38 |
|
|
65
|
+
| `skin.transform.basic` | 20 |
|
|
66
|
+
| `spines.basic_spine` | 108 |
|
|
67
|
+
| `spines.basic_tail` | 69 |
|
|
68
|
+
| `spines.super_head` | 44 |
|
|
69
|
+
| `spines.super_spine` | 195 |
|
|
70
|
+
|
|
71
|
+
These are concrete upstream sample configurations, including their generated
|
|
72
|
+
root and containing controls. Changing chain length, component settings or the
|
|
73
|
+
metarig composition can change the controls; export a profile from the resulting
|
|
74
|
+
rig instead of assuming a sample's channel layout still applies.
|
|
75
|
+
|
|
76
|
+
`skin.glue` has **no independent animator control**: it connects to a skin control
|
|
77
|
+
created by another component. Its fixture adds a real `skin.anchor` at the glue
|
|
78
|
+
head, successfully generates both components and records their containing rig's
|
|
79
|
+
root/anchor channels. Both the catalogue and profile explicitly mark this context.
|
|
80
|
+
The channels are not represented as new controls created by glue. Similarly,
|
|
81
|
+
the default `basic.raw_copy` sample exposes its generated root while preserving
|
|
82
|
+
its deformation bone internally.
|
|
83
|
+
|
|
84
|
+
The catalogue also enumerates each component's **generation settings**—chain
|
|
85
|
+
segments, rotation-axis options and similar configuration. Those settings are
|
|
86
|
+
metadata, not animated control channels. They must be applied before generating
|
|
87
|
+
the rig. The runtime profile covers the generated result.
|
|
88
|
+
|
|
89
|
+
## Numbering and physical meaning
|
|
90
|
+
|
|
91
|
+
Each channel defines its stable **one-based number**, safe Recording dimension
|
|
92
|
+
name, exact bone name, Blender property path, array component, unit, minimum,
|
|
93
|
+
maximum, neutral value, value type and interpolation policy. Quaternion channels
|
|
94
|
+
retain Blender's **w, x, y, z** ordering. Euler controls retain the actual Euler
|
|
95
|
+
order chosen by Rigify; the bridge does not change a rig's rotation mode.
|
|
96
|
+
Integer selectors also retain their allowed numeric enum choices and labels.
|
|
97
|
+
|
|
98
|
+
For example, the shipped `Basic.basic_human` profile assigns:
|
|
99
|
+
|
|
100
|
+
| Channel | Exact control parameter |
|
|
101
|
+
| ---: | --- |
|
|
102
|
+
| 203–205 | `hand_ik.L.location` X, Y, Z |
|
|
103
|
+
| 206–209 | `hand_ik.L.rotation_quaternion` W, X, Y, Z |
|
|
104
|
+
| 210–212 | `hand_ik.L.scale` X, Y, Z |
|
|
105
|
+
| 265–267 | `root.location` X, Y, Z |
|
|
106
|
+
| 268–271 | `root.rotation_quaternion` W, X, Y, Z |
|
|
107
|
+
| 272–274 | `root.scale` X, Y, Z |
|
|
108
|
+
| 617 | `upper_arm_parent.L["IK_FK"]` |
|
|
109
|
+
| 619 | `upper_arm_parent.L["IK_parent"]` |
|
|
110
|
+
| 621 | `upper_arm_parent.L["pole_vector"]` |
|
|
111
|
+
|
|
112
|
+
These are exact assignments from the checked-in profile, not a generic humanoid
|
|
113
|
+
bone convention. The complete list is machine readable and can be exported to
|
|
114
|
+
CSV with the motion CLI.
|
|
115
|
+
|
|
116
|
+
Local location is measured in Blender units; the scene's `scale_length` is part
|
|
117
|
+
of the profile. Angles are radians and scales are dimensionless. Controls use
|
|
118
|
+
local pose/basis space relative to the retained bone rest matrices and hierarchy.
|
|
119
|
+
Armature-object animation is not captured as a bone channel; bake locomotion to
|
|
120
|
+
the rig's root control first when it is part of the motion.
|
|
121
|
+
|
|
122
|
+
The built-in transform ranges are ±10 Blender units for local translation,
|
|
123
|
+
±4π radians for Euler/axis-angle rotation and 0–4 for scale. Quaternion components
|
|
124
|
+
use −1–1. Numeric custom properties use their declared limits; custom enums use
|
|
125
|
+
the actual allowed choices rather than Blender's generic int32 storage range.
|
|
126
|
+
The exporter accepts explicit range overrides. A capture that exceeds its
|
|
127
|
+
profile's bounds fails visibly; it is never silently clipped.
|
|
128
|
+
|
|
129
|
+
The encoding into the existing normalized Recording contract is:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
encoded = 2 × (physical − minimum) / (maximum − minimum) − 1
|
|
133
|
+
physical = minimum + (encoded + 1) × (maximum − minimum) / 2
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The existing float64/twelve-decimal contract remains in force. Quaternion groups
|
|
137
|
+
must form a unit quaternion, and native output is normalized when applied in
|
|
138
|
+
Blender to remove small numerical drift. Boolean/integer/enum channels use step
|
|
139
|
+
sampling and reject invalid intermediate values; float blends remain continuous.
|
|
140
|
+
Euler curves preserve their sampled winding. Correct discontinuous Euler wraps
|
|
141
|
+
in the source action before capture, or use an appropriate quaternion control.
|
|
142
|
+
|
|
143
|
+
Initial exports enumerate bones deterministically and their writable transforms
|
|
144
|
+
and numeric custom properties in fixed order. Re-export with `--previous` to
|
|
145
|
+
retain every existing channel number and append new bindings. Removed channels,
|
|
146
|
+
changed ranges or changed parameter semantics require an explicit new profile;
|
|
147
|
+
old recordings are not silently reinterpreted. Rest-pose changes also change the
|
|
148
|
+
fingerprint even when names and numbers remain identical.
|
|
149
|
+
|
|
150
|
+
## Export and capture a real character
|
|
151
|
+
|
|
152
|
+
The helper lives in the installable package as
|
|
153
|
+
`dancerudiments_authoring.blender_bridge`; `tools/blender_rigify_bridge.py` is its
|
|
154
|
+
checkout entry point. Capture and profile discovery use Blender plus the Python
|
|
155
|
+
authoring package. Native apply/bake additionally require a DanceRudiments
|
|
156
|
+
extension built for the Python ABI used by that Blender installation.
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
blender --background character.blend --python tools/blender_rigify_bridge.py -- export-profile --armature rig --model my_character --output my-character.profile.json
|
|
160
|
+
blender --background character.blend --python tools/blender_rigify_bridge.py -- capture-action --armature rig --profile my-character.profile.json --frame-start 1 --frame-end 241 --output my-character.capture.json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Use `--location-range`, `--rotation-range` and `--scale-max` on `export-profile`
|
|
164
|
+
when the character needs different explicit transform bounds. Use `--previous`
|
|
165
|
+
with a prior profile to preserve numbering when adding controls. A saved profile
|
|
166
|
+
fingerprints the actual rest matrices, modes, source version and unit scale, but
|
|
167
|
+
does not include the current pose or random Rigify instance ID. Applying to a
|
|
168
|
+
different rig fails with a fingerprint mismatch.
|
|
169
|
+
|
|
170
|
+
Capture accepts fractional `--frame-start`, `--frame-end` and `--frame-step`
|
|
171
|
+
values. It always retains the explicit last endpoint. `--sample-fps` selects an
|
|
172
|
+
explicit sampling rate instead of `--frame-step`, including subframes. Time zero
|
|
173
|
+
is the first captured frame; source frames, FPS and time origin are recorded in
|
|
174
|
+
provenance. Supply musical beat anchors relative to this origin when compiling
|
|
175
|
+
the capture. Captures are bounded by the existing scalar-count and file-size
|
|
176
|
+
limits; select an excerpt when a long, high-channel-count motion exceeds them.
|
|
177
|
+
|
|
178
|
+
## Import and retarget motion
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
blender --background --factory-startup --python tools/blender_rigify_bridge.py -- import-source dance.bvh --save dance-source.blend
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The bridge delegates `.bvh`, `.fbx`, `.gltf` and `.glb` to Blender's own importers.
|
|
185
|
+
BVH import preserves the source FPS in the scene and updates its duration.
|
|
186
|
+
FBX/glTF may have fractional keyframe endpoints; choose explicit capture bounds
|
|
187
|
+
and sampling rates after retargeting. Imports retain their original skeleton
|
|
188
|
+
until an explicit retargeting/baking step maps them to the generated Rigify
|
|
189
|
+
**controls**. Bone-name copying is not sufficient to compensate for rest-pose
|
|
190
|
+
axes, scale, IK, poles or foot contacts. See [motion sources and tools](motion-sources.md).
|
|
191
|
+
|
|
192
|
+
After retargeting, export the exact destination rig's profile and use
|
|
193
|
+
`capture-action`; the regular motion compiler maps this capture to 64 pips per
|
|
194
|
+
beat. The adapter does not infer musical tempo or foot-contact events.
|
|
195
|
+
|
|
196
|
+
## Native playback and saved Blender actions
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
blender --background character.blend --python tools/blender_rigify_bridge.py -- apply --armature rig --motion dance.rig-motion.json --pip 64 --save pose-at-beat-one.blend
|
|
200
|
+
blender --background character.blend --python tools/blender_rigify_bridge.py -- bake --armature rig --motion dance.rig-motion.json --target-beat-map song-beats.json --frame-start 1 --save song-matched-character.blend
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Both commands sample the existing native C++ recording engine. `apply` writes a
|
|
204
|
+
new one-pose Action, so the requested pose persists after reopening the file.
|
|
205
|
+
`bake` writes a new Action with a key at every recorded pip and explicitly sets
|
|
206
|
+
constant interpolation, retaining the integer-pip playback contract without
|
|
207
|
+
Bezier overshoot. It anchors the recording's first pip at `--frame-start`.
|
|
208
|
+
With `--target-beat-map`, each following pip uses the map's inverse beat-to-time
|
|
209
|
+
conversion; otherwise it uses the recording's nominal tempo.
|
|
210
|
+
|
|
211
|
+
The saved output uses the new Action at full replacement influence and disables
|
|
212
|
+
previous NLA evaluation, preventing accidental mixing with existing animation.
|
|
213
|
+
Previous Action and NLA data are retained. A failed operation restores the old
|
|
214
|
+
Action and animation settings. Output `.blend` paths must be new files.
|
|
215
|
+
|
|
216
|
+
The bridge never animates `DEF-`, `MCH-`, `ORG-` or visual helper bones directly.
|
|
217
|
+
It drives animator controls and lets the generated rig evaluate its constraints,
|
|
218
|
+
drivers and skin deformation. Mesh shape keys, arbitrary external feature-set
|
|
219
|
+
semantics and automatic universal retargeting are outside this profile contract.
|
|
220
|
+
For another Blender/Rigify version or a customized metarig, discover the actual
|
|
221
|
+
rig rather than assuming these pinned manifests remain valid.
|
|
222
|
+
|
|
223
|
+
## Reproduce the manifests and integration check
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
blender --background --factory-startup --python tools/blender_rigify_bridge.py -- catalogue --output-dir python/dancerudiments_authoring/rig_profiles
|
|
227
|
+
PYTHONPATH=python python -m unittest discover -s tests/python -p test_rigs.py -v
|
|
228
|
+
python tools/test_blender_rigify_bridge.py --output-dir .cache/rigify-smoke
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The last command runs in a Python environment with `bpy==5.2.2` and the matching
|
|
232
|
+
compiled DanceRudiments extension. It builds an actual Rigify basic human,
|
|
233
|
+
captures synthetic movement, compiles beat samples, drives a weighted test mesh,
|
|
234
|
+
checks fractional capture and selector handling, bakes variable-tempo native
|
|
235
|
+
motion, reopens saved apply/bake outputs in fresh Blender processes to verify
|
|
236
|
+
their control values and skin deformation, and verifies the contextual glue
|
|
237
|
+
sample. It uses no third-party motion.
|
|
238
|
+
|
|
239
|
+
## Primary references
|
|
240
|
+
|
|
241
|
+
- [Pinned Blender 5.2.2 Rigify source](https://github.com/blender/blender/tree/v5.2.2/scripts/addons_core/rigify).
|
|
242
|
+
- [Rigify component model and generation stages](https://developer.blender.org/docs/features/animation/rigify/).
|
|
243
|
+
- [Blender Rigify rig types](https://docs.blender.org/manual/en/latest/addons/rigify/rig_types/index.html).
|
|
244
|
+
- [Blender Rigify metarig construction](https://docs.blender.org/manual/en/latest/addons/rigify/metarigs.html).
|
|
245
|
+
- [Blender PoseBone API](https://docs.blender.org/api/current/bpy.types.PoseBone.html).
|
|
246
|
+
- [Official bpy distribution](https://pypi.org/project/bpy/5.2.2/).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Inspect one selected package and sample only the chosen clip natively."""
|
|
2
|
+
import argparse
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'python'))
|
|
6
|
+
from dancerudiments_authoring import MotionPackage, load_profile
|
|
7
|
+
|
|
8
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
9
|
+
parser.add_argument('package', type=Path)
|
|
10
|
+
parser.add_argument('--clip')
|
|
11
|
+
parser.add_argument('--profile', type=Path, help='Exact profile saved with the target scene')
|
|
12
|
+
args = parser.parse_args()
|
|
13
|
+
profile = load_profile(args.profile) if args.profile else None
|
|
14
|
+
with MotionPackage(args.package, expected_profile=profile) as package:
|
|
15
|
+
for clip in package.catalogue():
|
|
16
|
+
print(clip['id'], clip['start_pip'], clip['sample_count'], clip['byte_length'])
|
|
17
|
+
if args.clip:
|
|
18
|
+
motion = package.load(args.clip)
|
|
19
|
+
player = motion.player()
|
|
20
|
+
print('type:', motion.profile.type_uri)
|
|
21
|
+
print('first native pose by numbered channel:', player.by_channel(player.start_pip))
|