@pixerate/editor 0.68.0 → 0.69.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/dist/animation-editor/index.cjs +230 -46
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +66 -3
- package/dist/animation-editor/index.d.ts +66 -3
- package/dist/animation-editor/index.js +186 -2
- package/dist/animation-editor/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -56,6 +56,7 @@ var _chunk2FHLHZALcjs = require('../chunk-2FHLHZAL.cjs');
|
|
|
56
56
|
|
|
57
57
|
// src/animation-editor/types.ts
|
|
58
58
|
var MOTION_TRIGGERS = ["click", "hover", "press", "scroll"];
|
|
59
|
+
var MOTION_CLIP_TARGET = "$";
|
|
59
60
|
var MOTION_COMMAND_TYPES = [
|
|
60
61
|
"enter",
|
|
61
62
|
"exit",
|
|
@@ -67,6 +68,7 @@ var MOTION_COMMAND_TYPES = [
|
|
|
67
68
|
"style",
|
|
68
69
|
"morph",
|
|
69
70
|
"match",
|
|
71
|
+
"play",
|
|
70
72
|
"follow",
|
|
71
73
|
"pulse",
|
|
72
74
|
"shake",
|
|
@@ -94,6 +96,8 @@ var MOTION_DEFAULT_DURATIONS = {
|
|
|
94
96
|
style: 500,
|
|
95
97
|
morph: 900,
|
|
96
98
|
match: 900,
|
|
99
|
+
/** A clip's length comes from its commands (this is only a fallback). */
|
|
100
|
+
play: 600,
|
|
97
101
|
follow: 1400,
|
|
98
102
|
pulse: 600,
|
|
99
103
|
shake: 500,
|
|
@@ -137,6 +141,7 @@ var MOTION_ISSUE_CODES = {
|
|
|
137
141
|
"unknown-path": { severity: "error", wording: "A follow command names a path the artwork does not have." },
|
|
138
142
|
"not-a-path": { severity: "error", wording: "A follow path must be a path node, not a group." },
|
|
139
143
|
"reveal-no-box": { severity: "warning", wording: "A wipe, iris or split cannot clip a node with no width or height (a lone straight line), so it fades instead." },
|
|
144
|
+
"unknown-clip": { severity: "error", wording: "A play command names a clip the scene does not have." },
|
|
140
145
|
"unknown-shape": { severity: "error", wording: "A morph names a shape the artwork does not have." },
|
|
141
146
|
"match-self": { severity: "error", wording: "A match turns a group into itself or into a group it holds (or that holds it)." },
|
|
142
147
|
"match-unpaired": { severity: "note", wording: `Parts of a match's "into" group have no partner in the target, so they do not appear.` },
|
|
@@ -214,7 +219,7 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
|
|
|
214
219
|
this.problems = problems;
|
|
215
220
|
}
|
|
216
221
|
};
|
|
217
|
-
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "trigger", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
|
|
222
|
+
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "clips", "loop", "trigger", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
|
|
218
223
|
var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
|
|
219
224
|
var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
|
|
220
225
|
var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
|
|
@@ -229,6 +234,7 @@ var COMMAND_KEYS = {
|
|
|
229
234
|
style: [...BASE_KEYS, "fill", "stroke", "strokeWidth"],
|
|
230
235
|
morph: [...BASE_KEYS, "into"],
|
|
231
236
|
match: [...BASE_KEYS, "into"],
|
|
237
|
+
play: ["type", "targets", "delay", "stagger", "with", "note", "clip", "speed"],
|
|
232
238
|
follow: [...BASE_KEYS, "path", "rotate", "from", "to"],
|
|
233
239
|
pulse: [...BASE_KEYS, "amount", "count", "blend"],
|
|
234
240
|
shake: [...BASE_KEYS, "amount", "count", "axis", "blend"],
|
|
@@ -326,6 +332,10 @@ function commandShapeProblems(command2, where) {
|
|
|
326
332
|
if (command2.stroke !== void 0 && typeof command2.stroke !== "string") p(`"stroke" must be a CSS colour.`);
|
|
327
333
|
if (command2.strokeWidth !== void 0 && !(isNum(command2.strokeWidth) && command2.strokeWidth >= 0)) p(`"strokeWidth" must be a number \u2265 0.`);
|
|
328
334
|
break;
|
|
335
|
+
case "play":
|
|
336
|
+
if (typeof command2.clip !== "string" || !command2.clip) p(`"clip" must be the name of one of the scene's clips.`);
|
|
337
|
+
if (command2.speed !== void 0 && !(isNum(command2.speed) && command2.speed >= 0.05 && command2.speed <= 20)) p(`"speed" must be a number from 0.05 to 20 (2 plays the clip twice as fast).`);
|
|
338
|
+
break;
|
|
329
339
|
case "morph":
|
|
330
340
|
case "match":
|
|
331
341
|
if (typeof command2.into !== "string" || !command2.into) p(`"into" must be the id of a ${type === "match" ? "group to become" : "node whose shape to take"}.`);
|
|
@@ -363,6 +373,25 @@ function commandShapeProblems(command2, where) {
|
|
|
363
373
|
}
|
|
364
374
|
return problems;
|
|
365
375
|
}
|
|
376
|
+
function clipsShapeProblems(clips) {
|
|
377
|
+
if (!isObject(clips)) return ['Scene: "clips" must be an object of named clips { "pop": { "run": [ \u2026 ] } }.'];
|
|
378
|
+
const problems = [];
|
|
379
|
+
for (const [name, clip] of Object.entries(clips)) {
|
|
380
|
+
const where = `Clip "${name}"`;
|
|
381
|
+
if (!/^[A-Za-z_][\w-]*$/.test(name)) problems.push(`${where}: a clip name is letters, digits, "_" or "-" (e.g. "pop-in").`);
|
|
382
|
+
if (!isObject(clip) || !Array.isArray(clip.run)) {
|
|
383
|
+
problems.push(`${where} must be { "description"?, "run": [commands] }.`);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
unknownKeys(clip, ["description", "run"], where, problems);
|
|
387
|
+
if (clip.description !== void 0 && typeof clip.description !== "string") problems.push(`${where}: "description" must be a string.`);
|
|
388
|
+
clip.run.forEach((command2, i) => {
|
|
389
|
+
if (isObject(command2) && command2.type === "play") problems.push(`${where}, command ${i + 1}: a clip cannot play another clip; copy its commands in instead.`);
|
|
390
|
+
else problems.push(...commandShapeProblems(command2, `${where}, command ${i + 1}`));
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return problems;
|
|
394
|
+
}
|
|
366
395
|
function shotShapeProblems(shot, where) {
|
|
367
396
|
if (!isObject(shot)) return [`${where} must be an object.`];
|
|
368
397
|
const problems = [];
|
|
@@ -410,6 +439,7 @@ function motionSceneShapeProblems(input) {
|
|
|
410
439
|
const art = input.artwork;
|
|
411
440
|
if (!isObject(art) || !isNum(art.width) || !isNum(art.height) || !(art.width > 0) || !(art.height > 0) || !Array.isArray(art.nodes))
|
|
412
441
|
problems.push('Scene: "artwork" must be a vector document { width, height, nodes }.');
|
|
442
|
+
if (input.clips !== void 0) problems.push(...clipsShapeProblems(input.clips));
|
|
413
443
|
if (input.trigger !== void 0 && !MOTION_TRIGGERS.includes(input.trigger)) problems.push(`Scene: "trigger" must be one of ${MOTION_TRIGGERS.join(", ")}.`);
|
|
414
444
|
if (input.loop !== void 0 && !["none", "loop", "ping-pong"].includes(input.loop)) problems.push('Scene: "loop" must be "none", "loop" or "ping-pong".');
|
|
415
445
|
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.');
|
|
@@ -793,8 +823,38 @@ function motionCommandRepeat(command2) {
|
|
|
793
823
|
function motionCommandDuration(command2, scene, target) {
|
|
794
824
|
return motionCycleDuration(command2, scene, target) * motionCommandRepeat(command2);
|
|
795
825
|
}
|
|
826
|
+
function motionClipLayout(scene, clip, target, speed = 1) {
|
|
827
|
+
const run = _optionalChain([scene, 'access', _22 => _22.clips, 'optionalAccess', _23 => _23[clip], 'optionalAccess', _24 => _24.run]);
|
|
828
|
+
if (!run) return { items: [], length: 0 };
|
|
829
|
+
const f = speed > 0 && Number.isFinite(speed) ? 1 / speed : 1;
|
|
830
|
+
const items = [];
|
|
831
|
+
let runEnd = 0;
|
|
832
|
+
let prevStart = 0;
|
|
833
|
+
run.forEach((raw, i) => {
|
|
834
|
+
const base = raw.with === "previous" && i > 0 ? prevStart : runEnd;
|
|
835
|
+
if (raw.type === "wait") {
|
|
836
|
+
prevStart = base;
|
|
837
|
+
runEnd = Math.max(runEnd, base + raw.duration * f);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (raw.type === "play") return;
|
|
841
|
+
const command2 = { ...raw, targets: raw.targets.map((t) => t === MOTION_CLIP_TARGET ? target : t) };
|
|
842
|
+
const begin = base + (_nullishCoalesce(command2.delay, () => ( 0))) * f;
|
|
843
|
+
let end = begin;
|
|
844
|
+
command2.targets.forEach((t, k) => {
|
|
845
|
+
const cycle = motionCycleDuration(command2, scene, t) * f;
|
|
846
|
+
const s = begin + k * (_nullishCoalesce(command2.stagger, () => ( 0))) * f;
|
|
847
|
+
items.push({ command: command2, target: t, start: s, cycle });
|
|
848
|
+
end = Math.max(end, s + cycle * motionCommandRepeat(command2));
|
|
849
|
+
});
|
|
850
|
+
prevStart = begin;
|
|
851
|
+
runEnd = Math.max(runEnd, end);
|
|
852
|
+
});
|
|
853
|
+
return { items, length: runEnd };
|
|
854
|
+
}
|
|
796
855
|
function motionCycleDuration(command2, scene, target) {
|
|
797
856
|
if (command2.type === "wait") return command2.duration;
|
|
857
|
+
if (command2.type === "play") return motionClipLayout(scene, command2.clip, _nullishCoalesce(target, () => ( MOTION_CLIP_TARGET)), command2.speed).length;
|
|
798
858
|
if (command2.duration !== void 0) return command2.duration;
|
|
799
859
|
if (command2.ease !== void 0) {
|
|
800
860
|
const ease = _chunk2FHLHZALcjs.resolveEasing.call(void 0, command2.ease);
|
|
@@ -806,7 +866,7 @@ function motionCycleDuration(command2, scene, target) {
|
|
|
806
866
|
const ms = length * MOTION_DRAW_MS_PER_UNIT;
|
|
807
867
|
return Math.round(Math.min(MOTION_DRAW_RANGE[1], Math.max(MOTION_DRAW_RANGE[0], ms)));
|
|
808
868
|
}
|
|
809
|
-
if (command2.type === "move" && _optionalChain([command2, 'access',
|
|
869
|
+
if (command2.type === "move" && _optionalChain([command2, 'access', _25 => _25.via, 'optionalAccess', _26 => _26.length])) return MOTION_DEFAULT_DURATIONS.move + 400 * command2.via.length;
|
|
810
870
|
return MOTION_DEFAULT_DURATIONS[command2.type];
|
|
811
871
|
}
|
|
812
872
|
var MOTION_EASE_OUT = { type: "cubic-bezier", x1: 1 / 3, y1: 1, x2: 2 / 3, y2: 1 };
|
|
@@ -884,6 +944,19 @@ function compileMotionScene(scene) {
|
|
|
884
944
|
const shot = scene.shots[timing.shotIndex];
|
|
885
945
|
const command2 = shot.run[timing.command];
|
|
886
946
|
if (command2.type === "wait") continue;
|
|
947
|
+
if (command2.type === "play") {
|
|
948
|
+
const f = motionShotTimeScale(shot);
|
|
949
|
+
command2.targets.forEach((target, k) => {
|
|
950
|
+
if (!nodes.has(target)) return;
|
|
951
|
+
const at = timing.start + k * (_nullishCoalesce(command2.stagger, () => ( 0))) * f;
|
|
952
|
+
for (const item of motionClipLayout(scene, command2.clip, target, (_nullishCoalesce(command2.speed, () => ( 1))) / f).items) {
|
|
953
|
+
const entry = nodes.get(item.target);
|
|
954
|
+
if (!entry) continue;
|
|
955
|
+
jobs.push({ start: at + item.start, order: order++, run: () => compileRepeated(item.command, item.target, entry, at + item.start, item.cycle, timing) });
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
887
960
|
command2.targets.forEach((target, k) => {
|
|
888
961
|
const entry = nodes.get(target);
|
|
889
962
|
if (!entry) return;
|
|
@@ -935,7 +1008,7 @@ function compileMotionScene(scene) {
|
|
|
935
1008
|
const style = _nullishCoalesce(command2.style, () => ( "fade"));
|
|
936
1009
|
const opacity = rest("opacity");
|
|
937
1010
|
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;
|
|
938
|
-
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess',
|
|
1011
|
+
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _27 => _27.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
|
|
939
1012
|
if (reveal) {
|
|
940
1013
|
seg("opacity", opacity, "linear", start, start);
|
|
941
1014
|
seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
|
|
@@ -997,15 +1070,15 @@ function compileMotionScene(scene) {
|
|
|
997
1070
|
}
|
|
998
1071
|
case "move": {
|
|
999
1072
|
const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
|
|
1000
|
-
let dx = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
1001
|
-
let dy = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
1073
|
+
let dx = _nullishCoalesce(_optionalChain([command2, 'access', _28 => _28.by, 'optionalAccess', _29 => _29.x]), () => ( 0));
|
|
1074
|
+
let dy = _nullishCoalesce(_optionalChain([command2, 'access', _30 => _30.by, 'optionalAccess', _31 => _31.y]), () => ( 0));
|
|
1002
1075
|
if (command2.to) {
|
|
1003
1076
|
const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
|
|
1004
1077
|
const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
|
|
1005
1078
|
dx = target.x - (entry.center.x + num("x"));
|
|
1006
1079
|
dy = target.y - (entry.center.y + num("y"));
|
|
1007
1080
|
}
|
|
1008
|
-
if (_optionalChain([command2, 'access',
|
|
1081
|
+
if (_optionalChain([command2, 'access', _32 => _32.via, 'optionalAccess', _33 => _33.length])) {
|
|
1009
1082
|
const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
|
|
1010
1083
|
const x0 = num("x");
|
|
1011
1084
|
const y0 = num("y");
|
|
@@ -1116,7 +1189,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
|
|
|
1116
1189
|
var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
|
|
1117
1190
|
var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
|
|
1118
1191
|
function applyMotionControlValues(scene, values) {
|
|
1119
|
-
if (!values || !_optionalChain([scene, 'access',
|
|
1192
|
+
if (!values || !_optionalChain([scene, 'access', _34 => _34.controls, 'optionalAccess', _35 => _35.length])) return scene;
|
|
1120
1193
|
const map = /* @__PURE__ */ new Map();
|
|
1121
1194
|
for (const control of scene.controls) {
|
|
1122
1195
|
const value = values[control.name];
|
|
@@ -1162,7 +1235,7 @@ function shapeIn(entry, shape) {
|
|
|
1162
1235
|
}
|
|
1163
1236
|
function shapeAt(entry, timeline, nodes, time) {
|
|
1164
1237
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
|
|
1165
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
1238
|
+
if (!_optionalChain([track, 'optionalAccess', _36 => _36.length])) return null;
|
|
1166
1239
|
const s = activeSegment(track, time);
|
|
1167
1240
|
const fromId = String(s ? s.from : track[0].from);
|
|
1168
1241
|
if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
|
|
@@ -1185,8 +1258,8 @@ function motionFollowRoute(entry, path) {
|
|
|
1185
1258
|
}
|
|
1186
1259
|
function followAt(entry, timeline, nodes, time) {
|
|
1187
1260
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
|
|
1188
|
-
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess',
|
|
1189
|
-
if (!_optionalChain([s, 'optionalAccess',
|
|
1261
|
+
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _37 => _37[0]])));
|
|
1262
|
+
if (!_optionalChain([s, 'optionalAccess', _38 => _38.path])) return null;
|
|
1190
1263
|
const route = motionFollowRoute(entry, nodes.get(s.path));
|
|
1191
1264
|
const progress = Number(trackValue(track, time));
|
|
1192
1265
|
if (!route || !(progress >= 0)) return null;
|
|
@@ -1246,7 +1319,7 @@ function motionFrameAt(scene, time, options = {}) {
|
|
|
1246
1319
|
if (out.visible === void 0) delete out.visible;
|
|
1247
1320
|
}
|
|
1248
1321
|
const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
|
|
1249
|
-
if (effectTracks.length && _optionalChain([out, 'access',
|
|
1322
|
+
if (effectTracks.length && _optionalChain([out, 'access', _39 => _39.effects, 'optionalAccess', _40 => _40.length])) {
|
|
1250
1323
|
out = {
|
|
1251
1324
|
...out,
|
|
1252
1325
|
effects: out.effects.map((effect) => {
|
|
@@ -1359,7 +1432,7 @@ function motionPath(scene, id, options = {}) {
|
|
|
1359
1432
|
const r = (n) => Math.round(n * 100) / 100;
|
|
1360
1433
|
const centreAt = (t) => {
|
|
1361
1434
|
const node = indexMotionNodes(motionFrameAtCache(scene, t)).get(id);
|
|
1362
|
-
const b = _optionalChain([node, 'optionalAccess',
|
|
1435
|
+
const b = _optionalChain([node, 'optionalAccess', _41 => _41.bounds]);
|
|
1363
1436
|
return b ? { t: r(t), x: r(b.x + b.width / 2), y: r(b.y + b.height / 2) } : null;
|
|
1364
1437
|
};
|
|
1365
1438
|
const points = [];
|
|
@@ -1399,14 +1472,14 @@ function motionStageSvg(scene, time, options = {}) {
|
|
|
1399
1472
|
const open = current.indexOf(">") + 1;
|
|
1400
1473
|
const close = current.lastIndexOf("</svg>");
|
|
1401
1474
|
const body = current.slice(open, close);
|
|
1402
|
-
const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access',
|
|
1475
|
+
const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _42 => _42.exec, 'call', _43 => _43(body), 'optionalAccess', _44 => _44[0]]), () => ( "")) : "";
|
|
1403
1476
|
return `${current.slice(0, open)}${bg}${ghosts.join("")}${body.slice(bg.length)}${paths}</svg>`;
|
|
1404
1477
|
}
|
|
1405
1478
|
|
|
1406
1479
|
// src/animation-editor/check.ts
|
|
1407
1480
|
var MOTION_MIN_CHANGE_MS = 120;
|
|
1408
1481
|
var NUMERIC_PARAMS = (node, effectId) => {
|
|
1409
|
-
const effect = _optionalChain([node, 'access',
|
|
1482
|
+
const effect = _optionalChain([node, 'access', _45 => _45.effects, 'optionalAccess', _46 => _46.find, 'call', _47 => _47((e) => e.id === effectId)]);
|
|
1410
1483
|
if (!effect) return null;
|
|
1411
1484
|
return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
|
|
1412
1485
|
};
|
|
@@ -1483,6 +1556,8 @@ function checkMotionScene(scene) {
|
|
|
1483
1556
|
if (command2.type === "follow" && known(command2.path, "unknown-path", "path", where) && nodes.get(command2.path).node.type !== "path")
|
|
1484
1557
|
issues.push(motionIssue("not-a-path", `"${command2.path}" is a group; follow a single path node.`, { ...where, ids: [command2.path] }));
|
|
1485
1558
|
if (command2.type === "morph") known(command2.into, "unknown-shape", "shape", where);
|
|
1559
|
+
if (command2.type === "play" && !_optionalChain([scene, 'access', _48 => _48.clips, 'optionalAccess', _49 => _49[command2.clip]]))
|
|
1560
|
+
issues.push(motionIssue("unknown-clip", `There is no clip ${namedWithSuggestion(command2.clip, Object.keys(_nullishCoalesce(scene.clips, () => ( {}))))}; define it with set_animation_clip.`, where));
|
|
1486
1561
|
if (command2.type === "match" && known(command2.into, "unknown-shape", "group", where)) {
|
|
1487
1562
|
const into = nodes.get(command2.into);
|
|
1488
1563
|
for (const id of targets) {
|
|
@@ -1555,6 +1630,12 @@ function checkMotionScene(scene) {
|
|
|
1555
1630
|
}
|
|
1556
1631
|
});
|
|
1557
1632
|
});
|
|
1633
|
+
for (const [name, clip] of Object.entries(_nullishCoalesce(scene.clips, () => ( {}))))
|
|
1634
|
+
for (const command2 of clip.run)
|
|
1635
|
+
if (command2.type !== "wait") {
|
|
1636
|
+
for (const id of command2.targets)
|
|
1637
|
+
if (id !== MOTION_CLIP_TARGET && !nodes.has(id)) issues.push(motionIssue("unknown-target", `Clip "${name}" names ${namedWithSuggestion(id, [...nodes.keys()])}, which the artwork does not have; use "$" for the node the clip is played on.`, { ids: [id] }));
|
|
1638
|
+
}
|
|
1558
1639
|
if (scene.trigger) {
|
|
1559
1640
|
if ((_nullishCoalesce(scene.loop, () => ( "none"))) !== "none") issues.push(motionIssue("trigger-loop", `The scene plays on ${scene.trigger}, so its "${scene.loop}" loop does not apply; set loop to "none" or remove the trigger.`, {}));
|
|
1560
1641
|
if (scene.trigger === "click") {
|
|
@@ -1568,7 +1649,7 @@ function checkMotionScene(scene) {
|
|
|
1568
1649
|
if (!shot.at) return;
|
|
1569
1650
|
const timing = timeline.shots[i];
|
|
1570
1651
|
if (timing.cue === void 0) {
|
|
1571
|
-
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',
|
|
1652
|
+
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', _50 => _50.narration, 'optionalAccess', _51 => _51.words, 'access', _52 => _52.length]), () => ( 0))} words (indexes from 0)`;
|
|
1572
1653
|
issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
|
|
1573
1654
|
} else if (timing.missedBy !== void 0) {
|
|
1574
1655
|
const late = Math.round(timing.missedBy);
|
|
@@ -1601,7 +1682,7 @@ function checkMotionScene(scene) {
|
|
|
1601
1682
|
const cam = frame.camera;
|
|
1602
1683
|
const docIndex = indexMotionNodes(frame.document.nodes);
|
|
1603
1684
|
for (const id of command2.targets) {
|
|
1604
|
-
const b = _optionalChain([docIndex, 'access',
|
|
1685
|
+
const b = _optionalChain([docIndex, 'access', _53 => _53.get, 'call', _54 => _54(id), 'optionalAccess', _55 => _55.bounds]);
|
|
1605
1686
|
if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
|
|
1606
1687
|
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;
|
|
1607
1688
|
if (outside) {
|
|
@@ -1638,6 +1719,7 @@ var MOTION_EDIT_OPS = [
|
|
|
1638
1719
|
"move_command",
|
|
1639
1720
|
"set_artwork",
|
|
1640
1721
|
"set_controls",
|
|
1722
|
+
"set_clip",
|
|
1641
1723
|
"replace_scene"
|
|
1642
1724
|
];
|
|
1643
1725
|
var EditError = class extends Error {
|
|
@@ -1694,6 +1776,23 @@ function applyOne(scene, edit) {
|
|
|
1694
1776
|
else if (edit.target !== void 0) next.target = edit.target;
|
|
1695
1777
|
return next;
|
|
1696
1778
|
}
|
|
1779
|
+
case "set_clip": {
|
|
1780
|
+
if (typeof edit.name !== "string" || !edit.name) fail('Give the clip a "name".');
|
|
1781
|
+
const clips = { ..._nullishCoalesce(scene.clips, () => ( {})) };
|
|
1782
|
+
if (edit.clip === null) {
|
|
1783
|
+
if (!clips[edit.name]) fail(`There is no clip ${namedWithSuggestion(edit.name, Object.keys(clips))} to delete.`);
|
|
1784
|
+
const playing = scene.shots.filter((s) => s.run.some((c) => c.type === "play" && c.clip === edit.name)).map((s) => `"${s.id}"`);
|
|
1785
|
+
if (playing.length) fail(`Clip "${edit.name}" is played in shot${playing.length > 1 ? "s" : ""} ${playing.join(", ")}; remove those play commands first.`);
|
|
1786
|
+
delete clips[edit.name];
|
|
1787
|
+
} else {
|
|
1788
|
+
const problems = clipsShapeProblems({ [edit.name]: edit.clip });
|
|
1789
|
+
if (problems.length) fail(problems.join(" "));
|
|
1790
|
+
clips[edit.name] = edit.clip;
|
|
1791
|
+
}
|
|
1792
|
+
const next = { ...scene, clips };
|
|
1793
|
+
if (!Object.keys(clips).length) delete next.clips;
|
|
1794
|
+
return next;
|
|
1795
|
+
}
|
|
1697
1796
|
case "insert_shots": {
|
|
1698
1797
|
if (!edit.shots.length) fail("Give at least one shot.");
|
|
1699
1798
|
edit.shots.forEach((shot, i) => {
|
|
@@ -1807,9 +1906,9 @@ function applyMotionEdits(scene, edits) {
|
|
|
1807
1906
|
let next = scene;
|
|
1808
1907
|
for (let i = 0; i < list.length; i++) {
|
|
1809
1908
|
const edit = list[i];
|
|
1810
|
-
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess',
|
|
1909
|
+
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _56 => _56.op])}): ` : "";
|
|
1811
1910
|
if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
|
|
1812
|
-
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess',
|
|
1911
|
+
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _57 => _57.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
|
|
1813
1912
|
try {
|
|
1814
1913
|
next = applyOne(next, edit);
|
|
1815
1914
|
} catch (error) {
|
|
@@ -1969,7 +2068,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1969
2068
|
const wrapper = freshId(`${id}--follow`);
|
|
1970
2069
|
const centre = freshId(`${id}--follow-centre`);
|
|
1971
2070
|
const c = entry.center;
|
|
1972
|
-
const at = _optionalChain([motionNodeOffset, 'call',
|
|
2071
|
+
const at = _optionalChain([motionNodeOffset, 'call', _58 => _58(entry, timeline, 0, nodes), 'optionalAccess', _59 => _59.follow]);
|
|
1973
2072
|
const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
|
|
1974
2073
|
(wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
|
|
1975
2074
|
follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
|
|
@@ -2029,7 +2128,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
2029
2128
|
exact: () => false,
|
|
2030
2129
|
value: (t) => {
|
|
2031
2130
|
const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
|
|
2032
|
-
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call',
|
|
2131
|
+
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _60 => _60(frame.document.nodes), 'access', _61 => _61.get, 'call', _62 => _62(id), 'optionalAccess', _63 => _63.node]));
|
|
2033
2132
|
if (!now || now.type !== "path") return "";
|
|
2034
2133
|
const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
|
|
2035
2134
|
return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
|
|
@@ -2050,8 +2149,8 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
2050
2149
|
...delay > 0 ? { delay: round2(delay) } : {},
|
|
2051
2150
|
ms: round2(ms),
|
|
2052
2151
|
ease: written,
|
|
2053
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
2054
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
2152
|
+
..._optionalChain([segment, 'optionalAccess', _64 => _64.behavior]) ? { behavior: segment.behavior } : {},
|
|
2153
|
+
..._optionalChain([segment, 'optionalAccess', _65 => _65.steps]) ? { strobe: segment.steps } : {}
|
|
2055
2154
|
};
|
|
2056
2155
|
if (channel.kind === "view") animate.push({ ...base, view: to });
|
|
2057
2156
|
else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
|
|
@@ -2140,7 +2239,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
2140
2239
|
let svg = _chunkYJLCBA7Vcjs.vectorDocumentToSvg.call(void 0, { ...scene.artwork, nodes: _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, scene.artwork.nodes) }, { pretty: false, precision, wrap });
|
|
2141
2240
|
if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
|
|
2142
2241
|
let motion = { svg, animate };
|
|
2143
|
-
if (_optionalChain([scene, 'access',
|
|
2242
|
+
if (_optionalChain([scene, 'access', _66 => _66.controls, 'optionalAccess', _67 => _67.length])) {
|
|
2144
2243
|
const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
|
|
2145
2244
|
const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
|
|
2146
2245
|
svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
|
|
@@ -2162,7 +2261,7 @@ function toFlairVectorEffect(scene, options = {}) {
|
|
|
2162
2261
|
tags: _nullishCoalesce(options.tags, () => ( [])),
|
|
2163
2262
|
motion: out.motion,
|
|
2164
2263
|
hold: out.hold,
|
|
2165
|
-
..._optionalChain([scene, 'access',
|
|
2264
|
+
..._optionalChain([scene, 'access', _68 => _68.presets, 'optionalAccess', _69 => _69.length]) ? { presets: scene.presets } : {},
|
|
2166
2265
|
...out.trigger ? { trigger: out.trigger } : {}
|
|
2167
2266
|
};
|
|
2168
2267
|
}
|
|
@@ -2206,7 +2305,7 @@ function resolvePlaceholders(motion, notes) {
|
|
|
2206
2305
|
const filled = text.replace(PLACEHOLDER, (_, name) => {
|
|
2207
2306
|
const d = declared.get(name);
|
|
2208
2307
|
if (!d) unknown.add(name);
|
|
2209
|
-
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess',
|
|
2308
|
+
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _70 => _70.value]), () => ( ""))).replace(/"/g, '\\"');
|
|
2210
2309
|
});
|
|
2211
2310
|
for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
|
|
2212
2311
|
return { motion: JSON.parse(filled), controls };
|
|
@@ -2260,7 +2359,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
2260
2359
|
if (anim.along) {
|
|
2261
2360
|
const path = nodes.get(anim.along);
|
|
2262
2361
|
if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
|
|
2263
|
-
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access',
|
|
2362
|
+
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _71 => _71.at, 'optionalAccess', _72 => _72[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _73 => _73.at, 'optionalAccess', _74 => _74[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
2264
2363
|
}
|
|
2265
2364
|
for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
|
|
2266
2365
|
if (attr === "opacity") {
|
|
@@ -2271,7 +2370,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
2271
2370
|
out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
|
|
2272
2371
|
} else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
2273
2372
|
} else if (attr === "fill" || attr === "stroke") {
|
|
2274
|
-
if (_optionalChain([behavior, 'optionalAccess',
|
|
2373
|
+
if (_optionalChain([behavior, 'optionalAccess', _75 => _75.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
|
|
2275
2374
|
else {
|
|
2276
2375
|
if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
|
|
2277
2376
|
out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
@@ -2554,7 +2653,7 @@ function toLottie(scene, options = {}) {
|
|
|
2554
2653
|
const shapeSegments = [...timeline.tracks.entries()].filter(([k]) => k.startsWith(`${path.id}\0`) && (k.endsWith("\0shape") || k.includes("\0effect:"))).flatMap(([, v]) => v);
|
|
2555
2654
|
const geometryAt = (t) => {
|
|
2556
2655
|
if (!shapeSegments.length) return _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
|
|
2557
|
-
const now = _optionalChain([indexMotionNodes, 'call',
|
|
2656
|
+
const now = _optionalChain([indexMotionNodes, 'call', _76 => _76(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _77 => _77.get, 'call', _78 => _78(path.id), 'optionalAccess', _79 => _79.node]);
|
|
2558
2657
|
return now && now.type === "path" ? _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, [now])[0]) : _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
|
|
2559
2658
|
};
|
|
2560
2659
|
const rest = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(0)), round2);
|
|
@@ -2600,7 +2699,7 @@ function toLottie(scene, options = {}) {
|
|
|
2600
2699
|
lj: path.strokeLinejoin === "round" ? 2 : path.strokeLinejoin === "bevel" ? 3 : 1,
|
|
2601
2700
|
ml: 4
|
|
2602
2701
|
});
|
|
2603
|
-
if (_optionalChain([path, 'access',
|
|
2702
|
+
if (_optionalChain([path, 'access', _80 => _80.strokeDasharray, 'optionalAccess', _81 => _81.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
|
|
2604
2703
|
if (path.strokeAlign && path.strokeAlign !== "center") notes.push(`"${path.id}": its stroke is drawn centred (Lottie has no inside or outside strokes).`);
|
|
2605
2704
|
}
|
|
2606
2705
|
const fill = paintOf(path.fill, "fill", path.id);
|
|
@@ -2751,7 +2850,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2751
2850
|
time: p.time,
|
|
2752
2851
|
playing: p.playing,
|
|
2753
2852
|
loop: p.loop,
|
|
2754
|
-
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call',
|
|
2853
|
+
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _82 => _82(timeline, p.time), 'optionalAccess', _83 => _83.id]), () => ( null)),
|
|
2755
2854
|
selection: this.selection,
|
|
2756
2855
|
readOnly: this.readOnly,
|
|
2757
2856
|
error: this.error,
|
|
@@ -2790,7 +2889,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2790
2889
|
this.applying = false;
|
|
2791
2890
|
}
|
|
2792
2891
|
this.notify();
|
|
2793
|
-
_optionalChain([this, 'access',
|
|
2892
|
+
_optionalChain([this, 'access', _84 => _84.onChange, 'optionalCall', _85 => _85(scene)]);
|
|
2794
2893
|
}
|
|
2795
2894
|
commit(label, next) {
|
|
2796
2895
|
const before = this.scene;
|
|
@@ -3003,6 +3102,20 @@ var MOTION_COMMAND_SCHEMA = {
|
|
|
3003
3102
|
stroke: { type: "string", description: "CSS colour (hex, rgb(), rgba())." },
|
|
3004
3103
|
strokeWidth: { type: "number", minimum: 0, description: "Document units." }
|
|
3005
3104
|
}),
|
|
3105
|
+
object(
|
|
3106
|
+
{
|
|
3107
|
+
type: { const: "play", description: 'Plays a clip (set_animation_clip) on the targets: its commands, with "$" standing for each target, timed as in a shot.' },
|
|
3108
|
+
targets: NODE_IDS,
|
|
3109
|
+
clip: { type: "string", minLength: 1, description: "The clip's name (get_animation lists them)." },
|
|
3110
|
+
speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play it this many times as fast (default 1)." },
|
|
3111
|
+
delay: BASE.delay,
|
|
3112
|
+
stagger: BASE.stagger,
|
|
3113
|
+
with: BASE.with,
|
|
3114
|
+
note: BASE.note
|
|
3115
|
+
},
|
|
3116
|
+
["type", "targets", "clip"],
|
|
3117
|
+
"Plays a clip on the targets."
|
|
3118
|
+
),
|
|
3006
3119
|
command("match", `Smart Animate: tweens a group into another group (usually hidden), part by part. Parts pair by name, then by id with the group ids taken out (card-a-title \u2194 card-b-title), then by kind in order; each takes its partner's shape, paint and place. Unpaired parts fade out; parts of "into" with no partner do not appear (check_animation lists them).`, { into: { type: "string", minLength: 1, description: "Id of the group to become." } }, ["into"]),
|
|
3007
3120
|
command("morph", "Morphs each target into the shape (and paint) of another node, which usually stays hidden.", { into: { type: "string", minLength: 1, description: "Id of the node whose shape to take." } }, ["into"]),
|
|
3008
3121
|
command("follow", "Moves targets along a path node, centre on the path.", {
|
|
@@ -3211,6 +3324,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
3211
3324
|
...s.target ? { target: s.target } : {},
|
|
3212
3325
|
...includeElements ? { elements: describeMotionElements(s) } : {},
|
|
3213
3326
|
shots: describeMotionShots(s),
|
|
3327
|
+
...s.clips ? { clips: Object.fromEntries(Object.entries(s.clips).map(([name, c]) => [name, { ...c.description ? { description: c.description } : {}, commands: c.run.length, length: Math.round(motionClipLayout(s, name, "$").length) }])) } : {},
|
|
3214
3328
|
...s.controls ? { controls: s.controls } : {},
|
|
3215
3329
|
...s.presets ? { presets: s.presets } : {},
|
|
3216
3330
|
issues: issueSummary(controller.getState().issues),
|
|
@@ -3312,9 +3426,9 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
3312
3426
|
handler: ({ from = 0, to, limit = 200 }) => {
|
|
3313
3427
|
const s = scene();
|
|
3314
3428
|
const timeline = compileMotionScene(s);
|
|
3315
|
-
const words = _nullishCoalesce(_optionalChain([s, 'access',
|
|
3429
|
+
const words = _nullishCoalesce(_optionalChain([s, 'access', _86 => _86.narration, 'optionalAccess', _87 => _87.words]), () => ( []));
|
|
3316
3430
|
const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
|
|
3317
|
-
const shotAt = (ms) => _optionalChain([timeline, 'access',
|
|
3431
|
+
const shotAt = (ms) => _optionalChain([timeline, 'access', _88 => _88.shots, 'access', _89 => _89.find, 'call', _90 => _90((x) => ms >= x.start && ms < x.end), 'optionalAccess', _91 => _91.id]);
|
|
3318
3432
|
const beats = [];
|
|
3319
3433
|
if (s.beat && s.beat.bpm > 0) {
|
|
3320
3434
|
const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
|
|
@@ -3456,6 +3570,68 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
3456
3570
|
return apply(edits, "Fit to length", () => report);
|
|
3457
3571
|
}
|
|
3458
3572
|
};
|
|
3573
|
+
const setClip = {
|
|
3574
|
+
name: "set_animation_clip",
|
|
3575
|
+
title: "Define a reusable clip",
|
|
3576
|
+
description: `Defines, replaces or (with clip null) deletes a named clip: commands written against the target "$" that a play command plays on any node, e.g. a "pop-settle" or "attention" used many times. One undo step. Returns the clip's length and the timing.`,
|
|
3577
|
+
inputSchema: object(
|
|
3578
|
+
{
|
|
3579
|
+
name: { type: "string", pattern: "^[A-Za-z_][\\w-]*$", description: 'Clip name, e.g. "pop-settle".' },
|
|
3580
|
+
clip: {
|
|
3581
|
+
anyOf: [object({ description: { type: "string", description: "What it does, in a few words." }, run: { type: "array", items: MOTION_COMMAND_SCHEMA, minItems: 1, description: 'Commands, timed as in a shot; target "$" for the node it is played on. Clips cannot play clips.' } }, ["run"]), { type: "null" }],
|
|
3582
|
+
description: "The clip, or null to delete it (refused while a play command uses it)."
|
|
3583
|
+
}
|
|
3584
|
+
},
|
|
3585
|
+
["name", "clip"]
|
|
3586
|
+
),
|
|
3587
|
+
annotations: { title: "Define a reusable clip", ...WRITE_IDEMPOTENT },
|
|
3588
|
+
handler: ({ name, clip }) => apply({ op: "set_clip", name, clip }, clip === null ? "Delete clip" : "Set clip", (s) => clip === null ? { deleted: name } : { clip: name, length: Math.round(motionClipLayout(s, name, "$").length) })
|
|
3589
|
+
};
|
|
3590
|
+
const copyAnimation = {
|
|
3591
|
+
name: "copy_animation",
|
|
3592
|
+
title: "Copy one node's animation to others",
|
|
3593
|
+
description: 'Copies every command that animates "from" (in all shots, or the ones given) so it also animates "to": each copy goes right after its original and starts with it. One undo step. Returns how many commands were copied per shot. For motion you will reuse often, a clip (set_animation_clip + play) is tidier.',
|
|
3594
|
+
inputSchema: object(
|
|
3595
|
+
{
|
|
3596
|
+
from: { type: "string", minLength: 1, description: "Element id whose commands to copy." },
|
|
3597
|
+
to: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1, description: "Element ids to give the same motion." },
|
|
3598
|
+
shots: { type: "array", items: SHOT_ID, description: "Only these shots (default all)." },
|
|
3599
|
+
stagger: MS("Start each copy this much after the one before (default 0)")
|
|
3600
|
+
},
|
|
3601
|
+
["from", "to"]
|
|
3602
|
+
),
|
|
3603
|
+
annotations: { title: "Copy one node's animation to others", ...WRITE },
|
|
3604
|
+
handler: ({ from, to, shots, stagger }) => {
|
|
3605
|
+
const s = scene();
|
|
3606
|
+
const ids = describeMotionElements(s).map((e) => e.id);
|
|
3607
|
+
for (const id of [from, ...to]) if (!ids.includes(id)) throw new Error(`There is no element ${namedWithSuggestion(id, ids)}. Nothing was changed.`);
|
|
3608
|
+
if (to.includes(from)) throw new Error(`"to" includes "${from}" itself; give other elements. Nothing was changed.`);
|
|
3609
|
+
const only = shots ? new Set(shots) : null;
|
|
3610
|
+
if (only) {
|
|
3611
|
+
for (const id of only) if (!s.shots.some((x) => x.id === id)) throw new Error(`There is no shot ${namedWithSuggestion(id, s.shots.map((x) => x.id))}. Nothing was changed.`);
|
|
3612
|
+
}
|
|
3613
|
+
const copied = {};
|
|
3614
|
+
const edits = [];
|
|
3615
|
+
for (const shot of s.shots) {
|
|
3616
|
+
if (only && !only.has(shot.id)) continue;
|
|
3617
|
+
const run = [];
|
|
3618
|
+
let n = 0;
|
|
3619
|
+
for (const c of shot.run) {
|
|
3620
|
+
run.push(c);
|
|
3621
|
+
if (c.type === "wait" || !c.targets.includes(from)) continue;
|
|
3622
|
+
const { delay: _delay, ...rest } = c;
|
|
3623
|
+
run.push({ ...rest, targets: to, with: "previous", ...stagger ? { stagger } : {} });
|
|
3624
|
+
n++;
|
|
3625
|
+
}
|
|
3626
|
+
if (n) {
|
|
3627
|
+
copied[shot.id] = n;
|
|
3628
|
+
edits.push({ op: "update_shot", shot: shot.id, set: { run } });
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
if (!edits.length) throw new Error(`No command animates "${from}"${only ? " in those shots" : ""}; nothing to copy.`);
|
|
3632
|
+
return apply(edits, "Copy animation", () => ({ copied }));
|
|
3633
|
+
}
|
|
3634
|
+
};
|
|
3459
3635
|
const insertShots = {
|
|
3460
3636
|
name: "insert_shots",
|
|
3461
3637
|
title: "Add shots",
|
|
@@ -3502,7 +3678,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
3502
3678
|
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"]),
|
|
3503
3679
|
annotations: { title: "Add commands to a shot", ...WRITE },
|
|
3504
3680
|
handler: ({ shot, commands, at }) => {
|
|
3505
|
-
const before = _nullishCoalesce(_optionalChain([scene, 'call',
|
|
3681
|
+
const before = _nullishCoalesce(_optionalChain([scene, 'call', _92 => _92(), 'access', _93 => _93.shots, 'access', _94 => _94.find, 'call', _95 => _95((s) => s.id === shot), 'optionalAccess', _96 => _96.run, 'access', _97 => _97.length]), () => ( 0));
|
|
3506
3682
|
const first = _nullishCoalesce(at, () => ( before));
|
|
3507
3683
|
return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
|
|
3508
3684
|
const shots = describeMotionShots(s);
|
|
@@ -3632,7 +3808,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
3632
3808
|
}
|
|
3633
3809
|
};
|
|
3634
3810
|
const reads = [getAnimation, getShot, findElements, getMotionPath, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, exportLottie, seekAnimation];
|
|
3635
|
-
const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, moveCommand, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
3811
|
+
const writes = [setArtworkSvg, setClip, copyAnimation, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, moveCommand, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
3636
3812
|
return {
|
|
3637
3813
|
id: _nullishCoalesce(options.id, () => ( "animation")),
|
|
3638
3814
|
namespace: options.namespace,
|
|
@@ -3664,6 +3840,7 @@ Commands
|
|
|
3664
3840
|
- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.
|
|
3665
3841
|
- enter also reveals through a clip over the target's box: wipe (behind an edge travelling "direction", default right), iris (from the centre; "shape": circle or box) and split (from a line through the middle; "orientation": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.
|
|
3666
3842
|
- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.
|
|
3843
|
+
- Reuse motion: set_animation_clip defines a clip (commands targeting "$"), and { "type": "play", "clip": name, "targets": [...] } plays it on any nodes (with stagger and speed). copy_animation gives other nodes one node's motion.
|
|
3667
3844
|
- move takes "via" waypoints for a curved, smooth path that passes exactly through each point (steady speed by default); use it instead of chaining moves, which stop at each point.
|
|
3668
3845
|
- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
|
|
3669
3846
|
- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with "rotate", moves made meanwhile turn with the path).
|
|
@@ -3690,7 +3867,7 @@ var TIMING = /* @__PURE__ */ new Set(["duration", "delay", "stagger", "with", "e
|
|
|
3690
3867
|
var COLORS = /* @__PURE__ */ new Set(["fill", "stroke", "color"]);
|
|
3691
3868
|
var NODES = /* @__PURE__ */ new Set(["into", "path"]);
|
|
3692
3869
|
function branch(type) {
|
|
3693
|
-
return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access',
|
|
3870
|
+
return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access', _98 => _98.properties, 'optionalAccess', _99 => _99.type, 'optionalAccess', _100 => _100.const]) === type);
|
|
3694
3871
|
}
|
|
3695
3872
|
function unitOf(type, name) {
|
|
3696
3873
|
if (name === "duration" || name === "delay" || name === "stagger") return "ms";
|
|
@@ -3725,7 +3902,7 @@ function kindOf(name, schema) {
|
|
|
3725
3902
|
}
|
|
3726
3903
|
function motionCommandFields(type) {
|
|
3727
3904
|
const schema = branch(type);
|
|
3728
|
-
if (!_optionalChain([schema, 'optionalAccess',
|
|
3905
|
+
if (!_optionalChain([schema, 'optionalAccess', _101 => _101.properties])) return [];
|
|
3729
3906
|
const required = new Set(_nullishCoalesce(schema.required, () => ( [])));
|
|
3730
3907
|
const fields = Object.entries(schema.properties).filter(([name]) => name !== "type").map(([name, s]) => {
|
|
3731
3908
|
const kind = kindOf(name, s);
|
|
@@ -3782,7 +3959,7 @@ function defaultMotionCommand(type, targets, scene) {
|
|
|
3782
3959
|
if (type === "wait") return { type: "wait", duration: 500 };
|
|
3783
3960
|
if (!targets.length) return { error: "Pick a target first: click an element on the stage." };
|
|
3784
3961
|
const nodes = [...indexMotionNodes(scene.artwork.nodes).values()];
|
|
3785
|
-
const other = (pred) => _optionalChain([nodes, 'access',
|
|
3962
|
+
const other = (pred) => _optionalChain([nodes, 'access', _102 => _102.find, 'call', _103 => _103((e) => !targets.includes(e.node.id) && pred(e)), 'optionalAccess', _104 => _104.node, 'access', _105 => _105.id]);
|
|
3786
3963
|
const base = { targets: [...targets] };
|
|
3787
3964
|
switch (type) {
|
|
3788
3965
|
case "scale":
|
|
@@ -3803,14 +3980,18 @@ function defaultMotionCommand(type, targets, scene) {
|
|
|
3803
3980
|
const into = _nullishCoalesce(other((e) => e.node.type === "group" && e.node.visible === false), () => ( other((e) => e.node.type === "group")));
|
|
3804
3981
|
return into ? { type, ...base, into } : { error: "Match needs another group to become; group the end state (it can stay hidden)." };
|
|
3805
3982
|
}
|
|
3983
|
+
case "play": {
|
|
3984
|
+
const clip = Object.keys(_nullishCoalesce(scene.clips, () => ( {})))[0];
|
|
3985
|
+
return clip ? { type, ...base, clip } : { error: 'Play needs a clip; define one first (set_animation_clip), with "$" as its target.' };
|
|
3986
|
+
}
|
|
3806
3987
|
case "follow": {
|
|
3807
3988
|
const path = _nullishCoalesce(other((e) => e.node.type === "path" && e.node.visible === false), () => ( other((e) => e.node.type === "path")));
|
|
3808
3989
|
return path ? { type, ...base, path } : { error: "Follow needs a path to travel along; draw one (it can stay hidden)." };
|
|
3809
3990
|
}
|
|
3810
3991
|
case "effect": {
|
|
3811
3992
|
for (const id of targets) {
|
|
3812
|
-
const node = _optionalChain([nodes, 'access',
|
|
3813
|
-
for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess',
|
|
3993
|
+
const node = _optionalChain([nodes, 'access', _106 => _106.find, 'call', _107 => _107((e) => e.node.id === id), 'optionalAccess', _108 => _108.node]);
|
|
3994
|
+
for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess', _109 => _109.effects]), () => ( []))) {
|
|
3814
3995
|
const param = Object.entries(effect.params).find(([, v]) => typeof v === "number");
|
|
3815
3996
|
if (param) return { type, ...base, effect: effect.id, param: param[0], to: param[1] + 1 };
|
|
3816
3997
|
}
|
|
@@ -3835,11 +4016,11 @@ function motionCommandRows(scene, shotId) {
|
|
|
3835
4016
|
return scene.shots[index].run.map((command2, i) => {
|
|
3836
4017
|
const t = timeline.commands.find((c) => c.shotIndex === index && c.command === i);
|
|
3837
4018
|
const primitive = motionFlairPrimitive(command2);
|
|
3838
|
-
return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess',
|
|
4019
|
+
return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _110 => _110.start]), () => ( 0)), end: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _111 => _111.end]), () => ( 0)), ...primitive ? { flairPrimitive: primitive } : {} };
|
|
3839
4020
|
});
|
|
3840
4021
|
}
|
|
3841
4022
|
function duplicateMotionCommandEdit(scene, shot, command2) {
|
|
3842
|
-
const run = _nullishCoalesce(_optionalChain([scene, 'access',
|
|
4023
|
+
const run = _nullishCoalesce(_optionalChain([scene, 'access', _112 => _112.shots, 'access', _113 => _113.find, 'call', _114 => _114((s) => s.id === shot), 'optionalAccess', _115 => _115.run]), () => ( []));
|
|
3843
4024
|
return { op: "add_commands", shot, commands: run[command2] ? [structuredClone(run[command2])] : [], at: command2 + 1 };
|
|
3844
4025
|
}
|
|
3845
4026
|
var MOTION_EASE_CHOICES = [
|
|
@@ -3924,7 +4105,7 @@ function motionCueFromKey(key) {
|
|
|
3924
4105
|
function motionCueChoices(scene, limit = 256) {
|
|
3925
4106
|
const choices = [];
|
|
3926
4107
|
const s = (ms) => `${(ms / 1e3).toFixed(2)} s`;
|
|
3927
|
-
const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access',
|
|
4108
|
+
const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access', _116 => _116.narration, 'optionalAccess', _117 => _117.words, 'access', _118 => _118.map, 'call', _119 => _119((w) => _nullishCoalesce(w.end, () => ( w.start)))]), () => ( [0]))) + 2e3;
|
|
3928
4109
|
if (scene.beat && scene.beat.bpm > 0) {
|
|
3929
4110
|
for (let beat = 0; choices.length < limit; beat++) {
|
|
3930
4111
|
const time = motionBeatTime(scene.beat, beat);
|
|
@@ -3934,7 +4115,7 @@ function motionCueChoices(scene, limit = 256) {
|
|
|
3934
4115
|
choices.push({ value: `beat:${beat}`, label: `Beat ${beat} (bar ${at.bar}.${at.beatInBar}) \xB7 ${s(time)}`, time });
|
|
3935
4116
|
}
|
|
3936
4117
|
}
|
|
3937
|
-
_optionalChain([scene, 'access',
|
|
4118
|
+
_optionalChain([scene, 'access', _120 => _120.narration, 'optionalAccess', _121 => _121.words, 'access', _122 => _122.forEach, 'call', _123 => _123((w, i) => {
|
|
3938
4119
|
if (choices.length < limit * 2) choices.push({ value: `word:${i}`, label: `Word ${i} \u201C${w.text}\u201D \xB7 ${s(w.start)}`, time: w.start });
|
|
3939
4120
|
})]);
|
|
3940
4121
|
return choices;
|
|
@@ -3943,7 +4124,7 @@ function motionShotCueTime(scene, cue) {
|
|
|
3943
4124
|
return cue ? motionCueTime(scene, cue) : void 0;
|
|
3944
4125
|
}
|
|
3945
4126
|
function formatMotionNarration(narration) {
|
|
3946
|
-
return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess',
|
|
4127
|
+
return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess', _124 => _124.words]), () => ( []))).map((w) => `${w.start} ${w.text}`).join("\n");
|
|
3947
4128
|
}
|
|
3948
4129
|
function parseMotionNarration(text) {
|
|
3949
4130
|
const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -4080,5 +4261,8 @@ function parseMotionNarration(text) {
|
|
|
4080
4261
|
|
|
4081
4262
|
|
|
4082
4263
|
|
|
4083
|
-
|
|
4264
|
+
|
|
4265
|
+
|
|
4266
|
+
|
|
4267
|
+
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_CLIP_TARGET = MOTION_CLIP_TARGET; 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_CHOICES = MOTION_EASE_CHOICES; 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.MOTION_TRIGGERS = MOTION_TRIGGERS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.clipsShapeProblems = clipsShapeProblems; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.defaultMotionCommand = defaultMotionCommand; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.duplicateMotionCommandEdit = duplicateMotionCommandEdit; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.fitMotionSceneEdits = fitMotionSceneEdits; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.formatMotionNarration = formatMotionNarration; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.highlightMotionSvg = highlightMotionSvg; 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.motionClipLayout = motionClipLayout; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandFields = motionCommandFields; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCommandRows = motionCommandRows; exports.motionCommandSummary = motionCommandSummary; exports.motionCueChoices = motionCueChoices; exports.motionCueFromKey = motionCueFromKey; exports.motionCueKey = motionCueKey; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionEaseKey = motionEaseKey; exports.motionEasingCurve = motionEasingCurve; exports.motionEasingGuides = motionEasingGuides; exports.motionEasingPath = motionEasingPath; exports.motionFieldEdit = motionFieldEdit; exports.motionFieldLabel = motionFieldLabel; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionGhosts = motionGhosts; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionMovingIds = motionMovingIds; exports.motionNodeAtElement = motionNodeAtElement; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPath = motionPath; exports.motionPathLength = motionPathLength; exports.motionPathsSvg = motionPathsSvg; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionRulerTicks = motionRulerTicks; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotCueTime = motionShotCueTime; exports.motionShotTimeScale = motionShotTimeScale; exports.motionStageSvg = motionStageSvg; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; exports.parseMotionFieldInput = parseMotionFieldInput; exports.parseMotionNarration = parseMotionNarration; exports.parseMotionScene = parseMotionScene; exports.resolveMotionCamera = resolveMotionCamera; exports.segmentProgress = segmentProgress; exports.segmentValue = segmentValue; exports.shotShapeProblems = shotShapeProblems; exports.smoothMotionLegs = smoothMotionLegs; exports.smoothMotionTimes = smoothMotionTimes; exports.suggestName = suggestName; exports.toFlairEffectDocument = toFlairEffectDocument; exports.toFlairSvgMotion = toFlairSvgMotion; exports.toFlairVectorEffect = toFlairVectorEffect; exports.toLottie = toLottie; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
|
|
4084
4268
|
//# sourceMappingURL=index.cjs.map
|