@rig-c/pose 0.1.1 → 0.2.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/README.md +7 -0
- package/cli.ts +11 -2
- package/package.json +1 -1
- package/src/retarget.ts +127 -1
- package/src/run.ts +5 -3
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Nothing is written until every check passes:
|
|
|
29
29
|
and what the run cannot decide for you it lists:
|
|
30
30
|
|
|
31
31
|
- `HOLD` — a joint missing, or scored under `--min-score`, holds its last good value; one line per span, with the frames and the reason.
|
|
32
|
+
- `FLIP` — a segment that turns more than `--flip` degrees in one frame (with `--flip <deg>`; off by default) is spread over the frames around the jump; one line per spread, with the chain, the segment, the jump, the frames and h. Below.
|
|
32
33
|
- `BEND` — with `--bends clamp`, a hinge bent the wrong way is straightened on that frame instead of refusing.
|
|
33
34
|
- `FOLD` — `rigc render --folds` over the new animation: every mesh that folds, and when. A reading, not a gate.
|
|
34
35
|
- `POSTURE` — when the map has a pelvis chain (joints `r_hip`, `l_hip`) and a `translate`: the hip line's tilt range, and the sway — the x-range of mid_hip − mid_shoulder — in rig units and in shoulder widths, the pose's beside the posed rig's. A reading, not a gate.
|
|
@@ -37,6 +38,12 @@ and what the run cannot decide for you it lists:
|
|
|
37
38
|
|
|
38
39
|
**Use `--smooth 7` at 24 fps.** It was measured on a 13 s dance on a @rig-c/parts rig, read back from the gated build: the motion's timing did not move (speed cross-correlation lag 0 on the wrists, ankles and hip), the frame-to-frame second difference dropped 2.5× on the wrists, 1.7–2.1× on the ankles and 1.5× on the hip, and the hip's 95th-percentile speed fell to 0.85×. The criterion set beforehand, a 2× drop on every point, was not met on the hip. That criterion was flawed: the hip's second difference is mostly real motion, while the filter cuts white noise 4.6×. A measure that isolates noise is a later card, and the default stays off until it passes.
|
|
39
40
|
|
|
41
|
+
**The flip guard.** A pose model can put a fast sweep of a limb into one frame — under motion blur it takes the elbow or the wrist for another point — and the rig then whips the limb round between two keys. Position smoothing cannot remove a step like that. So after holds and smoothing, and before the hinge check, every segment the map aims (not a `reach`, which is aimed later from posed positions) is read frame to frame: a turn greater than `--flip <deg>` from frame k−1 to frame k is spread. For the smallest h ≥ 1 that keeps every one-frame turn at or under the threshold, frames k−h … k+h−1 are replaced by the straight line from frame k−h−1 to frame k+h, the turn's own way round. It is centred, so the step happens when the pose put it, over 2h frames, and both ends keep the pose's angles. A window that would cross the first or last frame stops there and the line says so; two windows that overlap become one. `--flip off` keeps every turn. The report lists each spread under `flips`.
|
|
42
|
+
|
|
43
|
+
The threshold was fixed by measuring first ([#15](https://github.com/rig-c/pose/issues/15)). On the 13 s dance (313 frames, RTMPose), the one-frame turns of the picture angle of 10 segments — both upper arms, forearms, thighs and shins, the eye line and the hip line, 3,120 turns — run p50 1.0°, p90 5.0°, p99 23.1°, p99.5 33.4°, p99.9 78.2°, max 148.6°. 90° is the p99.9 rounded up past a margin, and marks the 3 turns above it (0.1%): both forearms between frames 181 and 182 (148.6° and 114.4°, a motion-blurred stretch) and the left forearm between 53 and 54 (99.2°, the forearm pointing at the camera). None of them is a spike that comes back: each keeps the direction the limb was already turning, and the new angle holds — a real sweep put into one frame. Holding the last angle would be wrong a frame later, so the guard spreads the step instead. With it, on the @rig-c/parts rig #11 measured, the largest one-frame turn of any guarded bone in the gated build, posed by rig-c, is 78.2° (148.6° without it), and every bone still lands.
|
|
44
|
+
|
|
45
|
+
Re-measured with the guard on, against the guarded unsmoothed run, on #11's criteria (power above 6 Hz down ≥ 2× on every point, timing lag 0, p95 speed ≥ 0.85×): no window passes. `--smooth 7` comes closest — the left wrist's power drops 1.99× and the hip's p95 speed is 0.847× — so `--smooth` stays off by default, and so does the guard: the rule posted before the run made a default for either one depend on a window passing. **Use `--flip 90`**: with it, no bone of the gated build turns more than 90° in one frame (without it, 148.6°), and every bone still lands.
|
|
46
|
+
|
|
40
47
|
Exit codes: `0` written, `1` refused (every reason printed, nothing written), `2` bad invocation. `rig-pose --help` has every flag.
|
|
41
48
|
|
|
42
49
|
## The pose: `rig-pose-sequence/1`
|
package/cli.ts
CHANGED
|
@@ -35,13 +35,16 @@ retarget
|
|
|
35
35
|
--smooth <n> Savitzky–Golay over n frames of joint positions: a centred least-squares cubic,
|
|
36
36
|
read at each frame (default: 1, none); n is odd and ≥ 5 — a cubic through 3
|
|
37
37
|
points passes through all three, so 3 would change nothing
|
|
38
|
+
--flip <deg>|off a segment that turns more than <deg> degrees in one frame of the pose is
|
|
39
|
+
spread over the frames around the jump, centred on it, and listed as FLIP
|
|
40
|
+
(default: off, every turn kept as the pose gives it; 90 is the measured setting)
|
|
38
41
|
--bends refuse|clamp a hinge (elbow, knee: any joint inside a chain) bent the wrong way refuses
|
|
39
42
|
the run (default), or is straightened on that frame and listed
|
|
40
43
|
--bend-tolerance <d> degrees past straight a hinge may bend the wrong way before it counts (default: 0)
|
|
41
44
|
--profile spine|spine-html rigc's gate profile (default: spine, rigc's own default)
|
|
42
45
|
--build <dir> keep the gated build (skeleton.json, .atlas, .model.json) here
|
|
43
46
|
--render <dir> keep rigc's render of the animation (frames, contact.png, folds.json) here
|
|
44
|
-
--report <file> write the run's measurements (holds, bends, landings, folds) as JSON
|
|
47
|
+
--report <file> write the run's measurements (holds, flips, bends, landings, folds) as JSON
|
|
45
48
|
|
|
46
49
|
It writes nothing until rigc's gate is green with the animation in, every mapped bone lands on
|
|
47
50
|
the angle the pose asks for (posed by rig-c's own poser), and no hinge bends the wrong way.
|
|
@@ -96,8 +99,13 @@ function smoothOf(f: Map<string, string>): number {
|
|
|
96
99
|
return n;
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
function flipOf(f: Map<string, string>): number | 'off' {
|
|
103
|
+
if (!f.has('flip') || f.get('flip') === 'off') return 'off';
|
|
104
|
+
return num(f, 'flip', 90, (v) => v > 0 && v < 180, 'off, or degrees in (0, 180) — a one-frame turn is never more than 180°');
|
|
105
|
+
}
|
|
106
|
+
|
|
99
107
|
function retargetArgs(argv: string[]): RetargetArgs {
|
|
100
|
-
const f = parseFlags(argv, ['rig', 'map', 'pose', 'out', 'name', 'person', 'base-motion', 'min-score', 'smooth', 'bends', 'bend-tolerance', 'profile', 'build', 'render', 'report'], ['coco']);
|
|
108
|
+
const f = parseFlags(argv, ['rig', 'map', 'pose', 'out', 'name', 'person', 'base-motion', 'min-score', 'smooth', 'flip', 'bends', 'bend-tolerance', 'profile', 'build', 'render', 'report'], ['coco']);
|
|
101
109
|
const bends = f.get('bends') ?? 'refuse';
|
|
102
110
|
if (bends !== 'refuse' && bends !== 'clamp') throw new Usage(`--bends is "${bends}"; it must be refuse or clamp`);
|
|
103
111
|
const profile = f.get('profile') ?? 'spine';
|
|
@@ -115,6 +123,7 @@ function retargetArgs(argv: string[]): RetargetArgs {
|
|
|
115
123
|
baseMotion: f.get('base-motion'),
|
|
116
124
|
minScore: num(f, 'min-score', 0.3, (n) => n >= 0, 'a number ≥ 0'),
|
|
117
125
|
smooth: smoothOf(f),
|
|
126
|
+
flip: flipOf(f),
|
|
118
127
|
bends,
|
|
119
128
|
bendTolerance: num(f, 'bend-tolerance', 0, (n) => n >= 0 && n < 180, 'degrees in [0, 180)'),
|
|
120
129
|
profile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/pose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Puts motion from a body-18 pose sequence onto a rig built with rig-c: bone keys in rig-c's motion format, checked through rig-c's gate before anything is written. A CLI for agents that cannot see the rig.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/retarget.ts
CHANGED
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
* evaluated at the frame, on each joint's positions after its holds. Centred,
|
|
24
24
|
* it moves no motion in time; a cubic keeps a peak a moving average flattens.
|
|
25
25
|
*
|
|
26
|
+
* The flip guard (rig-c/pose#15) then spreads a segment's turn greater than
|
|
27
|
+
* `flip` degrees in one frame over the frames around it, linearly and centred on
|
|
28
|
+
* the jump (`spreadFlips`). It reads the mapped segments' target angles after
|
|
29
|
+
* holds and smoothing and before the hinge check. A reach's target is aimed
|
|
30
|
+
* later, from posed positions, and is not guarded.
|
|
31
|
+
*
|
|
26
32
|
* What this cannot know is held to account afterwards: `verify.ts` poses the
|
|
27
33
|
* gated build with rig-c's poser and measures where each bone landed.
|
|
28
34
|
*/
|
|
@@ -45,6 +51,8 @@ export interface RetargetOptions {
|
|
|
45
51
|
bends: 'refuse' | 'clamp';
|
|
46
52
|
/** Degrees past straight a hinge may bend the wrong way before it counts. */
|
|
47
53
|
bendTolerance: number;
|
|
54
|
+
/** A segment turning more than this many degrees in one frame is spread (`spreadFlips`); 'off' keeps every turn. */
|
|
55
|
+
flip: number | 'off';
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
export interface Hold {
|
|
@@ -69,6 +77,26 @@ export interface BendEvent {
|
|
|
69
77
|
rest: number;
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
/** One spread: a segment's turn over the threshold, and the frames whose angle was replaced. */
|
|
81
|
+
export interface Flip {
|
|
82
|
+
chain: string;
|
|
83
|
+
bone: string;
|
|
84
|
+
/** The segment the bone carries: from joint, to joint. */
|
|
85
|
+
joints: [string, string];
|
|
86
|
+
/** The largest input turn in the spread, degrees, signed (Spine's sense, counter-clockwise +). */
|
|
87
|
+
jump: number;
|
|
88
|
+
/** Every input turn over the threshold the spread covers: `frame` is k, the turn is from frame k−1 to k. */
|
|
89
|
+
turns: Array<{ frame: number; jump: number }>;
|
|
90
|
+
/** First and last frame whose angle was replaced; the frames either side keep the input's. */
|
|
91
|
+
frames: [number, number];
|
|
92
|
+
/** The half-width the spread needed (for spreads merged from neighbouring turns, the widest). */
|
|
93
|
+
h: number;
|
|
94
|
+
/** The one-frame turn across the spread, degrees, signed. */
|
|
95
|
+
step: number;
|
|
96
|
+
/** Which end of the series cut the window short, if one did. */
|
|
97
|
+
clipped: 'start' | 'end' | 'both' | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
72
100
|
export interface MotionKey {
|
|
73
101
|
t: number;
|
|
74
102
|
v: number[];
|
|
@@ -93,6 +121,7 @@ export interface Retarget {
|
|
|
93
121
|
origin: { bone: string; at: Array<[number, number]> } | null;
|
|
94
122
|
holds: Hold[];
|
|
95
123
|
bends: BendEvent[];
|
|
124
|
+
flips: Flip[];
|
|
96
125
|
/** The figure scale, rig units per picture pixel, and the lengths it came from. */
|
|
97
126
|
scale: { factor: number; pose: number; rig: number } | null;
|
|
98
127
|
}
|
|
@@ -329,6 +358,20 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
329
358
|
}
|
|
330
359
|
p.throwIfAny();
|
|
331
360
|
|
|
361
|
+
// The flip guard, on every segment the pose aims directly (not a reach).
|
|
362
|
+
const flips: Flip[] = [];
|
|
363
|
+
if (opts.flip !== 'off') {
|
|
364
|
+
const threshold = opts.flip;
|
|
365
|
+
for (const c of map.chains) {
|
|
366
|
+
if (c.reach) continue;
|
|
367
|
+
c.bones.forEach((b, k) => {
|
|
368
|
+
for (const s of spreadFlips(targets.get(b)!, threshold)) {
|
|
369
|
+
flips.push({ chain: c.name, bone: b, joints: [c.joints[k]!, c.joints[k + 1]!], ...s });
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
332
375
|
// Hinges: each joint strictly inside a chain (a reach has one bone, so none).
|
|
333
376
|
const bends: BendEvent[] = [];
|
|
334
377
|
for (const c of map.chains) {
|
|
@@ -409,7 +452,90 @@ export function retarget(rest: Rest, map: JointMap, frames: ReadonlyArray<Partia
|
|
|
409
452
|
}
|
|
410
453
|
if (translateTrack !== null) tracks.push(translateTrack);
|
|
411
454
|
|
|
412
|
-
return { animation: { duration: keyTimes[n - 1]!, tracks }, times: keyTimes, targets, origin, holds, bends, scale };
|
|
455
|
+
return { animation: { duration: keyTimes[n - 1]!, tracks }, times: keyTimes, targets, origin, holds, bends, flips, scale };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
type Spread = Omit<Flip, 'chain' | 'bone' | 'joints'>;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* The flip guard on one angle series (degrees), in place. A turn greater than
|
|
462
|
+
* `threshold` from frame k−1 to frame k is spread: for the smallest h ≥ 1, the
|
|
463
|
+
* angles of frames k−h…k+h−1 are replaced by the straight line from frame
|
|
464
|
+
* k−h−1 to frame k+h, taken through the unwrapped series (each one-frame turn
|
|
465
|
+
* the short way round, so the jump keeps its own direction), such that every
|
|
466
|
+
* one-frame turn in frames k−h…k+h is at or under the threshold. It is centred
|
|
467
|
+
* on the jump, so the step happens when the input put it, spread over 2h frames,
|
|
468
|
+
* and both ends are the input's own angles. A window that would cross the first
|
|
469
|
+
* or the last frame stops there (`clipped`). Windows that overlap — one replaces
|
|
470
|
+
* a frame the other ends on — merge into one line over both, widened again
|
|
471
|
+
* while that line still turns faster than the threshold. Returns the spreads.
|
|
472
|
+
*/
|
|
473
|
+
export function spreadFlips(angles: number[], threshold: number): Spread[] {
|
|
474
|
+
const n = angles.length;
|
|
475
|
+
if (n < 2) return [];
|
|
476
|
+
const u: number[] = [angles[0]!];
|
|
477
|
+
for (let i = 1; i < n; i++) u.push(u[i - 1]! + wrap(angles[i]! - angles[i - 1]!));
|
|
478
|
+
const fits = (lo: number, hi: number) => Math.abs(u[hi]! - u[lo]!) <= threshold * (hi - lo);
|
|
479
|
+
interface Win { lo: number; hi: number; h: number; start: boolean; end: boolean; turns: Array<{ frame: number; jump: number }> }
|
|
480
|
+
const wins: Win[] = [];
|
|
481
|
+
for (let k = 1; k < n; k++) {
|
|
482
|
+
const jump = u[k]! - u[k - 1]!;
|
|
483
|
+
if (Math.abs(jump) <= threshold) continue;
|
|
484
|
+
let h = 1;
|
|
485
|
+
let lo = Math.max(0, k - h - 1);
|
|
486
|
+
let hi = Math.min(n - 1, k + h);
|
|
487
|
+
while (!fits(lo, hi) && (lo > 0 || hi < n - 1)) {
|
|
488
|
+
h++;
|
|
489
|
+
lo = Math.max(0, k - h - 1);
|
|
490
|
+
hi = Math.min(n - 1, k + h);
|
|
491
|
+
}
|
|
492
|
+
wins.push({ lo, hi, h, start: k - h - 1 < 0, end: k + h > n - 1, turns: [{ frame: k, jump }] });
|
|
493
|
+
}
|
|
494
|
+
// Merge overlapping windows; a merged line that turns too fast widens by one on each side, then merges again.
|
|
495
|
+
for (let changed = true; changed;) {
|
|
496
|
+
changed = false;
|
|
497
|
+
wins.sort((a, b) => a.lo - b.lo);
|
|
498
|
+
for (let i = 0; i + 1 < wins.length; i++) {
|
|
499
|
+
const a = wins[i]!;
|
|
500
|
+
const b = wins[i + 1]!;
|
|
501
|
+
if (b.lo >= a.hi) continue;
|
|
502
|
+
wins.splice(i, 2, { lo: a.lo, hi: Math.max(a.hi, b.hi), h: Math.max(a.h, b.h), start: a.start || b.start, end: a.end || b.end, turns: [...a.turns, ...b.turns].sort((x, y) => x.frame - y.frame) });
|
|
503
|
+
changed = true;
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
if (changed) continue;
|
|
507
|
+
for (const w of wins) {
|
|
508
|
+
if (w.turns.length < 2 || fits(w.lo, w.hi) || (w.lo === 0 && w.hi === n - 1)) continue;
|
|
509
|
+
if (w.lo === 0) w.start = true;
|
|
510
|
+
if (w.hi === n - 1) w.end = true;
|
|
511
|
+
w.lo = Math.max(0, w.lo - 1);
|
|
512
|
+
w.hi = Math.min(n - 1, w.hi + 1);
|
|
513
|
+
w.h++;
|
|
514
|
+
changed = true;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return wins.map((w) => {
|
|
518
|
+
const step = (u[w.hi]! - u[w.lo]!) / (w.hi - w.lo);
|
|
519
|
+
for (let i = w.lo + 1; i < w.hi; i++) angles[i] = wrap(u[w.lo]! + step * (i - w.lo));
|
|
520
|
+
const top = w.turns.reduce((m, t) => (Math.abs(t.jump) > Math.abs(m.jump) ? t : m));
|
|
521
|
+
return {
|
|
522
|
+
jump: top.jump,
|
|
523
|
+
turns: w.turns,
|
|
524
|
+
frames: [w.lo + 1, w.hi - 1] as [number, number],
|
|
525
|
+
h: w.h,
|
|
526
|
+
step,
|
|
527
|
+
clipped: w.start && w.end ? 'both' : w.start ? 'start' : w.end ? 'end' : null,
|
|
528
|
+
};
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export function flipLine(f: Flip, times: readonly number[], threshold: number): string {
|
|
533
|
+
const sg = (v: number) => `${v >= 0 ? '+' : '−'}${Math.abs(v).toFixed(1)}°`;
|
|
534
|
+
const turns = f.turns.map((t) => `${sg(t.jump)} from frame ${t.frame - 1} to ${t.frame} (t=${times[t.frame - 1]!.toFixed(3)}–${times[t.frame]!.toFixed(3)}s)`).join(', ');
|
|
535
|
+
const span = f.frames[0] > f.frames[1] ? 'no frame (the pose has none between the two)' : f.frames[0] === f.frames[1] ? `frame ${f.frames[0]}` : `frames ${f.frames[0]}–${f.frames[1]}`;
|
|
536
|
+
const clip = f.clipped === null ? '' : f.clipped === 'both' ? '; the window was cut short by both ends of the pose' : `; the window was cut short at the ${f.clipped === 'start' ? 'first' : 'last'} frame of the pose`;
|
|
537
|
+
const still = Math.abs(f.step) > threshold ? ` — still over ${threshold}°: the pose is too short to spread it` : '';
|
|
538
|
+
return `chain "${f.chain}" bone "${f.bone}" (${f.joints[0]} → ${f.joints[1]}) turned ${turns}, over ${threshold}° in one frame; spread over ${span} (h=${f.h}), ${sg(f.step)} per frame${clip}${still}`;
|
|
413
539
|
}
|
|
414
540
|
|
|
415
541
|
/** Frames before the first with a direction take its value; none at all is a problem. */
|
package/src/run.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { dirname, join } from 'node:path';
|
|
|
9
9
|
import { readMap, resolveMap } from './map.ts';
|
|
10
10
|
import { convertCoco17, personTrack, readPoseSequence } from './pose.ts';
|
|
11
11
|
import { lineOf, Problems, Refusal, type Problem } from './problems.ts';
|
|
12
|
-
import { bendLine, holdLine, retarget, type RetargetOptions } from './retarget.ts';
|
|
12
|
+
import { bendLine, flipLine, holdLine, retarget, type RetargetOptions } from './retarget.ts';
|
|
13
13
|
import { baseMotionOf, folds, gate, readBuild, resolveRig, restOf } from './rigc.ts';
|
|
14
14
|
import { landed, measureLanding, measurePosture, poseFrames, postureLines, ANGLE_TOLERANCE, ORIGIN_TOLERANCE } from './verify.ts';
|
|
15
15
|
|
|
@@ -78,9 +78,10 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
78
78
|
resolveMap(map, rest, a.map);
|
|
79
79
|
|
|
80
80
|
const r = retarget(rest, map, frames, times, a);
|
|
81
|
-
say(`rig-pose retarget: ${seq.frames.length} frame(s), ${r.animation.duration.toFixed(3)}s, onto "${input.name}" as "${a.name}" — ${r.animation.tracks.length} track(s); min-score ${a.minScore}, smooth ${a.smooth}, bends ${a.bends} (tolerance ${a.bendTolerance}°)`);
|
|
81
|
+
say(`rig-pose retarget: ${seq.frames.length} frame(s), ${r.animation.duration.toFixed(3)}s, onto "${input.name}" as "${a.name}" — ${r.animation.tracks.length} track(s); min-score ${a.minScore}, smooth ${a.smooth}, flip ${a.flip === 'off' ? 'off' : `${a.flip}°`}, bends ${a.bends} (tolerance ${a.bendTolerance}°)`);
|
|
82
82
|
if (r.scale !== null) say(` SCALE ${r.scale.factor.toFixed(5)} rig units per picture px (rig ${r.scale.rig.toFixed(2)} / pose median ${r.scale.pose.toFixed(2)})`);
|
|
83
83
|
for (const h of r.holds) say(` HOLD ${holdLine(h, r.times)}`);
|
|
84
|
+
if (a.flip !== 'off') for (const fl of r.flips) say(` FLIP ${flipLine(fl, r.times, a.flip)}`);
|
|
84
85
|
for (const e of r.bends) say(` BEND ${bendLine(e)} — clamped straight`);
|
|
85
86
|
|
|
86
87
|
const motion = { ...base, animations: { ...animations, [a.name]: r.animation } };
|
|
@@ -129,10 +130,11 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
|
|
|
129
130
|
animation: a.name,
|
|
130
131
|
frames: seq.frames.length,
|
|
131
132
|
duration: r.animation.duration,
|
|
132
|
-
options: { minScore: a.minScore, smooth: a.smooth, bends: a.bends, bendTolerance: a.bendTolerance, profile: a.profile },
|
|
133
|
+
options: { minScore: a.minScore, smooth: a.smooth, flip: a.flip, bends: a.bends, bendTolerance: a.bendTolerance, profile: a.profile },
|
|
133
134
|
scale: r.scale,
|
|
134
135
|
holds: r.holds,
|
|
135
136
|
bends: r.bends,
|
|
137
|
+
flips: r.flips,
|
|
136
138
|
gate: g1.summary,
|
|
137
139
|
landings,
|
|
138
140
|
posture,
|