@pixerate/editor 0.62.0 → 0.63.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/THIRD_PARTY_NOTICES.md +1 -0
- package/dist/animation-editor/index.cjs +156 -33
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +64 -3
- package/dist/animation-editor/index.d.ts +64 -3
- package/dist/animation-editor/index.js +131 -8
- package/dist/animation-editor/index.js.map +1 -1
- package/package.json +1 -1
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -185,6 +185,7 @@ GridCraft. Apache POI is © The Apache Software Foundation, licensed under the A
|
|
|
185
185
|
|
|
186
186
|
- `crates/engine/src/lib.rs` (`Engine::execute_merged`, `hold_merge`, `end_merge`) → `packages/core/src/history/HistoryManager.ts`
|
|
187
187
|
- `crates/engine/src/lib.rs` (`Engine::is_dirty`, `same_document`, `mark_clean`) → `packages/core/src/dirty.ts`
|
|
188
|
+
- `crates/time/src/tempo.rs` and `grid.rs` (beats from a tempo and an offset, counted into bars by the meter) → `motionBeatTime` and `motionBeatAt` in `packages/core/src/animation-editor/compile.ts`; one steady tempo instead of a tempo map, with shots cued to beats instead of positions snapped to a grid
|
|
188
189
|
|
|
189
190
|
## vectorcraft
|
|
190
191
|
|
|
@@ -149,6 +149,8 @@ var MOTION_ISSUE_CODES = {
|
|
|
149
149
|
"out-of-frame": { severity: "warning", wording: "A node a command animates ends up outside the camera." },
|
|
150
150
|
"too-fast": { severity: "warning", wording: "A change is too quick to follow." },
|
|
151
151
|
"spring-cut-short": { severity: "warning", wording: "A spring is given a duration shorter than it takes to settle, so it jumps to its end." },
|
|
152
|
+
"unknown-cue": { severity: "error", wording: "A shot is cued to a beat with no beat grid, or to a word the narration does not have." },
|
|
153
|
+
"cue-missed": { severity: "warning", wording: "A shot starts after its cue, because the shot before runs past it." },
|
|
152
154
|
"scene-too-long": { severity: "warning", wording: "The scene runs past its target length." },
|
|
153
155
|
"scene-too-short": { severity: "warning", wording: "The scene ends before its target length." },
|
|
154
156
|
"unknown-control-color": { severity: "warning", wording: "A control stands for a colour the scene never uses." },
|
|
@@ -206,8 +208,8 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
|
|
|
206
208
|
this.problems = problems;
|
|
207
209
|
}
|
|
208
210
|
};
|
|
209
|
-
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "target", "controls", "presets"];
|
|
210
|
-
var SHOT_KEYS = ["id", "label", "camera", "run", "hold", "speed"];
|
|
211
|
+
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
|
|
212
|
+
var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
|
|
211
213
|
var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
|
|
212
214
|
var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
|
|
213
215
|
var COMMAND_KEYS = {
|
|
@@ -361,6 +363,16 @@ function shotShapeProblems(shot, where) {
|
|
|
361
363
|
if (shot.label !== void 0 && typeof shot.label !== "string") problems.push(`${name}: "label" must be a string.`);
|
|
362
364
|
if (shot.hold !== void 0 && !(isNum(shot.hold) && shot.hold >= 0)) problems.push(`${name}: "hold" must be a number of milliseconds \u2265 0.`);
|
|
363
365
|
if (shot.speed !== void 0 && !(isNum(shot.speed) && shot.speed >= 0.05 && shot.speed <= 20)) problems.push(`${name}: "speed" must be a number from 0.05 to 20 (2 plays it twice as fast).`);
|
|
366
|
+
if (shot.at !== void 0) {
|
|
367
|
+
const at = shot.at;
|
|
368
|
+
const given = isObject(at) ? ["beat", "word", "ms"].filter((k) => at[k] !== void 0) : [];
|
|
369
|
+
if (!isObject(at) || given.length !== 1) problems.push(`${name}: "at" must give one of { "beat": number }, { "word": index } or { "ms": number }.`);
|
|
370
|
+
else {
|
|
371
|
+
unknownKeys(at, ["beat", "word", "ms"], `${name} at`, problems);
|
|
372
|
+
const k = given[0];
|
|
373
|
+
if (!isNum(at[k]) || at[k] < 0 || k === "word" && !Number.isInteger(at[k])) problems.push(`${name}: "at.${k}" must be ${k === "word" ? "a word index \u2265 0" : "a number \u2265 0"}.`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
364
376
|
if (shot.camera !== void 0) {
|
|
365
377
|
const c = shot.camera;
|
|
366
378
|
if (!isObject(c)) problems.push(`${name}: "camera" must be an object.`);
|
|
@@ -391,6 +403,16 @@ function motionSceneShapeProblems(input) {
|
|
|
391
403
|
problems.push('Scene: "artwork" must be a vector document { width, height, nodes }.');
|
|
392
404
|
if (input.loop !== void 0 && !["none", "loop", "ping-pong"].includes(input.loop)) problems.push('Scene: "loop" must be "none", "loop" or "ping-pong".');
|
|
393
405
|
if (input.fps !== void 0 && !(isNum(input.fps) && input.fps > 0 && input.fps <= 120)) problems.push('Scene: "fps" must be a number from 1 to 120.');
|
|
406
|
+
if (input.beat !== void 0) {
|
|
407
|
+
const b = input.beat;
|
|
408
|
+
if (!isObject(b) || !(isNum(b.bpm) && b.bpm >= 1 && b.bpm <= 1e3) || b.offset !== void 0 && !isNum(b.offset) || b.meter !== void 0 && !(Number.isInteger(b.meter) && b.meter >= 1))
|
|
409
|
+
problems.push('Scene: "beat" must be { "bpm": 1\u20131000, "offset"?: ms, "meter"?: beats a bar }.');
|
|
410
|
+
}
|
|
411
|
+
if (input.narration !== void 0) {
|
|
412
|
+
const n = input.narration;
|
|
413
|
+
if (!isObject(n) || !Array.isArray(n.words) || !n.words.every((w, i, all) => isObject(w) && typeof w.text === "string" && isNum(w.start) && w.start >= 0 && (w.end === void 0 || isNum(w.end) && w.end >= w.start) && (i === 0 || w.start >= (_nullishCoalesce(all[i - 1].start, () => ( 0))))))
|
|
414
|
+
problems.push('Scene: "narration" must be { "words": [{ "text", "start": ms, "end"?: ms }, \u2026] } in time order.');
|
|
415
|
+
}
|
|
394
416
|
if (input.steps !== void 0 && !(isNum(input.steps) && input.steps > 0 && input.steps <= 120)) problems.push('Scene: "steps" must be frames per second from above 0 to 120.');
|
|
395
417
|
if (input.target !== void 0 && !(Array.isArray(input.target) && input.target.length === 2 && input.target.every(isNum) && input.target[0] <= input.target[1]))
|
|
396
418
|
problems.push('Scene: "target" must be [min, max] seconds.');
|
|
@@ -699,6 +721,21 @@ function motionPathLength(node, parentWorld = _chunkYJLCBA7Vcjs.IDENTITY_MATRIX)
|
|
|
699
721
|
function motionShotTimeScale(shot) {
|
|
700
722
|
return typeof shot.speed === "number" && shot.speed > 0 && Number.isFinite(shot.speed) ? 1 / shot.speed : 1;
|
|
701
723
|
}
|
|
724
|
+
function motionBeatTime(grid, beat) {
|
|
725
|
+
return (_nullishCoalesce(grid.offset, () => ( 0))) + beat * 6e4 / grid.bpm;
|
|
726
|
+
}
|
|
727
|
+
function motionBeatAt(grid, ms) {
|
|
728
|
+
const beat = (ms - (_nullishCoalesce(grid.offset, () => ( 0)))) * grid.bpm / 6e4;
|
|
729
|
+
const meter = Math.max(1, Math.round(_nullishCoalesce(grid.meter, () => ( 4))));
|
|
730
|
+
const whole = Math.floor(beat + 1e-9);
|
|
731
|
+
return { beat, bar: Math.floor(whole / meter) + 1, beatInBar: (whole % meter + meter) % meter + 1 };
|
|
732
|
+
}
|
|
733
|
+
function motionCueTime(scene, cue) {
|
|
734
|
+
if (typeof cue.ms === "number") return Math.max(0, cue.ms);
|
|
735
|
+
if (typeof cue.beat === "number") return scene.beat && scene.beat.bpm > 0 ? Math.max(0, motionBeatTime(scene.beat, cue.beat)) : void 0;
|
|
736
|
+
if (typeof cue.word === "number") return _optionalChain([scene, 'access', _18 => _18.narration, 'optionalAccess', _19 => _19.words, 'access', _20 => _20[cue.word], 'optionalAccess', _21 => _21.start]);
|
|
737
|
+
return void 0;
|
|
738
|
+
}
|
|
702
739
|
function motionCommandRepeat(command2) {
|
|
703
740
|
if (command2.type === "wait" || command2.type === "enter" || command2.type === "exit") return 1;
|
|
704
741
|
const n = _nullishCoalesce(command2.repeat, () => ( 1));
|
|
@@ -736,6 +773,12 @@ function compileMotionScene(scene) {
|
|
|
736
773
|
const cameraMoves = [];
|
|
737
774
|
let t = 0;
|
|
738
775
|
scene.shots.forEach((shot, index) => {
|
|
776
|
+
const cue = shot.at ? motionCueTime(scene, shot.at) : void 0;
|
|
777
|
+
if (cue !== void 0 && cue > t) {
|
|
778
|
+
if (shots.length) shots[shots.length - 1].end = cue;
|
|
779
|
+
t = cue;
|
|
780
|
+
}
|
|
781
|
+
const missedBy = cue !== void 0 && t - cue > 0.5 ? t - cue : void 0;
|
|
739
782
|
const start = t;
|
|
740
783
|
let cursor = start;
|
|
741
784
|
const f = motionShotTimeScale(shot);
|
|
@@ -762,7 +805,7 @@ function compileMotionScene(scene) {
|
|
|
762
805
|
runEnd = Math.max(runEnd, end2);
|
|
763
806
|
});
|
|
764
807
|
const end = runEnd + (_nullishCoalesce(shot.hold, () => ( MOTION_DEFAULT_HOLD))) * f;
|
|
765
|
-
shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end });
|
|
808
|
+
shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end, ...cue !== void 0 ? { cue } : {}, ...missedBy !== void 0 ? { missedBy } : {} });
|
|
766
809
|
t = end;
|
|
767
810
|
});
|
|
768
811
|
const tracks = /* @__PURE__ */ new Map();
|
|
@@ -842,7 +885,7 @@ function compileMotionScene(scene) {
|
|
|
842
885
|
const style = _nullishCoalesce(command2.style, () => ( "fade"));
|
|
843
886
|
const opacity = rest("opacity");
|
|
844
887
|
const reveal = style === "wipe" ? motionRevealClip(entry, { style, direction: command2.direction }) : style === "iris" ? motionRevealClip(entry, { style, shape: command2.shape }) : style === "split" ? motionRevealClip(entry, { style, orientation: command2.orientation }) : null;
|
|
845
|
-
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess',
|
|
888
|
+
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _22 => _22.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
|
|
846
889
|
if (reveal) {
|
|
847
890
|
seg("opacity", opacity, "linear", start, start);
|
|
848
891
|
seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
|
|
@@ -904,8 +947,8 @@ function compileMotionScene(scene) {
|
|
|
904
947
|
}
|
|
905
948
|
case "move": {
|
|
906
949
|
const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
|
|
907
|
-
let dx = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
908
|
-
let dy = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
950
|
+
let dx = _nullishCoalesce(_optionalChain([command2, 'access', _23 => _23.by, 'optionalAccess', _24 => _24.x]), () => ( 0));
|
|
951
|
+
let dy = _nullishCoalesce(_optionalChain([command2, 'access', _25 => _25.by, 'optionalAccess', _26 => _26.y]), () => ( 0));
|
|
909
952
|
if (command2.to) {
|
|
910
953
|
const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
|
|
911
954
|
const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
|
|
@@ -1009,7 +1052,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
|
|
|
1009
1052
|
var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
|
|
1010
1053
|
var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
|
|
1011
1054
|
function applyMotionControlValues(scene, values) {
|
|
1012
|
-
if (!values || !_optionalChain([scene, 'access',
|
|
1055
|
+
if (!values || !_optionalChain([scene, 'access', _27 => _27.controls, 'optionalAccess', _28 => _28.length])) return scene;
|
|
1013
1056
|
const map = /* @__PURE__ */ new Map();
|
|
1014
1057
|
for (const control of scene.controls) {
|
|
1015
1058
|
const value = values[control.name];
|
|
@@ -1055,7 +1098,7 @@ function shapeIn(entry, shape) {
|
|
|
1055
1098
|
}
|
|
1056
1099
|
function shapeAt(entry, timeline, nodes, time) {
|
|
1057
1100
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
|
|
1058
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
1101
|
+
if (!_optionalChain([track, 'optionalAccess', _29 => _29.length])) return null;
|
|
1059
1102
|
const s = activeSegment(track, time);
|
|
1060
1103
|
const fromId = String(s ? s.from : track[0].from);
|
|
1061
1104
|
if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
|
|
@@ -1078,8 +1121,8 @@ function motionFollowRoute(entry, path) {
|
|
|
1078
1121
|
}
|
|
1079
1122
|
function followAt(entry, timeline, nodes, time) {
|
|
1080
1123
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
|
|
1081
|
-
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess',
|
|
1082
|
-
if (!_optionalChain([s, 'optionalAccess',
|
|
1124
|
+
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _30 => _30[0]])));
|
|
1125
|
+
if (!_optionalChain([s, 'optionalAccess', _31 => _31.path])) return null;
|
|
1083
1126
|
const route = motionFollowRoute(entry, nodes.get(s.path));
|
|
1084
1127
|
const progress = Number(trackValue(track, time));
|
|
1085
1128
|
if (!route || !(progress >= 0)) return null;
|
|
@@ -1139,7 +1182,7 @@ function motionFrameAt(scene, time, options = {}) {
|
|
|
1139
1182
|
if (out.visible === void 0) delete out.visible;
|
|
1140
1183
|
}
|
|
1141
1184
|
const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
|
|
1142
|
-
if (effectTracks.length && _optionalChain([out, 'access',
|
|
1185
|
+
if (effectTracks.length && _optionalChain([out, 'access', _32 => _32.effects, 'optionalAccess', _33 => _33.length])) {
|
|
1143
1186
|
out = {
|
|
1144
1187
|
...out,
|
|
1145
1188
|
effects: out.effects.map((effect) => {
|
|
@@ -1217,7 +1260,7 @@ function withViewBox(svg, box) {
|
|
|
1217
1260
|
// src/animation-editor/check.ts
|
|
1218
1261
|
var MOTION_MIN_CHANGE_MS = 120;
|
|
1219
1262
|
var NUMERIC_PARAMS = (node, effectId) => {
|
|
1220
|
-
const effect = _optionalChain([node, 'access',
|
|
1263
|
+
const effect = _optionalChain([node, 'access', _34 => _34.effects, 'optionalAccess', _35 => _35.find, 'call', _36 => _36((e) => e.id === effectId)]);
|
|
1221
1264
|
if (!effect) return null;
|
|
1222
1265
|
return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
|
|
1223
1266
|
};
|
|
@@ -1366,6 +1409,17 @@ function checkMotionScene(scene) {
|
|
|
1366
1409
|
}
|
|
1367
1410
|
});
|
|
1368
1411
|
});
|
|
1412
|
+
scene.shots.forEach((shot, i) => {
|
|
1413
|
+
if (!shot.at) return;
|
|
1414
|
+
const timing = timeline.shots[i];
|
|
1415
|
+
if (timing.cue === void 0) {
|
|
1416
|
+
const what = shot.at.beat !== void 0 ? `beat ${shot.at.beat}, but the scene has no beat grid; set one with set_animation_settings ({ "beat": { "bpm": \u2026 } })` : `word ${shot.at.word}, but the narration has ${_nullishCoalesce(_optionalChain([scene, 'access', _37 => _37.narration, 'optionalAccess', _38 => _38.words, 'access', _39 => _39.length]), () => ( 0))} words (indexes from 0)`;
|
|
1417
|
+
issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
|
|
1418
|
+
} else if (timing.missedBy !== void 0) {
|
|
1419
|
+
const late = Math.round(timing.missedBy);
|
|
1420
|
+
issues.push(motionIssue("cue-missed", `Shot "${shot.id}" is cued at ${Math.round(timing.cue)} ms but starts ${late} ms late, because the shot before runs until ${Math.round(timing.start)} ms; shorten that shot (its hold, durations or speed) or cue this one later.`, { shot: shot.id, measured: late, needed: 0, unit: "ms" }));
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1369
1423
|
for (const [key, track] of timeline.tracks) {
|
|
1370
1424
|
if (key.endsWith("\0add")) continue;
|
|
1371
1425
|
for (let i = 1; i < track.length; i++) {
|
|
@@ -1392,7 +1446,7 @@ function checkMotionScene(scene) {
|
|
|
1392
1446
|
const cam = frame.camera;
|
|
1393
1447
|
const docIndex = indexMotionNodes(frame.document.nodes);
|
|
1394
1448
|
for (const id of command2.targets) {
|
|
1395
|
-
const b = _optionalChain([docIndex, 'access',
|
|
1449
|
+
const b = _optionalChain([docIndex, 'access', _40 => _40.get, 'call', _41 => _41(id), 'optionalAccess', _42 => _42.bounds]);
|
|
1396
1450
|
if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
|
|
1397
1451
|
const outside = b.x + b.width < cam.x || b.y + b.height < cam.y || b.x > cam.x + cam.width || b.y > cam.y + cam.height;
|
|
1398
1452
|
if (outside) {
|
|
@@ -1467,6 +1521,14 @@ function applyOne(scene, edit) {
|
|
|
1467
1521
|
if (!(typeof edit.steps === "number" && edit.steps > 0 && edit.steps <= 120)) fail('"steps" must be frames per second from above 0 to 120 (12 is "on twos" at 24 fps), or null for smooth.');
|
|
1468
1522
|
next.steps = edit.steps;
|
|
1469
1523
|
}
|
|
1524
|
+
for (const key of ["beat", "narration"]) {
|
|
1525
|
+
if (edit[key] === null) delete next[key];
|
|
1526
|
+
else if (edit[key] !== void 0) next[key] = edit[key];
|
|
1527
|
+
}
|
|
1528
|
+
{
|
|
1529
|
+
const problems = motionSceneShapeProblems(next).filter((p) => /"beat"|"narration"/.test(p));
|
|
1530
|
+
if (problems.length) fail(problems.join(" "));
|
|
1531
|
+
}
|
|
1470
1532
|
if (edit.target === null) delete next.target;
|
|
1471
1533
|
else if (edit.target !== void 0) next.target = edit.target;
|
|
1472
1534
|
return next;
|
|
@@ -1574,9 +1636,9 @@ function applyMotionEdits(scene, edits) {
|
|
|
1574
1636
|
let next = scene;
|
|
1575
1637
|
for (let i = 0; i < list.length; i++) {
|
|
1576
1638
|
const edit = list[i];
|
|
1577
|
-
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess',
|
|
1639
|
+
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _43 => _43.op])}): ` : "";
|
|
1578
1640
|
if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
|
|
1579
|
-
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess',
|
|
1641
|
+
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _44 => _44.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
|
|
1580
1642
|
try {
|
|
1581
1643
|
next = applyOne(next, edit);
|
|
1582
1644
|
} catch (error) {
|
|
@@ -1736,7 +1798,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1736
1798
|
const wrapper = freshId(`${id}--follow`);
|
|
1737
1799
|
const centre = freshId(`${id}--follow-centre`);
|
|
1738
1800
|
const c = entry.center;
|
|
1739
|
-
const at = _optionalChain([motionNodeOffset, 'call',
|
|
1801
|
+
const at = _optionalChain([motionNodeOffset, 'call', _45 => _45(entry, timeline, 0, nodes), 'optionalAccess', _46 => _46.follow]);
|
|
1740
1802
|
const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
|
|
1741
1803
|
(wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
|
|
1742
1804
|
follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
|
|
@@ -1796,7 +1858,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1796
1858
|
exact: () => false,
|
|
1797
1859
|
value: (t) => {
|
|
1798
1860
|
const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
|
|
1799
|
-
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call',
|
|
1861
|
+
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _47 => _47(frame.document.nodes), 'access', _48 => _48.get, 'call', _49 => _49(id), 'optionalAccess', _50 => _50.node]));
|
|
1800
1862
|
if (!now || now.type !== "path") return "";
|
|
1801
1863
|
const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
|
|
1802
1864
|
return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
|
|
@@ -1817,8 +1879,8 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1817
1879
|
...delay > 0 ? { delay: round2(delay) } : {},
|
|
1818
1880
|
ms: round2(ms),
|
|
1819
1881
|
ease: written,
|
|
1820
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1821
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1882
|
+
..._optionalChain([segment, 'optionalAccess', _51 => _51.behavior]) ? { behavior: segment.behavior } : {},
|
|
1883
|
+
..._optionalChain([segment, 'optionalAccess', _52 => _52.steps]) ? { strobe: segment.steps } : {}
|
|
1822
1884
|
};
|
|
1823
1885
|
if (channel.kind === "view") animate.push({ ...base, view: to });
|
|
1824
1886
|
else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
|
|
@@ -1891,7 +1953,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1891
1953
|
if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
|
|
1892
1954
|
const lastChange = animate.reduce((m, a) => Math.max(m, (_nullishCoalesce(a.delay, () => ( 0))) + (_nullishCoalesce(a.ms, () => ( 0)))), 0);
|
|
1893
1955
|
let motion = { svg, animate };
|
|
1894
|
-
if (_optionalChain([scene, 'access',
|
|
1956
|
+
if (_optionalChain([scene, 'access', _53 => _53.controls, 'optionalAccess', _54 => _54.length])) {
|
|
1895
1957
|
const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
|
|
1896
1958
|
const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
|
|
1897
1959
|
svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
|
|
@@ -1913,7 +1975,7 @@ function toFlairVectorEffect(scene, options = {}) {
|
|
|
1913
1975
|
tags: _nullishCoalesce(options.tags, () => ( [])),
|
|
1914
1976
|
motion: out.motion,
|
|
1915
1977
|
hold: out.hold,
|
|
1916
|
-
..._optionalChain([scene, 'access',
|
|
1978
|
+
..._optionalChain([scene, 'access', _55 => _55.presets, 'optionalAccess', _56 => _56.length]) ? { presets: scene.presets } : {},
|
|
1917
1979
|
...options.trigger ? { trigger: options.trigger } : {}
|
|
1918
1980
|
};
|
|
1919
1981
|
}
|
|
@@ -1956,7 +2018,7 @@ function resolvePlaceholders(motion, notes) {
|
|
|
1956
2018
|
const filled = text.replace(PLACEHOLDER, (_, name) => {
|
|
1957
2019
|
const d = declared.get(name);
|
|
1958
2020
|
if (!d) unknown.add(name);
|
|
1959
|
-
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess',
|
|
2021
|
+
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _57 => _57.value]), () => ( ""))).replace(/"/g, '\\"');
|
|
1960
2022
|
});
|
|
1961
2023
|
for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
|
|
1962
2024
|
return { motion: JSON.parse(filled), controls };
|
|
@@ -2010,7 +2072,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
2010
2072
|
if (anim.along) {
|
|
2011
2073
|
const path = nodes.get(anim.along);
|
|
2012
2074
|
if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
|
|
2013
|
-
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access',
|
|
2075
|
+
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _58 => _58.at, 'optionalAccess', _59 => _59[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _60 => _60.at, 'optionalAccess', _61 => _61[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
2014
2076
|
}
|
|
2015
2077
|
for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
|
|
2016
2078
|
if (attr === "opacity") {
|
|
@@ -2021,7 +2083,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
2021
2083
|
out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
|
|
2022
2084
|
} else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
2023
2085
|
} else if (attr === "fill" || attr === "stroke") {
|
|
2024
|
-
if (_optionalChain([behavior, 'optionalAccess',
|
|
2086
|
+
if (_optionalChain([behavior, 'optionalAccess', _62 => _62.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
|
|
2025
2087
|
else {
|
|
2026
2088
|
if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
|
|
2027
2089
|
out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
@@ -2188,7 +2250,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2188
2250
|
time: p.time,
|
|
2189
2251
|
playing: p.playing,
|
|
2190
2252
|
loop: p.loop,
|
|
2191
|
-
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call',
|
|
2253
|
+
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _63 => _63(timeline, p.time), 'optionalAccess', _64 => _64.id]), () => ( null)),
|
|
2192
2254
|
selection: this.selection,
|
|
2193
2255
|
readOnly: this.readOnly,
|
|
2194
2256
|
error: this.error,
|
|
@@ -2227,7 +2289,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2227
2289
|
this.applying = false;
|
|
2228
2290
|
}
|
|
2229
2291
|
this.notify();
|
|
2230
|
-
_optionalChain([this, 'access',
|
|
2292
|
+
_optionalChain([this, 'access', _65 => _65.onChange, 'optionalCall', _66 => _66(scene)]);
|
|
2231
2293
|
}
|
|
2232
2294
|
commit(label, next) {
|
|
2233
2295
|
const before = this.scene;
|
|
@@ -2495,7 +2557,12 @@ var SHOT_FIELDS = {
|
|
|
2495
2557
|
camera: CAMERA,
|
|
2496
2558
|
run: { type: "array", items: MOTION_COMMAND_SCHEMA, description: "Commands, in order." },
|
|
2497
2559
|
hold: MS("Hold after the commands (default 600)"),
|
|
2498
|
-
speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play the whole shot this many times as fast (default 1; 0.5 is half speed). fit_animation sets it." }
|
|
2560
|
+
speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play the whole shot this many times as fast (default 1; 0.5 is half speed). fit_animation sets it." },
|
|
2561
|
+
at: object(
|
|
2562
|
+
{ beat: { type: "number", minimum: 0, description: `Beat number on the scene's beat grid, from 0 (2.5 is the "and" of beat 3).` }, word: { type: "integer", minimum: 0, description: "Index of a narration word, from 0." }, ms: { type: "number", minimum: 0, description: "A time in ms." } },
|
|
2563
|
+
[],
|
|
2564
|
+
"Start this shot on a cue (give one of beat, word or ms); the shot before holds until then. get_timing_grid lists beats and words."
|
|
2565
|
+
)
|
|
2499
2566
|
};
|
|
2500
2567
|
var SHOT = object({ id: { type: "string", pattern: "^[A-Za-z0-9_][\\w.-]*$", description: 'Unique shot id, e.g. "intro".' }, ...SHOT_FIELDS }, ["id", "run"], "A shot.");
|
|
2501
2568
|
var CONTROL = object(
|
|
@@ -2515,6 +2582,20 @@ var SETTINGS = {
|
|
|
2515
2582
|
loop: { type: "string", enum: ["none", "loop", "ping-pong"], description: "Playback past the end (default none)." },
|
|
2516
2583
|
fps: { type: "number", minimum: 1, maximum: 120, description: "Sampling rate for exports (default 30)." },
|
|
2517
2584
|
steps: { anyOf: [{ type: "number", exclusiveMinimum: 0, maximum: 120 }, { type: "null" }], description: 'Stepped time for every command without its own: changes hold on a grid of this many frames per second (12 = "on twos" at 24 fps) for a hand-drawn look; null makes it smooth again (default).' },
|
|
2585
|
+
beat: {
|
|
2586
|
+
anyOf: [
|
|
2587
|
+
object({ bpm: { type: "number", minimum: 1, maximum: 1e3, description: "Beats a minute." }, offset: { type: "number", description: "When beat 0 falls, ms (default 0)." }, meter: { type: "integer", minimum: 1, description: "Beats a bar (default 4)." } }, ["bpm"]),
|
|
2588
|
+
{ type: "null" }
|
|
2589
|
+
],
|
|
2590
|
+
description: `A beat grid (music or a voiceover's rhythm) that shots can be cued to with "at": { "beat": n }; null clears.`
|
|
2591
|
+
},
|
|
2592
|
+
narration: {
|
|
2593
|
+
anyOf: [
|
|
2594
|
+
object({ words: { type: "array", items: object({ text: { type: "string", description: "The word." }, start: { type: "number", minimum: 0, description: "When it is said, ms." }, end: { type: "number", minimum: 0, description: "When it ends, ms." } }, ["text", "start"]), description: "Words in time order, from a TTS or speech-to-text word timing." } }, ["words"]),
|
|
2595
|
+
{ type: "null" }
|
|
2596
|
+
],
|
|
2597
|
+
description: `A voiceover's word timings that shots can be cued to with "at": { "word": index }; null clears.`
|
|
2598
|
+
},
|
|
2518
2599
|
target: { anyOf: [{ type: "array", items: { type: "number", minimum: 0 }, minItems: 2, maxItems: 2 }, { type: "null" }], description: "Wanted length [min, max] in seconds; null clears." }
|
|
2519
2600
|
};
|
|
2520
2601
|
var EDIT_OP = {
|
|
@@ -2522,7 +2603,7 @@ var EDIT_OP = {
|
|
|
2522
2603
|
oneOf: [
|
|
2523
2604
|
object({ op: { const: "set_settings", description: "Like set_animation_settings." }, ...SETTINGS }, ["op"]),
|
|
2524
2605
|
object({ op: { const: "insert_shots", description: "Like insert_shots." }, shots: { type: "array", items: SHOT, minItems: 1, description: "Shots to add." }, at: { type: "integer", minimum: 0, description: "Position (default: the end)." } }, ["op", "shots"]),
|
|
2525
|
-
object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." } }, ["op", "shot"]),
|
|
2606
|
+
object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed", "at"] }, description: "Fields to clear." } }, ["op", "shot"]),
|
|
2526
2607
|
object({ op: { const: "delete_shots", description: "Like delete_shots." }, shots: { type: "array", items: SHOT_ID, minItems: 1, description: "Shot ids." } }, ["op", "shots"]),
|
|
2527
2608
|
object({ op: { const: "move_shot", description: "Like move_shot." }, shot: SHOT_ID, to: { type: "integer", minimum: 0, description: "New position (0 = first)." } }, ["op", "shot", "to"]),
|
|
2528
2609
|
object({ op: { const: "add_commands", description: "Like add_commands." }, shot: SHOT_ID, commands: { type: "array", items: MOTION_COMMAND_SCHEMA, minItems: 1, description: "Commands to add." }, at: { type: "integer", minimum: 0, description: "Index in the shot's run (default: the end)." } }, ["op", "shot", "commands"]),
|
|
@@ -2567,6 +2648,8 @@ function describeMotionShots(scene) {
|
|
|
2567
2648
|
end: t.end,
|
|
2568
2649
|
...shot.camera ? { camera: shot.camera } : {},
|
|
2569
2650
|
...shot.hold !== void 0 ? { hold: shot.hold } : {},
|
|
2651
|
+
...t.cue !== void 0 ? { cue: Math.round(t.cue) } : {},
|
|
2652
|
+
...t.missedBy !== void 0 ? { missedBy: Math.round(t.missedBy) } : {},
|
|
2570
2653
|
commands: shot.run.map((c, index) => {
|
|
2571
2654
|
const ct = timeline.commands.find((x) => x.shotIndex === i && x.command === index);
|
|
2572
2655
|
const primitive = motionFlairPrimitive(c);
|
|
@@ -2695,6 +2778,42 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2695
2778
|
const CURVES_DESCRIPTION = "Write cubic-bezier easings (ease, ease-in, ease-out, ease-in-out, back eases, custom curves) as one change with ease: [x1, y1, x2, y2] (default true). Needs Flair 0.36 or later; false samples them into linear steps for older Flair.";
|
|
2696
2779
|
const FLAIR_DESCRIPTION = "The Flair version to export for (default 0.39): springs and stepped time need 0.39, behaviours 0.38, cubic-bezier curves 0.36; older versions get those sampled into linear steps.";
|
|
2697
2780
|
const BEHAVIORS_DESCRIPTION = "Write pulses, shakes, teeters, waves, wiggles and colour pulses as one change with a Flair behavior, so they play exactly (default true; needs Flair 0.38 or later). False samples them into linear steps for older Flair.";
|
|
2781
|
+
const getTimingGrid = {
|
|
2782
|
+
name: "get_timing_grid",
|
|
2783
|
+
title: "Read the beat grid and narration",
|
|
2784
|
+
description: 'Returns the beats (number, time in ms, bar and beat in the bar) and narration words (index, text, start, end) between two times, to cue shots to with "at". Also says which shot plays at each. Without a beat grid or narration, that part is empty and says how to add one.',
|
|
2785
|
+
inputSchema: object({
|
|
2786
|
+
from: { type: "number", minimum: 0, description: "From this time, ms (default 0)." },
|
|
2787
|
+
to: { type: "number", minimum: 0, description: "Up to this time, ms (default: the end of the animation, or of the narration if later)." },
|
|
2788
|
+
limit: { type: "integer", minimum: 1, maximum: 1e3, description: "At most this many beats and words each (default 200)." }
|
|
2789
|
+
}),
|
|
2790
|
+
annotations: { title: "Read the beat grid and narration", ...READ },
|
|
2791
|
+
handler: ({ from = 0, to, limit = 200 }) => {
|
|
2792
|
+
const s = scene();
|
|
2793
|
+
const timeline = compileMotionScene(s);
|
|
2794
|
+
const words = _nullishCoalesce(_optionalChain([s, 'access', _67 => _67.narration, 'optionalAccess', _68 => _68.words]), () => ( []));
|
|
2795
|
+
const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
|
|
2796
|
+
const shotAt = (ms) => _optionalChain([timeline, 'access', _69 => _69.shots, 'access', _70 => _70.find, 'call', _71 => _71((x) => ms >= x.start && ms < x.end), 'optionalAccess', _72 => _72.id]);
|
|
2797
|
+
const beats = [];
|
|
2798
|
+
if (s.beat && s.beat.bpm > 0) {
|
|
2799
|
+
const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
|
|
2800
|
+
for (let b = first; beats.length < limit; b++) {
|
|
2801
|
+
const ms = motionBeatTime(s.beat, b);
|
|
2802
|
+
if (ms > end) break;
|
|
2803
|
+
const at = motionBeatAt(s.beat, ms);
|
|
2804
|
+
const shot = shotAt(ms);
|
|
2805
|
+
beats.push({ beat: b, ms: Math.round(ms), bar: at.bar, beatInBar: at.beatInBar, ...shot ? { shot } : {} });
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
const listed = words.map((w, index) => ({ index, text: w.text, start: w.start, ...w.end !== void 0 ? { end: w.end } : {}, ...shotAt(w.start) ? { shot: shotAt(w.start) } : {} })).filter((w) => w.start >= from && w.start <= end).slice(0, limit);
|
|
2809
|
+
return {
|
|
2810
|
+
from,
|
|
2811
|
+
to: Math.round(end),
|
|
2812
|
+
...s.beat ? { grid: s.beat, beats } : { beats: [], hint: 'No beat grid: set_animation_settings with { "beat": { "bpm": 120 } }.' },
|
|
2813
|
+
...words.length ? { words: listed } : { words: [], narrationHint: 'No narration: set_animation_settings with { "narration": { "words": [{ "text", "start" }, \u2026] } }.' }
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2698
2817
|
const exportFlairMotion = {
|
|
2699
2818
|
name: "export_flair_motion",
|
|
2700
2819
|
title: "Export as Flair vector motion",
|
|
@@ -2806,7 +2925,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2806
2925
|
{
|
|
2807
2926
|
shot: SHOT_ID,
|
|
2808
2927
|
set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."),
|
|
2809
|
-
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." }
|
|
2928
|
+
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed", "at"] }, description: "Fields to clear." }
|
|
2810
2929
|
},
|
|
2811
2930
|
["shot"]
|
|
2812
2931
|
),
|
|
@@ -2836,7 +2955,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2836
2955
|
inputSchema: object({ shot: SHOT_ID, commands: { type: "array", items: MOTION_COMMAND_SCHEMA, minItems: 1, description: "Commands to add." }, at: { type: "integer", minimum: 0, description: "Index in the run (default: the end)." } }, ["shot", "commands"]),
|
|
2837
2956
|
annotations: { title: "Add commands to a shot", ...WRITE },
|
|
2838
2957
|
handler: ({ shot, commands, at }) => {
|
|
2839
|
-
const before = _nullishCoalesce(_optionalChain([scene, 'call',
|
|
2958
|
+
const before = _nullishCoalesce(_optionalChain([scene, 'call', _73 => _73(), 'access', _74 => _74.shots, 'access', _75 => _75.find, 'call', _76 => _76((s) => s.id === shot), 'optionalAccess', _77 => _77.run, 'access', _78 => _78.length]), () => ( 0));
|
|
2840
2959
|
const first = _nullishCoalesce(at, () => ( before));
|
|
2841
2960
|
return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
|
|
2842
2961
|
const shots = describeMotionShots(s);
|
|
@@ -2872,7 +2991,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2872
2991
|
const setSettings = {
|
|
2873
2992
|
name: "set_animation_settings",
|
|
2874
2993
|
title: "Change animation settings",
|
|
2875
|
-
description: "Sets the name, description, loop mode, export frame rate, stepped time or target length. Returns the timing.",
|
|
2994
|
+
description: "Sets the name, description, loop mode, export frame rate, stepped time, beat grid, narration word timings or target length. Returns the timing.",
|
|
2876
2995
|
inputSchema: object(SETTINGS),
|
|
2877
2996
|
annotations: { title: "Change animation settings", ...WRITE_IDEMPOTENT },
|
|
2878
2997
|
handler: (args) => apply({ op: "set_settings", ...args }, "Change settings")
|
|
@@ -2950,7 +3069,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2950
3069
|
return { redone, ...redone ? { label } : {} };
|
|
2951
3070
|
}
|
|
2952
3071
|
};
|
|
2953
|
-
const reads = [getAnimation, getShot, findElements, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
|
|
3072
|
+
const reads = [getAnimation, getShot, findElements, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
|
|
2954
3073
|
const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
2955
3074
|
return {
|
|
2956
3075
|
id: _nullishCoalesce(options.id, () => ( "animation")),
|
|
@@ -2989,6 +3108,7 @@ Commands
|
|
|
2989
3108
|
- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale "blend": "add" to stack them too (what they add stays); "blend": "replace" makes a change take over instead.
|
|
2990
3109
|
- Any command but enter and exit can "repeat" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); "duration" is one cycle.
|
|
2991
3110
|
- Springs ("spring-gentle", "spring-quick", "spring-bouncy", "spring-slow" or { "type": "spring", \u2026 }) feel physical; leave "duration" out and the spring sets it.
|
|
3111
|
+
- To time shots to music or a voiceover: set a "beat" grid or "narration" word timings with set_animation_settings, read them with get_timing_grid, and give a shot "at": { "beat": n } or { "word": i }. The shot before holds until the cue; check_animation warns when a cue is missed.
|
|
2992
3112
|
- "steps": 12 (on a command, or for the whole scene with set_animation_settings) holds poses "on twos" for a hand-drawn look. "geometric": true on scale or a camera zooms at an even rate.
|
|
2993
3113
|
- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's "flairPrimitive"), and emphasis, reveals and follows export to Flair exactly.
|
|
2994
3114
|
- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).
|
|
@@ -3087,5 +3207,8 @@ Keep motion purposeful: one thing moving at a time reads best, ease-out for arri
|
|
|
3087
3207
|
|
|
3088
3208
|
|
|
3089
3209
|
|
|
3090
|
-
|
|
3210
|
+
|
|
3211
|
+
|
|
3212
|
+
|
|
3213
|
+
exports.ANIMATION_EDITOR_AGENT_INSTRUCTIONS = ANIMATION_EDITOR_AGENT_INSTRUCTIONS; exports.AnimationEditorController = AnimationEditorController; exports.FLAIR_EASES = FLAIR_EASES; exports.FLAIR_EXPORT_VERSION = FLAIR_EXPORT_VERSION; exports.MOTION_ADDITIVE_PROPERTIES = MOTION_ADDITIVE_PROPERTIES; exports.MOTION_BLENDS = MOTION_BLENDS; exports.MOTION_CAMERA_MS = MOTION_CAMERA_MS; exports.MOTION_COMMAND_SCHEMA = MOTION_COMMAND_SCHEMA; exports.MOTION_COMMAND_TYPES = MOTION_COMMAND_TYPES; exports.MOTION_DEFAULT_DURATIONS = MOTION_DEFAULT_DURATIONS; exports.MOTION_DEFAULT_HOLD = MOTION_DEFAULT_HOLD; exports.MOTION_DIRECTIONS = MOTION_DIRECTIONS; exports.MOTION_DRAW_EFFECT_ID = MOTION_DRAW_EFFECT_ID; exports.MOTION_DRAW_MS_PER_UNIT = MOTION_DRAW_MS_PER_UNIT; exports.MOTION_DRAW_RANGE = MOTION_DRAW_RANGE; exports.MOTION_EASE_IN_OUT = MOTION_EASE_IN_OUT; exports.MOTION_EASE_OUT = MOTION_EASE_OUT; exports.MOTION_EDIT_OPS = MOTION_EDIT_OPS; exports.MOTION_ENTER_STYLES = MOTION_ENTER_STYLES; exports.MOTION_EXIT_STYLES = MOTION_EXIT_STYLES; exports.MOTION_IRIS_SHAPES = MOTION_IRIS_SHAPES; exports.MOTION_ISSUE_CODES = MOTION_ISSUE_CODES; exports.MOTION_MIN_CHANGE_MS = MOTION_MIN_CHANGE_MS; exports.MOTION_REPEAT_MODES = MOTION_REPEAT_MODES; exports.MOTION_SPEED_RANGE = MOTION_SPEED_RANGE; exports.MOTION_SPLIT_ORIENTATIONS = MOTION_SPLIT_ORIENTATIONS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionBeatAt = motionBeatAt; exports.motionBeatTime = motionBeatTime; exports.motionClipAttrs = motionClipAttrs; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPathLength = motionPathLength; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotTimeScale = motionShotTimeScale; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; exports.parseMotionScene = parseMotionScene; exports.resolveMotionCamera = resolveMotionCamera; exports.segmentProgress = segmentProgress; exports.segmentValue = segmentValue; exports.shotShapeProblems = shotShapeProblems; exports.suggestName = suggestName; exports.toFlairEffectDocument = toFlairEffectDocument; exports.toFlairSvgMotion = toFlairSvgMotion; exports.toFlairVectorEffect = toFlairVectorEffect; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
|
|
3091
3214
|
//# sourceMappingURL=index.cjs.map
|