@pixerate/editor 0.59.0 → 0.61.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 +2 -0
- package/dist/agent/index.cjs +111 -38
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +30 -2
- package/dist/agent/index.d.ts +30 -2
- package/dist/agent/index.js +96 -23
- package/dist/agent/index.js.map +1 -1
- package/dist/agent-caret-BmYhQF7L.d.cts +106 -0
- package/dist/agent-caret-BmYhQF7L.d.ts +106 -0
- package/dist/animation-editor/index.cjs +197 -82
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +136 -3
- package/dist/animation-editor/index.d.ts +136 -3
- package/dist/animation-editor/index.js +163 -48
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/{chunk-LMUDHBWR.js → chunk-4YFMSPBU.js} +2 -2
- package/dist/{chunk-3I6V7PI4.cjs → chunk-7ZSU4MQZ.cjs} +45 -45
- package/dist/{chunk-3I6V7PI4.cjs.map → chunk-7ZSU4MQZ.cjs.map} +1 -1
- package/dist/{chunk-WLVZ235X.js → chunk-OK7T2LHK.js} +229 -2
- package/dist/chunk-OK7T2LHK.js.map +1 -0
- package/dist/{chunk-AHCENCU4.cjs → chunk-T4XWVPJK.cjs} +253 -26
- package/dist/chunk-T4XWVPJK.cjs.map +1 -0
- package/dist/extensions/index.cjs +27 -3
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +2 -1
- package/dist/extensions/index.d.ts +2 -1
- package/dist/extensions/index.js +26 -2
- package/dist/{index-CTCc3m3C.d.ts → index-B16wbOn_.d.ts} +1 -0
- package/dist/{index-BcHyG0Wq.d.cts → index-BAt14aIU.d.cts} +1 -0
- package/dist/index.cjs +32 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AHCENCU4.cjs.map +0 -1
- package/dist/chunk-WLVZ235X.js.map +0 -1
- /package/dist/{chunk-LMUDHBWR.js.map → chunk-4YFMSPBU.js.map} +0 -0
|
@@ -63,6 +63,7 @@ var MOTION_COMMAND_TYPES = [
|
|
|
63
63
|
"fade",
|
|
64
64
|
"style",
|
|
65
65
|
"morph",
|
|
66
|
+
"match",
|
|
66
67
|
"follow",
|
|
67
68
|
"pulse",
|
|
68
69
|
"shake",
|
|
@@ -74,6 +75,7 @@ var MOTION_COMMAND_TYPES = [
|
|
|
74
75
|
"wait"
|
|
75
76
|
];
|
|
76
77
|
var MOTION_ENTER_STYLES = ["fade", "pop", "grow", "slide", "draw", "zoom", "rise", "wipe", "iris", "split"];
|
|
78
|
+
var MOTION_BLENDS = ["replace", "add"];
|
|
77
79
|
var MOTION_REPEAT_MODES = ["loop", "mirror", "offset"];
|
|
78
80
|
var MOTION_EXIT_STYLES = ["fade", "shrink", "slide", "undraw", "wipe"];
|
|
79
81
|
var MOTION_IRIS_SHAPES = ["circle", "box"];
|
|
@@ -88,6 +90,7 @@ var MOTION_DEFAULT_DURATIONS = {
|
|
|
88
90
|
fade: 400,
|
|
89
91
|
style: 500,
|
|
90
92
|
morph: 900,
|
|
93
|
+
match: 900,
|
|
91
94
|
follow: 1400,
|
|
92
95
|
pulse: 600,
|
|
93
96
|
shake: 500,
|
|
@@ -132,6 +135,8 @@ var MOTION_ISSUE_CODES = {
|
|
|
132
135
|
"not-a-path": { severity: "error", wording: "A follow path must be a path node, not a group." },
|
|
133
136
|
"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." },
|
|
134
137
|
"unknown-shape": { severity: "error", wording: "A morph names a shape the artwork does not have." },
|
|
138
|
+
"match-self": { severity: "error", wording: "A match turns a group into itself or into a group it holds (or that holds it)." },
|
|
139
|
+
"match-unpaired": { severity: "note", wording: `Parts of a match's "into" group have no partner in the target, so they do not appear.` },
|
|
135
140
|
"unknown-effect": { severity: "error", wording: "An effect command names an effect the node does not have." },
|
|
136
141
|
"unknown-effect-param": { severity: "error", wording: "An effect command names a parameter that is not a number of that effect." },
|
|
137
142
|
"unknown-frame": { severity: "error", wording: "A camera frames a node the artwork does not have." },
|
|
@@ -209,18 +214,19 @@ var COMMAND_KEYS = {
|
|
|
209
214
|
enter: [...BASE_KEYS, "style", "direction", "distance", "scale", "shape", "orientation"],
|
|
210
215
|
exit: [...BASE_KEYS, "style", "direction", "distance"],
|
|
211
216
|
draw: [...BASE_KEYS, "from", "to"],
|
|
212
|
-
move: [...BASE_KEYS, "by", "to"],
|
|
213
|
-
rotate: [...BASE_KEYS, "by", "to"],
|
|
214
|
-
scale: [...BASE_KEYS, "to", "geometric"],
|
|
217
|
+
move: [...BASE_KEYS, "by", "to", "blend"],
|
|
218
|
+
rotate: [...BASE_KEYS, "by", "to", "blend"],
|
|
219
|
+
scale: [...BASE_KEYS, "to", "geometric", "blend"],
|
|
215
220
|
fade: [...BASE_KEYS, "to"],
|
|
216
221
|
style: [...BASE_KEYS, "fill", "stroke", "strokeWidth"],
|
|
217
222
|
morph: [...BASE_KEYS, "into"],
|
|
223
|
+
match: [...BASE_KEYS, "into"],
|
|
218
224
|
follow: [...BASE_KEYS, "path", "rotate", "from", "to"],
|
|
219
|
-
pulse: [...BASE_KEYS, "amount", "count"],
|
|
220
|
-
shake: [...BASE_KEYS, "amount", "count", "axis"],
|
|
221
|
-
teeter: [...BASE_KEYS, "angle", "count"],
|
|
222
|
-
wave: [...BASE_KEYS, "amount", "count"],
|
|
223
|
-
wiggle: [...BASE_KEYS, "amount", "frequency", "octaves", "rotate", "seed"],
|
|
225
|
+
pulse: [...BASE_KEYS, "amount", "count", "blend"],
|
|
226
|
+
shake: [...BASE_KEYS, "amount", "count", "axis", "blend"],
|
|
227
|
+
teeter: [...BASE_KEYS, "angle", "count", "blend"],
|
|
228
|
+
wave: [...BASE_KEYS, "amount", "count", "blend"],
|
|
229
|
+
wiggle: [...BASE_KEYS, "amount", "frequency", "octaves", "rotate", "seed", "blend"],
|
|
224
230
|
"color-pulse": [...BASE_KEYS, "color", "paint", "count"],
|
|
225
231
|
effect: [...BASE_KEYS, "effect", "param", "to"],
|
|
226
232
|
wait: ["type", "duration", "with", "note"]
|
|
@@ -264,6 +270,7 @@ function commandShapeProblems(command2, where) {
|
|
|
264
270
|
if (type === "enter" || type === "exit") p(`"repeat" is not for ${type} (it brings things in or out once); repeat an emphasis or a move instead.`);
|
|
265
271
|
else if (!(Number.isInteger(command2.repeat) && command2.repeat >= 1 && command2.repeat <= 1e3)) p(`"repeat" must be a whole number from 1 to 1000.`);
|
|
266
272
|
}
|
|
273
|
+
if (command2.blend !== void 0 && !MOTION_BLENDS.includes(command2.blend)) p(`"blend" must be "replace" (the change takes over) or "add" (a layer on top).`);
|
|
267
274
|
if (command2.repeatMode !== void 0 && !MOTION_REPEAT_MODES.includes(command2.repeatMode)) p(`"repeatMode" must be one of ${MOTION_REPEAT_MODES.join(", ")}.`);
|
|
268
275
|
if (command2.steps !== void 0 && !(isNum(command2.steps) && command2.steps > 0 && command2.steps <= 120)) p(`"steps" must be frames per second from above 0 to 120 (12 holds each pose for two frames at 24 fps).`);
|
|
269
276
|
switch (type) {
|
|
@@ -309,7 +316,8 @@ function commandShapeProblems(command2, where) {
|
|
|
309
316
|
if (command2.strokeWidth !== void 0 && !(isNum(command2.strokeWidth) && command2.strokeWidth >= 0)) p(`"strokeWidth" must be a number \u2265 0.`);
|
|
310
317
|
break;
|
|
311
318
|
case "morph":
|
|
312
|
-
|
|
319
|
+
case "match":
|
|
320
|
+
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"}.`);
|
|
313
321
|
break;
|
|
314
322
|
case "follow":
|
|
315
323
|
if (typeof command2.path !== "string" || !command2.path) p(`"path" must be the id of a path node.`);
|
|
@@ -418,6 +426,53 @@ function motionSceneToJson(scene, space = 2) {
|
|
|
418
426
|
var isMotionShot = (shot) => shotShapeProblems(shot, "Shot").length === 0;
|
|
419
427
|
var isMotionCommand = (command2) => commandShapeProblems(command2, "Command").length === 0;
|
|
420
428
|
|
|
429
|
+
// src/animation-editor/match.ts
|
|
430
|
+
var SEPARATOR = /[-_/.:]/;
|
|
431
|
+
function localId(id, groupId) {
|
|
432
|
+
if (groupId && id.includes(groupId)) return id.split(groupId).join("").replace(/^[-_/.:]+|[-_/.:]+$/g, "");
|
|
433
|
+
const parts = id.split(SEPARATOR);
|
|
434
|
+
return parts[parts.length - 1] ?? id;
|
|
435
|
+
}
|
|
436
|
+
function matchMotionNodes(from, into) {
|
|
437
|
+
const out = { pairs: [], unpairedFrom: [], unpairedInto: [] };
|
|
438
|
+
const visit = (a, b) => {
|
|
439
|
+
const aKids = a.type === "group" ? a.children : [];
|
|
440
|
+
const bKids = b.type === "group" ? b.children : [];
|
|
441
|
+
if (!aKids.length || !bKids.length) {
|
|
442
|
+
out.pairs.push([a.id, b.id]);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const left = new Set(aKids);
|
|
446
|
+
const right = new Set(bKids);
|
|
447
|
+
const pair = (key) => {
|
|
448
|
+
for (const x of [...left]) {
|
|
449
|
+
const k = key(x, a);
|
|
450
|
+
if (!k) continue;
|
|
451
|
+
const y = [...right].find((n) => key(n, b) === k);
|
|
452
|
+
if (!y) continue;
|
|
453
|
+
left.delete(x);
|
|
454
|
+
right.delete(y);
|
|
455
|
+
if (x.type === "group" && y.type === "group") visit(x, y);
|
|
456
|
+
else out.pairs.push([x.id, y.id]);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
pair((n) => n.name?.trim() || void 0);
|
|
460
|
+
pair((n, g) => localId(n.id, g.id) || void 0);
|
|
461
|
+
for (const x of [...left]) {
|
|
462
|
+
const y = [...right].find((n) => n.type === x.type);
|
|
463
|
+
if (!y) continue;
|
|
464
|
+
left.delete(x);
|
|
465
|
+
right.delete(y);
|
|
466
|
+
if (x.type === "group" && y.type === "group") visit(x, y);
|
|
467
|
+
else out.pairs.push([x.id, y.id]);
|
|
468
|
+
}
|
|
469
|
+
out.unpairedFrom.push(...[...left].map((n) => n.id));
|
|
470
|
+
out.unpairedInto.push(...[...right].map((n) => n.id));
|
|
471
|
+
};
|
|
472
|
+
visit(from, into);
|
|
473
|
+
return out;
|
|
474
|
+
}
|
|
475
|
+
|
|
421
476
|
// src/animation-editor/compile.ts
|
|
422
477
|
var indexCache = /* @__PURE__ */ new WeakMap();
|
|
423
478
|
function indexMotionNodes(nodes) {
|
|
@@ -595,6 +650,20 @@ function trackValue(track, time, raw = false) {
|
|
|
595
650
|
return first.from;
|
|
596
651
|
}
|
|
597
652
|
var motionTrackKey = (node, property) => `${node}\0${property}`;
|
|
653
|
+
var motionAddTrackKey = (node, property) => `${node}\0${property}\0add`;
|
|
654
|
+
var MOTION_ADDITIVE_PROPERTIES = ["x", "y", "rotation", "scaleX", "scaleY"];
|
|
655
|
+
function withMotionLayers(timeline, node, property, base, time, raw = false) {
|
|
656
|
+
const layers = timeline.tracks.get(motionAddTrackKey(node, property));
|
|
657
|
+
if (!layers) return base;
|
|
658
|
+
const scale = property === "scaleX" || property === "scaleY";
|
|
659
|
+
let v = base;
|
|
660
|
+
for (const layer of layers) {
|
|
661
|
+
if (layer.start > time) continue;
|
|
662
|
+
const x = Number(segmentValue(layer, time, raw));
|
|
663
|
+
if (Number.isFinite(x)) v = scale ? v * x : v + x;
|
|
664
|
+
}
|
|
665
|
+
return v;
|
|
666
|
+
}
|
|
598
667
|
function fitMotionRect(rect, aspect) {
|
|
599
668
|
const cx = rect.x + rect.width / 2;
|
|
600
669
|
const cy = rect.y + rect.height / 2;
|
|
@@ -698,7 +767,7 @@ function compileMotionScene(scene) {
|
|
|
698
767
|
return v ?? motionRestValue(nodes.get(node), property, artwork);
|
|
699
768
|
};
|
|
700
769
|
const push = (segment) => {
|
|
701
|
-
const key = motionTrackKey(segment.node, segment.property);
|
|
770
|
+
const key = segment.blend === "add" ? motionAddTrackKey(segment.node, segment.property) : motionTrackKey(segment.node, segment.property);
|
|
702
771
|
let track = tracks.get(key);
|
|
703
772
|
if (!track) tracks.set(key, track = []);
|
|
704
773
|
track.push(segment);
|
|
@@ -837,18 +906,26 @@ function compileMotionScene(scene) {
|
|
|
837
906
|
dx = target.x - (entry.center.x + num("x"));
|
|
838
907
|
dy = target.y - (entry.center.y + num("y"));
|
|
839
908
|
}
|
|
840
|
-
|
|
841
|
-
|
|
909
|
+
if (command2.blend === "add") {
|
|
910
|
+
seg("x", dx, e, start, end, { from: 0, blend: "add" });
|
|
911
|
+
seg("y", dy, e, start, end, { from: 0, blend: "add" });
|
|
912
|
+
} else {
|
|
913
|
+
seg("x", num("x") + dx, e);
|
|
914
|
+
seg("y", num("y") + dy, e);
|
|
915
|
+
}
|
|
842
916
|
break;
|
|
843
917
|
}
|
|
844
|
-
case "rotate":
|
|
845
|
-
|
|
918
|
+
case "rotate": {
|
|
919
|
+
const e = ease ?? MOTION_EASE_IN_OUT;
|
|
920
|
+
if (command2.blend === "add") seg("rotation", command2.to !== void 0 ? command2.to - num("rotation") : command2.by ?? 0, e, start, end, { from: 0, blend: "add" });
|
|
921
|
+
else seg("rotation", command2.to ?? num("rotation") + (command2.by ?? 0), e);
|
|
846
922
|
break;
|
|
923
|
+
}
|
|
847
924
|
case "scale": {
|
|
848
925
|
const to = typeof command2.to === "number" ? { x: command2.to, y: command2.to } : command2.to;
|
|
849
|
-
const
|
|
850
|
-
seg("scaleX", to.x, ease ?? MOTION_EASE_IN_OUT, start, end,
|
|
851
|
-
seg("scaleY", to.y, ease ?? MOTION_EASE_IN_OUT, start, end,
|
|
926
|
+
const extra = { ...command2.geometric ? { geometric: true } : {}, ...command2.blend === "add" ? { from: 1, blend: "add" } : {} };
|
|
927
|
+
seg("scaleX", to.x, ease ?? MOTION_EASE_IN_OUT, start, end, extra);
|
|
928
|
+
seg("scaleY", to.y, ease ?? MOTION_EASE_IN_OUT, start, end, extra);
|
|
852
929
|
break;
|
|
853
930
|
}
|
|
854
931
|
case "fade":
|
|
@@ -862,34 +939,44 @@ function compileMotionScene(scene) {
|
|
|
862
939
|
case "morph":
|
|
863
940
|
seg("shape", command2.into, ease ?? MOTION_EASE_IN_OUT);
|
|
864
941
|
break;
|
|
942
|
+
case "match": {
|
|
943
|
+
const into = nodes.get(command2.into);
|
|
944
|
+
if (!into || command2.into === node) break;
|
|
945
|
+
const match = matchMotionNodes(entry.node, into.node);
|
|
946
|
+
const e = ease ?? MOTION_EASE_IN_OUT;
|
|
947
|
+
for (const [a, b] of match.pairs) push({ node: a, property: "shape", start, end, from: valueAt(a, "shape", start), to: b, ease: e, ...where });
|
|
948
|
+
for (const a of match.unpairedFrom) push({ node: a, property: "opacity", start, end, from: valueAt(a, "opacity", start), to: 0, ease: e, ...where });
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
865
951
|
case "follow":
|
|
866
952
|
seg("follow", command2.to ?? 1, ease ?? MOTION_EASE_IN_OUT, start, end, { from: command2.from ?? 0, path: command2.path, rotate: command2.rotate ?? false });
|
|
867
953
|
break;
|
|
868
954
|
// Emphasis: a change toward a peak that a behaviour swings round (as Flair's primitives do).
|
|
869
|
-
case "pulse":
|
|
870
|
-
|
|
871
|
-
const k = 1 + (command2.amount ?? 0.15);
|
|
872
|
-
seg("scaleX", num("scaleX") * k, ease ?? "linear", start, end, { behavior });
|
|
873
|
-
seg("scaleY", num("scaleY") * k, ease ?? "linear", start, end, { behavior });
|
|
874
|
-
break;
|
|
875
|
-
}
|
|
876
|
-
case "shake": {
|
|
877
|
-
const p = command2.axis === "vertical" ? "y" : "x";
|
|
878
|
-
seg(p, num(p) + (command2.amount ?? 8), ease ?? "linear", start, end, { behavior: { type: "oscillate", count: command2.count ?? 3, decay: 1 } });
|
|
879
|
-
break;
|
|
880
|
-
}
|
|
955
|
+
case "pulse":
|
|
956
|
+
case "shake":
|
|
881
957
|
case "teeter":
|
|
882
|
-
seg("rotation", num("rotation") + (command2.angle ?? 8), ease ?? "linear", start, end, { behavior: { type: "oscillate", count: command2.count ?? 3, decay: 1 } });
|
|
883
|
-
break;
|
|
884
958
|
case "wave":
|
|
885
|
-
seg("y", num("y") - (command2.amount ?? 6), ease ?? "linear", start, end, { behavior: { type: "oscillate", count: command2.count ?? 2 } });
|
|
886
|
-
break;
|
|
887
959
|
case "wiggle": {
|
|
888
|
-
const
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
960
|
+
const add = (command2.blend ?? "add") === "add";
|
|
961
|
+
const swing = (p, by, extra) => {
|
|
962
|
+
const scale = p === "scaleX" || p === "scaleY";
|
|
963
|
+
if (add) seg(p, by, ease ?? "linear", start, end, { from: scale ? 1 : 0, blend: "add", ...extra });
|
|
964
|
+
else seg(p, scale ? num(p) * by : num(p) + by, ease ?? "linear", start, end, extra);
|
|
965
|
+
};
|
|
966
|
+
if (command2.type === "pulse") {
|
|
967
|
+
const behavior = { type: "pulse", count: command2.count ?? 1 };
|
|
968
|
+
swing("scaleX", 1 + (command2.amount ?? 0.15), { behavior });
|
|
969
|
+
swing("scaleY", 1 + (command2.amount ?? 0.15), { behavior });
|
|
970
|
+
} else if (command2.type === "shake") swing(command2.axis === "vertical" ? "y" : "x", command2.amount ?? 8, { behavior: { type: "oscillate", count: command2.count ?? 3, decay: 1 } });
|
|
971
|
+
else if (command2.type === "teeter") swing("rotation", command2.angle ?? 8, { behavior: { type: "oscillate", count: command2.count ?? 3, decay: 1 } });
|
|
972
|
+
else if (command2.type === "wave") swing("y", -(command2.amount ?? 6), { behavior: { type: "oscillate", count: command2.count ?? 2 } });
|
|
973
|
+
else {
|
|
974
|
+
const behavior = { type: "wriggle", frequency: command2.frequency ?? 3, octaves: command2.octaves ?? 2, seed: command2.seed || motionSeedFromId(node) };
|
|
975
|
+
const amount = command2.amount ?? 6;
|
|
976
|
+
swing("x", amount, { behavior, index: 0 });
|
|
977
|
+
swing("y", amount, { behavior, index: 1 });
|
|
978
|
+
if (command2.rotate) swing("rotation", command2.rotate, { behavior, index: 2 });
|
|
979
|
+
}
|
|
893
980
|
break;
|
|
894
981
|
}
|
|
895
982
|
case "color-pulse":
|
|
@@ -1001,9 +1088,9 @@ function followAt(entry, timeline, nodes, time) {
|
|
|
1001
1088
|
function motionNodeOffset(entry, timeline, time, nodes = indexMotionNodes([entry.node]), raw = false, follow = true) {
|
|
1002
1089
|
const read = (p, rest) => {
|
|
1003
1090
|
const v = trackValue(timeline.tracks.get(motionTrackKey(entry.node.id, p)), time, raw);
|
|
1004
|
-
return typeof v === "number" ? v : rest;
|
|
1091
|
+
return withMotionLayers(timeline, entry.node.id, p, typeof v === "number" ? v : rest, time, raw);
|
|
1005
1092
|
};
|
|
1006
|
-
if (!TRANSFORM_PROPS.some((p) => (follow || p !== "follow") && timeline.tracks.has(motionTrackKey(entry.node.id, p)))) return null;
|
|
1093
|
+
if (!TRANSFORM_PROPS.some((p) => (follow || p !== "follow") && (timeline.tracks.has(motionTrackKey(entry.node.id, p)) || timeline.tracks.has(motionAddTrackKey(entry.node.id, p))))) return null;
|
|
1007
1094
|
const dx = read("x", 0);
|
|
1008
1095
|
const dy = read("y", 0);
|
|
1009
1096
|
const rotation = read("rotation", 0);
|
|
@@ -1201,6 +1288,19 @@ function checkMotionScene(scene) {
|
|
|
1201
1288
|
if (command2.type === "follow" && known(command2.path, "unknown-path", "path", where) && nodes.get(command2.path).node.type !== "path")
|
|
1202
1289
|
issues.push(motionIssue("not-a-path", `"${command2.path}" is a group; follow a single path node.`, { ...where, ids: [command2.path] }));
|
|
1203
1290
|
if (command2.type === "morph") known(command2.into, "unknown-shape", "shape", where);
|
|
1291
|
+
if (command2.type === "match" && known(command2.into, "unknown-shape", "group", where)) {
|
|
1292
|
+
const into = nodes.get(command2.into);
|
|
1293
|
+
for (const id of targets) {
|
|
1294
|
+
const entry = nodes.get(id);
|
|
1295
|
+
if (id === command2.into || into.ancestors.includes(id) || entry.ancestors.includes(command2.into)) {
|
|
1296
|
+
issues.push(motionIssue("match-self", `"${id}" cannot match "${command2.into}": one is the other or holds it. Match two separate groups (the second usually hidden).`, { ...where, ids: [id, command2.into] }));
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
const m = matchMotionNodes(entry.node, into.node);
|
|
1300
|
+
if (m.unpairedInto.length)
|
|
1301
|
+
issues.push(motionIssue("match-unpaired", `${m.unpairedInto.map((x) => `"${x}"`).join(", ")} in "${command2.into}" ${m.unpairedInto.length > 1 ? "have" : "has"} no partner in "${id}", so ${m.unpairedInto.length > 1 ? "they do" : "it does"} not appear; add a part with the same name or id to "${id}" (it can start hidden or tiny).`, { ...where, ids: m.unpairedInto }));
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1204
1304
|
const style = command2.type === "enter" || command2.type === "exit" ? command2.style : void 0;
|
|
1205
1305
|
if (style === "wipe" || style === "iris" || style === "split") {
|
|
1206
1306
|
for (const id of targets)
|
|
@@ -1261,6 +1361,7 @@ function checkMotionScene(scene) {
|
|
|
1261
1361
|
});
|
|
1262
1362
|
});
|
|
1263
1363
|
for (const [key, track] of timeline.tracks) {
|
|
1364
|
+
if (key.endsWith("\0add")) continue;
|
|
1264
1365
|
for (let i = 1; i < track.length; i++) {
|
|
1265
1366
|
const a = track[i - 1];
|
|
1266
1367
|
const b = track[i];
|
|
@@ -1610,7 +1711,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1610
1711
|
if (timeline.tracks.has(motionTrackKey(id, "opacity")))
|
|
1611
1712
|
channels.push({ id, kind: "attr", attr: "opacity", segments: [...track(id, "opacity")], exact: plays, value: (t) => round2(Math.min(1, Math.max(0, Number(trackValue(track(id, "opacity"), t))))) });
|
|
1612
1713
|
const followed = follows.some((x) => x.id === id);
|
|
1613
|
-
const transformSegments = TRANSFORM_PROPS2.flatMap((p) => followed && p === "follow" ? [] : track(id, p));
|
|
1714
|
+
const transformSegments = TRANSFORM_PROPS2.flatMap((p) => followed && p === "follow" ? [] : [...track(id, p), ...timeline.tracks.get(motionAddTrackKey(id, p)) ?? []]);
|
|
1614
1715
|
if (transformSegments.length) {
|
|
1615
1716
|
const base = node.transform ? ` matrix(${node.transform.map(f).join(" ")})` : "";
|
|
1616
1717
|
const c = entry.center;
|
|
@@ -2269,6 +2370,11 @@ var BASE = {
|
|
|
2269
2370
|
repeatMode: { type: "string", enum: [...MOTION_REPEAT_MODES], description: "loop (default) restarts each cycle, mirror plays every other one backwards, offset carries on from where the last ended (rotate by 90 with repeat 4 makes a full turn)." },
|
|
2270
2371
|
steps: { type: "number", exclusiveMinimum: 0, maximum: 120, description: `Stepped time: hold on a grid of this many frames per second (12 = "on twos"). Default: the scene's steps, else smooth.` }
|
|
2271
2372
|
};
|
|
2373
|
+
var BLEND = (fallback) => ({
|
|
2374
|
+
type: "string",
|
|
2375
|
+
enum: [...MOTION_BLENDS],
|
|
2376
|
+
description: `"add" makes it a layer over the pose that stacks with other changes (a shake on a slide); "replace" makes it a change the next one takes over from (default ${fallback}).`
|
|
2377
|
+
});
|
|
2272
2378
|
var command = (type, description, extra = {}, required = []) => object({ type: { const: type, description }, ...BASE, ...extra }, ["type", "targets", ...required], description);
|
|
2273
2379
|
var MOTION_COMMAND_SCHEMA = {
|
|
2274
2380
|
description: `A command (one of ${MOTION_COMMAND_TYPES.join(", ")}). Commands in a shot play one after another unless "with": "previous".`,
|
|
@@ -2291,15 +2397,16 @@ var MOTION_COMMAND_SCHEMA = {
|
|
|
2291
2397
|
distance: { type: "number", description: "For slide: document units (default 40)." }
|
|
2292
2398
|
}),
|
|
2293
2399
|
command("draw", "Draws strokes on (or off) to a fraction of their length.", { to: FRACTION("Fraction drawn at the end (default 1)."), from: FRACTION("Fraction drawn at the start (default: what is drawn now).") }),
|
|
2294
|
-
command("move", "Moves targets by an offset or so their centre lands on a point.", { by: POINT("Offset in document units."), to: POINT("Point (document coordinates) for the centre.") }),
|
|
2295
|
-
command("rotate", "Turns targets about their centre (degrees, clockwise).", { by: { type: "number", description: "Degrees to turn by." }, to: { type: "number", description: "Angle to turn to, in degrees from rest." } }),
|
|
2296
|
-
command("scale", "Scales targets about their centre (1 = rest size).", { to: { description: "A factor, or { x, y } factors.", anyOf: [{ type: "number" }, POINT("Factors per axis.")] }, geometric: { type: "boolean", description: "Grow by a steady factor, which reads as an even zoom (default false)." } }, ["to"]),
|
|
2400
|
+
command("move", "Moves targets by an offset or so their centre lands on a point.", { blend: BLEND("replace"), by: POINT("Offset in document units."), to: POINT("Point (document coordinates) for the centre.") }),
|
|
2401
|
+
command("rotate", "Turns targets about their centre (degrees, clockwise).", { blend: BLEND("replace"), by: { type: "number", description: "Degrees to turn by." }, to: { type: "number", description: "Angle to turn to, in degrees from rest." } }),
|
|
2402
|
+
command("scale", "Scales targets about their centre (1 = rest size).", { blend: BLEND("replace"), to: { description: "A factor, or { x, y } factors.", anyOf: [{ type: "number" }, POINT("Factors per axis.")] }, geometric: { type: "boolean", description: "Grow by a steady factor, which reads as an even zoom (default false)." } }, ["to"]),
|
|
2297
2403
|
command("fade", "Changes opacity.", { to: FRACTION("Opacity at the end (0\u20131).") }, ["to"]),
|
|
2298
2404
|
command("style", "Blends solid fill / stroke colours (in OKLab) and stroke width. On a group it changes every path in it.", {
|
|
2299
2405
|
fill: { type: "string", description: "CSS colour (hex, rgb(), rgba())." },
|
|
2300
2406
|
stroke: { type: "string", description: "CSS colour (hex, rgb(), rgba())." },
|
|
2301
2407
|
strokeWidth: { type: "number", minimum: 0, description: "Document units." }
|
|
2302
2408
|
}),
|
|
2409
|
+
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"]),
|
|
2303
2410
|
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"]),
|
|
2304
2411
|
command("follow", "Moves targets along a path node, centre on the path.", {
|
|
2305
2412
|
path: { type: "string", minLength: 1, description: "Id of a path node (it can be hidden)." },
|
|
@@ -2307,15 +2414,17 @@ var MOTION_COMMAND_SCHEMA = {
|
|
|
2307
2414
|
from: FRACTION("Where to start along the path (default 0)."),
|
|
2308
2415
|
to: FRACTION("Where to end along the path (default 1).")
|
|
2309
2416
|
}, ["path"]),
|
|
2310
|
-
command("pulse", "Scales up and back to draw the eye.", { amount: { type: "number", description: "Extra scale at the peak (default 0.15)." }, count: { type: "integer", minimum: 1, description: "Pulses (default 1)." } }),
|
|
2417
|
+
command("pulse", "Scales up and back to draw the eye.", { blend: BLEND("add"), amount: { type: "number", description: "Extra scale at the peak (default 0.15)." }, count: { type: "integer", minimum: 1, description: "Pulses (default 1)." } }),
|
|
2311
2418
|
command("shake", "Shakes side to side (or up and down), settling.", {
|
|
2419
|
+
blend: BLEND("add"),
|
|
2312
2420
|
amount: { type: "number", description: "Document units (default 8)." },
|
|
2313
2421
|
count: { type: "integer", minimum: 1, description: "Shakes (default 3)." },
|
|
2314
2422
|
axis: { type: "string", enum: ["horizontal", "vertical"], description: "Default horizontal." }
|
|
2315
2423
|
}),
|
|
2316
|
-
command("teeter", "Rocks about the centre either way, settling.", { angle: { type: "number", description: "Degrees either way (default 8)." }, count: { type: "integer", minimum: 1, description: "Rocks (default 3)." } }),
|
|
2317
|
-
command("wave", "Bobs up and down without settling, like something floating (default 1000 ms).", { amount: { type: "number", description: "Height in document units (default 6)." }, count: { type: "integer", minimum: 1, description: "Bobs (default 2)." } }),
|
|
2424
|
+
command("teeter", "Rocks about the centre either way, settling.", { blend: BLEND("add"), angle: { type: "number", description: "Degrees either way (default 8)." }, count: { type: "integer", minimum: 1, description: "Rocks (default 3)." } }),
|
|
2425
|
+
command("wave", "Bobs up and down without settling, like something floating (default 1000 ms).", { blend: BLEND("add"), amount: { type: "number", description: "Height in document units (default 6)." }, count: { type: "integer", minimum: 1, description: "Bobs (default 2)." } }),
|
|
2318
2426
|
command("wiggle", "Wanders about its place as smooth seeded noise, easing out of and back to rest (default 1500 ms). The same seed always wanders the same way.", {
|
|
2427
|
+
blend: BLEND("add"),
|
|
2319
2428
|
amount: { type: "number", description: "Up to about this far, in document units (default 6)." },
|
|
2320
2429
|
frequency: { type: "number", exclusiveMinimum: 0, description: "Wanders per second, in Hz (default 3)." },
|
|
2321
2430
|
octaves: { type: "integer", minimum: 1, maximum: 8, description: "Layers of finer detail (default 2)." },
|
|
@@ -2819,8 +2928,9 @@ Commands
|
|
|
2819
2928
|
- 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.
|
|
2820
2929
|
- 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.
|
|
2821
2930
|
- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
|
|
2822
|
-
- morph turns a shape into another node's shape (keep the target hidden); follow moves along a path node (with "rotate", moves made meanwhile turn with the path).
|
|
2931
|
+
- 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).
|
|
2823
2932
|
- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or "axis": "vertical"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).
|
|
2933
|
+
- 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.
|
|
2824
2934
|
- 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.
|
|
2825
2935
|
- Springs ("spring-gentle", "spring-quick", "spring-bouncy", "spring-slow" or { "type": "spring", \u2026 }) feel physical; leave "duration" out and the spring sets it.
|
|
2826
2936
|
- "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.
|
|
@@ -2839,6 +2949,8 @@ export {
|
|
|
2839
2949
|
AnimationEditorController,
|
|
2840
2950
|
FLAIR_EASES,
|
|
2841
2951
|
FLAIR_EXPORT_VERSION,
|
|
2952
|
+
MOTION_ADDITIVE_PROPERTIES,
|
|
2953
|
+
MOTION_BLENDS,
|
|
2842
2954
|
MOTION_CAMERA_MS,
|
|
2843
2955
|
MOTION_COMMAND_SCHEMA,
|
|
2844
2956
|
MOTION_COMMAND_TYPES,
|
|
@@ -2880,6 +2992,8 @@ export {
|
|
|
2880
2992
|
interpolateMotionValue,
|
|
2881
2993
|
isMotionCommand,
|
|
2882
2994
|
isMotionShot,
|
|
2995
|
+
matchMotionNodes,
|
|
2996
|
+
motionAddTrackKey,
|
|
2883
2997
|
motionClipAttrs,
|
|
2884
2998
|
motionCommandDuration,
|
|
2885
2999
|
motionCommandRepeat,
|
|
@@ -2912,6 +3026,7 @@ export {
|
|
|
2912
3026
|
toFlairSvgMotion,
|
|
2913
3027
|
toFlairVectorEffect,
|
|
2914
3028
|
trackValue,
|
|
3029
|
+
withMotionLayers,
|
|
2915
3030
|
withViewBox
|
|
2916
3031
|
};
|
|
2917
3032
|
//# sourceMappingURL=index.js.map
|