@pixerate/editor 0.60.0 → 0.62.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 +4 -0
- package/dist/animation-editor/index.cjs +187 -51
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +85 -4
- package/dist/animation-editor/index.d.ts +85 -4
- package/dist/animation-editor/index.js +153 -17
- package/dist/animation-editor/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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",
|
|
@@ -89,6 +90,7 @@ var MOTION_DEFAULT_DURATIONS = {
|
|
|
89
90
|
fade: 400,
|
|
90
91
|
style: 500,
|
|
91
92
|
morph: 900,
|
|
93
|
+
match: 900,
|
|
92
94
|
follow: 1400,
|
|
93
95
|
pulse: 600,
|
|
94
96
|
shake: 500,
|
|
@@ -133,6 +135,8 @@ var MOTION_ISSUE_CODES = {
|
|
|
133
135
|
"not-a-path": { severity: "error", wording: "A follow path must be a path node, not a group." },
|
|
134
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." },
|
|
135
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.` },
|
|
136
140
|
"unknown-effect": { severity: "error", wording: "An effect command names an effect the node does not have." },
|
|
137
141
|
"unknown-effect-param": { severity: "error", wording: "An effect command names a parameter that is not a number of that effect." },
|
|
138
142
|
"unknown-frame": { severity: "error", wording: "A camera frames a node the artwork does not have." },
|
|
@@ -203,7 +207,7 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
|
|
|
203
207
|
}
|
|
204
208
|
};
|
|
205
209
|
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "target", "controls", "presets"];
|
|
206
|
-
var SHOT_KEYS = ["id", "label", "camera", "run", "hold"];
|
|
210
|
+
var SHOT_KEYS = ["id", "label", "camera", "run", "hold", "speed"];
|
|
207
211
|
var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
|
|
208
212
|
var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
|
|
209
213
|
var COMMAND_KEYS = {
|
|
@@ -216,6 +220,7 @@ var COMMAND_KEYS = {
|
|
|
216
220
|
fade: [...BASE_KEYS, "to"],
|
|
217
221
|
style: [...BASE_KEYS, "fill", "stroke", "strokeWidth"],
|
|
218
222
|
morph: [...BASE_KEYS, "into"],
|
|
223
|
+
match: [...BASE_KEYS, "into"],
|
|
219
224
|
follow: [...BASE_KEYS, "path", "rotate", "from", "to"],
|
|
220
225
|
pulse: [...BASE_KEYS, "amount", "count", "blend"],
|
|
221
226
|
shake: [...BASE_KEYS, "amount", "count", "axis", "blend"],
|
|
@@ -311,7 +316,8 @@ function commandShapeProblems(command2, where) {
|
|
|
311
316
|
if (command2.strokeWidth !== void 0 && !(isNum(command2.strokeWidth) && command2.strokeWidth >= 0)) p(`"strokeWidth" must be a number \u2265 0.`);
|
|
312
317
|
break;
|
|
313
318
|
case "morph":
|
|
314
|
-
|
|
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"}.`);
|
|
315
321
|
break;
|
|
316
322
|
case "follow":
|
|
317
323
|
if (typeof command2.path !== "string" || !command2.path) p(`"path" must be the id of a path node.`);
|
|
@@ -354,6 +360,7 @@ function shotShapeProblems(shot, where) {
|
|
|
354
360
|
const name = typeof shot.id === "string" ? `Shot "${shot.id}"` : where;
|
|
355
361
|
if (shot.label !== void 0 && typeof shot.label !== "string") problems.push(`${name}: "label" must be a string.`);
|
|
356
362
|
if (shot.hold !== void 0 && !(isNum(shot.hold) && shot.hold >= 0)) problems.push(`${name}: "hold" must be a number of milliseconds \u2265 0.`);
|
|
363
|
+
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).`);
|
|
357
364
|
if (shot.camera !== void 0) {
|
|
358
365
|
const c = shot.camera;
|
|
359
366
|
if (!isObject(c)) problems.push(`${name}: "camera" must be an object.`);
|
|
@@ -420,6 +427,53 @@ function motionSceneToJson(scene, space = 2) {
|
|
|
420
427
|
var isMotionShot = (shot) => shotShapeProblems(shot, "Shot").length === 0;
|
|
421
428
|
var isMotionCommand = (command2) => commandShapeProblems(command2, "Command").length === 0;
|
|
422
429
|
|
|
430
|
+
// src/animation-editor/match.ts
|
|
431
|
+
var SEPARATOR = /[-_/.:]/;
|
|
432
|
+
function localId(id, groupId) {
|
|
433
|
+
if (groupId && id.includes(groupId)) return id.split(groupId).join("").replace(/^[-_/.:]+|[-_/.:]+$/g, "");
|
|
434
|
+
const parts = id.split(SEPARATOR);
|
|
435
|
+
return _nullishCoalesce(parts[parts.length - 1], () => ( id));
|
|
436
|
+
}
|
|
437
|
+
function matchMotionNodes(from, into) {
|
|
438
|
+
const out = { pairs: [], unpairedFrom: [], unpairedInto: [] };
|
|
439
|
+
const visit = (a, b) => {
|
|
440
|
+
const aKids = a.type === "group" ? a.children : [];
|
|
441
|
+
const bKids = b.type === "group" ? b.children : [];
|
|
442
|
+
if (!aKids.length || !bKids.length) {
|
|
443
|
+
out.pairs.push([a.id, b.id]);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const left = new Set(aKids);
|
|
447
|
+
const right = new Set(bKids);
|
|
448
|
+
const pair = (key) => {
|
|
449
|
+
for (const x of [...left]) {
|
|
450
|
+
const k = key(x, a);
|
|
451
|
+
if (!k) continue;
|
|
452
|
+
const y = [...right].find((n) => key(n, b) === k);
|
|
453
|
+
if (!y) continue;
|
|
454
|
+
left.delete(x);
|
|
455
|
+
right.delete(y);
|
|
456
|
+
if (x.type === "group" && y.type === "group") visit(x, y);
|
|
457
|
+
else out.pairs.push([x.id, y.id]);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
pair((n) => _optionalChain([n, 'access', _4 => _4.name, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]) || void 0);
|
|
461
|
+
pair((n, g) => localId(n.id, g.id) || void 0);
|
|
462
|
+
for (const x of [...left]) {
|
|
463
|
+
const y = [...right].find((n) => n.type === x.type);
|
|
464
|
+
if (!y) continue;
|
|
465
|
+
left.delete(x);
|
|
466
|
+
right.delete(y);
|
|
467
|
+
if (x.type === "group" && y.type === "group") visit(x, y);
|
|
468
|
+
else out.pairs.push([x.id, y.id]);
|
|
469
|
+
}
|
|
470
|
+
out.unpairedFrom.push(...[...left].map((n) => n.id));
|
|
471
|
+
out.unpairedInto.push(...[...right].map((n) => n.id));
|
|
472
|
+
};
|
|
473
|
+
visit(from, into);
|
|
474
|
+
return out;
|
|
475
|
+
}
|
|
476
|
+
|
|
423
477
|
// src/animation-editor/compile.ts
|
|
424
478
|
var indexCache = /* @__PURE__ */ new WeakMap();
|
|
425
479
|
function indexMotionNodes(nodes) {
|
|
@@ -499,10 +553,10 @@ function motionRevealClip(entry, reveal) {
|
|
|
499
553
|
}
|
|
500
554
|
}
|
|
501
555
|
function motionRestValue(entry, property, artwork) {
|
|
502
|
-
const node = _optionalChain([entry, 'optionalAccess',
|
|
556
|
+
const node = _optionalChain([entry, 'optionalAccess', _7 => _7.node]);
|
|
503
557
|
switch (property) {
|
|
504
558
|
case "opacity":
|
|
505
|
-
return _nullishCoalesce(_optionalChain([node, 'optionalAccess',
|
|
559
|
+
return _nullishCoalesce(_optionalChain([node, 'optionalAccess', _8 => _8.opacity]), () => ( 1));
|
|
506
560
|
case "x":
|
|
507
561
|
case "y":
|
|
508
562
|
case "rotation":
|
|
@@ -520,11 +574,11 @@ function motionRestValue(entry, property, artwork) {
|
|
|
520
574
|
case "stroke":
|
|
521
575
|
return _nullishCoalesce((node && firstPaint(node, property)), () => ( "transparent"));
|
|
522
576
|
case "strokeWidth": {
|
|
523
|
-
const path = _optionalChain([node, 'optionalAccess',
|
|
524
|
-
return _nullishCoalesce(_optionalChain([path, 'optionalAccess',
|
|
577
|
+
const path = _optionalChain([node, 'optionalAccess', _9 => _9.type]) === "path" ? node : node ? _chunkYJLCBA7Vcjs.collectPathNodes.call(void 0, node)[0] : void 0;
|
|
578
|
+
return _nullishCoalesce(_optionalChain([path, 'optionalAccess', _10 => _10.strokeWidth]), () => ( 1));
|
|
525
579
|
}
|
|
526
580
|
case "shape":
|
|
527
|
-
return _nullishCoalesce(_optionalChain([node, 'optionalAccess',
|
|
581
|
+
return _nullishCoalesce(_optionalChain([node, 'optionalAccess', _11 => _11.id]), () => ( ""));
|
|
528
582
|
case "clipRect":
|
|
529
583
|
case "clipCircle": {
|
|
530
584
|
const pad = entry && motionRevealPadding(entry);
|
|
@@ -534,7 +588,7 @@ function motionRestValue(entry, property, artwork) {
|
|
|
534
588
|
return [0, 0, artwork.width, artwork.height];
|
|
535
589
|
}
|
|
536
590
|
const [, effectId, param] = property.split(":");
|
|
537
|
-
const effect = _optionalChain([node, 'optionalAccess',
|
|
591
|
+
const effect = _optionalChain([node, 'optionalAccess', _12 => _12.effects, 'optionalAccess', _13 => _13.find, 'call', _14 => _14((e) => e.id === effectId)]);
|
|
538
592
|
const v = effect ? effect.params[param] : void 0;
|
|
539
593
|
return typeof v === "number" ? v : 0;
|
|
540
594
|
}
|
|
@@ -583,13 +637,13 @@ function segmentValue(segment, time, raw = false) {
|
|
|
583
637
|
return typeof segment.from === "string" ? interpolateMotionValue(segment.from, segment.to, Math.min(1, Math.max(0, p))) : interpolateMotionValue(segment.from, segment.to, p);
|
|
584
638
|
}
|
|
585
639
|
function activeSegment(track, time) {
|
|
586
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
640
|
+
if (!_optionalChain([track, 'optionalAccess', _15 => _15.length])) return void 0;
|
|
587
641
|
let found;
|
|
588
642
|
for (const s of track) if (s.start <= time && (!found || s.start >= found.start)) found = s;
|
|
589
643
|
return found;
|
|
590
644
|
}
|
|
591
645
|
function trackValue(track, time, raw = false) {
|
|
592
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
646
|
+
if (!_optionalChain([track, 'optionalAccess', _16 => _16.length])) return void 0;
|
|
593
647
|
const s = activeSegment(track, time);
|
|
594
648
|
if (s) return segmentValue(s, time, raw);
|
|
595
649
|
let first = track[0];
|
|
@@ -622,7 +676,7 @@ function fitMotionRect(rect, aspect) {
|
|
|
622
676
|
function resolveMotionCamera(camera, scene) {
|
|
623
677
|
if ("frame" in camera && typeof camera.frame === "string") {
|
|
624
678
|
const entry = indexMotionNodes(scene.artwork.nodes).get(camera.frame);
|
|
625
|
-
if (!_optionalChain([entry, 'optionalAccess',
|
|
679
|
+
if (!_optionalChain([entry, 'optionalAccess', _17 => _17.bounds])) return null;
|
|
626
680
|
const pad = _nullishCoalesce(camera.padding, () => ( 24));
|
|
627
681
|
const b = entry.bounds;
|
|
628
682
|
const rect = { x: b.x - pad, y: b.y - pad, width: Math.max(1, b.width + pad * 2), height: Math.max(1, b.height + pad * 2) };
|
|
@@ -642,6 +696,9 @@ function motionPathLength(node, parentWorld = _chunkYJLCBA7Vcjs.IDENTITY_MATRIX)
|
|
|
642
696
|
}
|
|
643
697
|
return longest;
|
|
644
698
|
}
|
|
699
|
+
function motionShotTimeScale(shot) {
|
|
700
|
+
return typeof shot.speed === "number" && shot.speed > 0 && Number.isFinite(shot.speed) ? 1 / shot.speed : 1;
|
|
701
|
+
}
|
|
645
702
|
function motionCommandRepeat(command2) {
|
|
646
703
|
if (command2.type === "wait" || command2.type === "enter" || command2.type === "exit") return 1;
|
|
647
704
|
const n = _nullishCoalesce(command2.repeat, () => ( 1));
|
|
@@ -681,10 +738,11 @@ function compileMotionScene(scene) {
|
|
|
681
738
|
scene.shots.forEach((shot, index) => {
|
|
682
739
|
const start = t;
|
|
683
740
|
let cursor = start;
|
|
741
|
+
const f = motionShotTimeScale(shot);
|
|
684
742
|
if (shot.camera) {
|
|
685
743
|
const rect = resolveMotionCamera(shot.camera, scene);
|
|
686
744
|
if (rect && rect.width > 0 && rect.height > 0) {
|
|
687
|
-
const ms = index === 0 ? 0 : _nullishCoalesce(shot.camera.ms, () => ( MOTION_CAMERA_MS));
|
|
745
|
+
const ms = index === 0 ? 0 : (_nullishCoalesce(shot.camera.ms, () => ( MOTION_CAMERA_MS))) * f;
|
|
688
746
|
cameraMoves.push({ shot: shot.id, start, end: start + ms, to: rect, ease: _nullishCoalesce(shot.camera.ease, () => ( MOTION_EASE_IN_OUT)), geometric: shot.camera.geometric === true });
|
|
689
747
|
cursor = start + ms;
|
|
690
748
|
}
|
|
@@ -694,16 +752,16 @@ function compileMotionScene(scene) {
|
|
|
694
752
|
let prevStart = cursor;
|
|
695
753
|
shot.run.forEach((command2, i) => {
|
|
696
754
|
const base = command2.with === "previous" && i > 0 ? prevStart : runEnd;
|
|
697
|
-
const begin = base + (command2.type === "wait" ? 0 : _nullishCoalesce(command2.delay, () => ( 0)));
|
|
755
|
+
const begin = base + (command2.type === "wait" ? 0 : (_nullishCoalesce(command2.delay, () => ( 0))) * f);
|
|
698
756
|
const targets = command2.type === "wait" ? [] : command2.targets;
|
|
699
|
-
const durations = command2.type === "wait" ? [command2.duration] : targets.map((id) => motionCommandDuration(command2, scene, id));
|
|
700
|
-
const stagger = command2.type === "wait" ? 0 : _nullishCoalesce(command2.stagger, () => ( 0));
|
|
757
|
+
const durations = command2.type === "wait" ? [command2.duration * f] : targets.map((id) => motionCommandDuration(command2, scene, id) * f);
|
|
758
|
+
const stagger = command2.type === "wait" ? 0 : (_nullishCoalesce(command2.stagger, () => ( 0))) * f;
|
|
701
759
|
const end2 = durations.reduce((m, d, k) => Math.max(m, begin + k * stagger + d), begin);
|
|
702
760
|
commands.push({ shot: shot.id, shotIndex: index, command: i, type: command2.type, start: begin, end: end2, duration: _nullishCoalesce(durations[0], () => ( 0)) });
|
|
703
761
|
prevStart = begin;
|
|
704
762
|
runEnd = Math.max(runEnd, end2);
|
|
705
763
|
});
|
|
706
|
-
const end = runEnd + (_nullishCoalesce(shot.hold, () => ( MOTION_DEFAULT_HOLD)));
|
|
764
|
+
const end = runEnd + (_nullishCoalesce(shot.hold, () => ( MOTION_DEFAULT_HOLD))) * f;
|
|
707
765
|
shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end });
|
|
708
766
|
t = end;
|
|
709
767
|
});
|
|
@@ -736,8 +794,9 @@ function compileMotionScene(scene) {
|
|
|
736
794
|
command2.targets.forEach((target, k) => {
|
|
737
795
|
const entry = nodes.get(target);
|
|
738
796
|
if (!entry) return;
|
|
739
|
-
const
|
|
740
|
-
const
|
|
797
|
+
const f = motionShotTimeScale(shot);
|
|
798
|
+
const start = timing.start + k * (_nullishCoalesce(command2.stagger, () => ( 0))) * f;
|
|
799
|
+
const cycle = motionCycleDuration(command2, scene, target) * f;
|
|
741
800
|
jobs.push({ start, order: order++, run: () => compileRepeated(command2, target, entry, start, cycle, timing) });
|
|
742
801
|
});
|
|
743
802
|
}
|
|
@@ -783,7 +842,7 @@ function compileMotionScene(scene) {
|
|
|
783
842
|
const style = _nullishCoalesce(command2.style, () => ( "fade"));
|
|
784
843
|
const opacity = rest("opacity");
|
|
785
844
|
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;
|
|
786
|
-
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess',
|
|
845
|
+
for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _18 => _18.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
|
|
787
846
|
if (reveal) {
|
|
788
847
|
seg("opacity", opacity, "linear", start, start);
|
|
789
848
|
seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
|
|
@@ -845,8 +904,8 @@ function compileMotionScene(scene) {
|
|
|
845
904
|
}
|
|
846
905
|
case "move": {
|
|
847
906
|
const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
|
|
848
|
-
let dx = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
849
|
-
let dy = _nullishCoalesce(_optionalChain([command2, 'access',
|
|
907
|
+
let dx = _nullishCoalesce(_optionalChain([command2, 'access', _19 => _19.by, 'optionalAccess', _20 => _20.x]), () => ( 0));
|
|
908
|
+
let dy = _nullishCoalesce(_optionalChain([command2, 'access', _21 => _21.by, 'optionalAccess', _22 => _22.y]), () => ( 0));
|
|
850
909
|
if (command2.to) {
|
|
851
910
|
const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
|
|
852
911
|
const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
|
|
@@ -886,6 +945,15 @@ function compileMotionScene(scene) {
|
|
|
886
945
|
case "morph":
|
|
887
946
|
seg("shape", command2.into, _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)));
|
|
888
947
|
break;
|
|
948
|
+
case "match": {
|
|
949
|
+
const into = nodes.get(command2.into);
|
|
950
|
+
if (!into || command2.into === node) break;
|
|
951
|
+
const match = matchMotionNodes(entry.node, into.node);
|
|
952
|
+
const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
|
|
953
|
+
for (const [a, b] of match.pairs) push({ node: a, property: "shape", start, end, from: valueAt(a, "shape", start), to: b, ease: e, ...where });
|
|
954
|
+
for (const a of match.unpairedFrom) push({ node: a, property: "opacity", start, end, from: valueAt(a, "opacity", start), to: 0, ease: e, ...where });
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
889
957
|
case "follow":
|
|
890
958
|
seg("follow", _nullishCoalesce(command2.to, () => ( 1)), _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)), start, end, { from: _nullishCoalesce(command2.from, () => ( 0)), path: command2.path, rotate: _nullishCoalesce(command2.rotate, () => ( false)) });
|
|
891
959
|
break;
|
|
@@ -941,7 +1009,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
|
|
|
941
1009
|
var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
|
|
942
1010
|
var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
|
|
943
1011
|
function applyMotionControlValues(scene, values) {
|
|
944
|
-
if (!values || !_optionalChain([scene, 'access',
|
|
1012
|
+
if (!values || !_optionalChain([scene, 'access', _23 => _23.controls, 'optionalAccess', _24 => _24.length])) return scene;
|
|
945
1013
|
const map = /* @__PURE__ */ new Map();
|
|
946
1014
|
for (const control of scene.controls) {
|
|
947
1015
|
const value = values[control.name];
|
|
@@ -987,7 +1055,7 @@ function shapeIn(entry, shape) {
|
|
|
987
1055
|
}
|
|
988
1056
|
function shapeAt(entry, timeline, nodes, time) {
|
|
989
1057
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
|
|
990
|
-
if (!_optionalChain([track, 'optionalAccess',
|
|
1058
|
+
if (!_optionalChain([track, 'optionalAccess', _25 => _25.length])) return null;
|
|
991
1059
|
const s = activeSegment(track, time);
|
|
992
1060
|
const fromId = String(s ? s.from : track[0].from);
|
|
993
1061
|
if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
|
|
@@ -1010,8 +1078,8 @@ function motionFollowRoute(entry, path) {
|
|
|
1010
1078
|
}
|
|
1011
1079
|
function followAt(entry, timeline, nodes, time) {
|
|
1012
1080
|
const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
|
|
1013
|
-
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess',
|
|
1014
|
-
if (!_optionalChain([s, 'optionalAccess',
|
|
1081
|
+
const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _26 => _26[0]])));
|
|
1082
|
+
if (!_optionalChain([s, 'optionalAccess', _27 => _27.path])) return null;
|
|
1015
1083
|
const route = motionFollowRoute(entry, nodes.get(s.path));
|
|
1016
1084
|
const progress = Number(trackValue(track, time));
|
|
1017
1085
|
if (!route || !(progress >= 0)) return null;
|
|
@@ -1071,7 +1139,7 @@ function motionFrameAt(scene, time, options = {}) {
|
|
|
1071
1139
|
if (out.visible === void 0) delete out.visible;
|
|
1072
1140
|
}
|
|
1073
1141
|
const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
|
|
1074
|
-
if (effectTracks.length && _optionalChain([out, 'access',
|
|
1142
|
+
if (effectTracks.length && _optionalChain([out, 'access', _28 => _28.effects, 'optionalAccess', _29 => _29.length])) {
|
|
1075
1143
|
out = {
|
|
1076
1144
|
...out,
|
|
1077
1145
|
effects: out.effects.map((effect) => {
|
|
@@ -1149,7 +1217,7 @@ function withViewBox(svg, box) {
|
|
|
1149
1217
|
// src/animation-editor/check.ts
|
|
1150
1218
|
var MOTION_MIN_CHANGE_MS = 120;
|
|
1151
1219
|
var NUMERIC_PARAMS = (node, effectId) => {
|
|
1152
|
-
const effect = _optionalChain([node, 'access',
|
|
1220
|
+
const effect = _optionalChain([node, 'access', _30 => _30.effects, 'optionalAccess', _31 => _31.find, 'call', _32 => _32((e) => e.id === effectId)]);
|
|
1153
1221
|
if (!effect) return null;
|
|
1154
1222
|
return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
|
|
1155
1223
|
};
|
|
@@ -1226,6 +1294,19 @@ function checkMotionScene(scene) {
|
|
|
1226
1294
|
if (command2.type === "follow" && known(command2.path, "unknown-path", "path", where) && nodes.get(command2.path).node.type !== "path")
|
|
1227
1295
|
issues.push(motionIssue("not-a-path", `"${command2.path}" is a group; follow a single path node.`, { ...where, ids: [command2.path] }));
|
|
1228
1296
|
if (command2.type === "morph") known(command2.into, "unknown-shape", "shape", where);
|
|
1297
|
+
if (command2.type === "match" && known(command2.into, "unknown-shape", "group", where)) {
|
|
1298
|
+
const into = nodes.get(command2.into);
|
|
1299
|
+
for (const id of targets) {
|
|
1300
|
+
const entry = nodes.get(id);
|
|
1301
|
+
if (id === command2.into || into.ancestors.includes(id) || entry.ancestors.includes(command2.into)) {
|
|
1302
|
+
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] }));
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const m = matchMotionNodes(entry.node, into.node);
|
|
1306
|
+
if (m.unpairedInto.length)
|
|
1307
|
+
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 }));
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1229
1310
|
const style = command2.type === "enter" || command2.type === "exit" ? command2.style : void 0;
|
|
1230
1311
|
if (style === "wipe" || style === "iris" || style === "split") {
|
|
1231
1312
|
for (const id of targets)
|
|
@@ -1311,7 +1392,7 @@ function checkMotionScene(scene) {
|
|
|
1311
1392
|
const cam = frame.camera;
|
|
1312
1393
|
const docIndex = indexMotionNodes(frame.document.nodes);
|
|
1313
1394
|
for (const id of command2.targets) {
|
|
1314
|
-
const b = _optionalChain([docIndex, 'access',
|
|
1395
|
+
const b = _optionalChain([docIndex, 'access', _33 => _33.get, 'call', _34 => _34(id), 'optionalAccess', _35 => _35.bounds]);
|
|
1315
1396
|
if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
|
|
1316
1397
|
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;
|
|
1317
1398
|
if (outside) {
|
|
@@ -1329,8 +1410,8 @@ function checkMotionScene(scene) {
|
|
|
1329
1410
|
if (scene.target && scene.shots.length) {
|
|
1330
1411
|
const seconds = Math.round(timeline.duration / 100) / 10;
|
|
1331
1412
|
const [min, max] = scene.target;
|
|
1332
|
-
if (seconds > max) issues.push(motionIssue("scene-too-long", `The scene runs ${seconds} s, past its ${max} s target; shorten holds or durations.`, { measured: seconds, needed: max, unit: "s" }));
|
|
1333
|
-
if (seconds < min) issues.push(motionIssue("scene-too-short", `The scene runs ${seconds} s, under its ${min} s target; hold longer or
|
|
1413
|
+
if (seconds > max) issues.push(motionIssue("scene-too-long", `The scene runs ${seconds} s, past its ${max} s target; shorten holds or durations, or fit it (fit_animation).`, { measured: seconds, needed: max, unit: "s" }));
|
|
1414
|
+
if (seconds < min) issues.push(motionIssue("scene-too-short", `The scene runs ${seconds} s, under its ${min} s target; hold longer, add shots, or fit it (fit_animation).`, { measured: seconds, needed: min, unit: "s" }));
|
|
1334
1415
|
}
|
|
1335
1416
|
return issues;
|
|
1336
1417
|
}
|
|
@@ -1493,9 +1574,9 @@ function applyMotionEdits(scene, edits) {
|
|
|
1493
1574
|
let next = scene;
|
|
1494
1575
|
for (let i = 0; i < list.length; i++) {
|
|
1495
1576
|
const edit = list[i];
|
|
1496
|
-
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess',
|
|
1577
|
+
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _36 => _36.op])}): ` : "";
|
|
1497
1578
|
if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
|
|
1498
|
-
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess',
|
|
1579
|
+
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _37 => _37.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
|
|
1499
1580
|
try {
|
|
1500
1581
|
next = applyOne(next, edit);
|
|
1501
1582
|
} catch (error) {
|
|
@@ -1515,6 +1596,34 @@ function createMotionScene(artwork, init = {}) {
|
|
|
1515
1596
|
return { version: 1, name: _nullishCoalesce(init.name, () => ( "animation")), ...init, artwork, shots: _nullishCoalesce(init.shots, () => ( [])) };
|
|
1516
1597
|
}
|
|
1517
1598
|
|
|
1599
|
+
// src/animation-editor/fit.ts
|
|
1600
|
+
var MOTION_SPEED_RANGE = [0.05, 20];
|
|
1601
|
+
function fitMotionScene(scene, ms, options = {}) {
|
|
1602
|
+
if (!(Number.isFinite(ms) && ms > 0)) throw new Error("Give the length to fit as a number of milliseconds above 0.");
|
|
1603
|
+
const ids = scene.shots.map((s) => s.id);
|
|
1604
|
+
const keep = new Set(_nullishCoalesce(options.keep, () => ( [])));
|
|
1605
|
+
for (const id of keep) if (!ids.includes(id)) throw new Error(`There is no shot ${namedWithSuggestion(id, ids)} to keep.`);
|
|
1606
|
+
const timeline = compileMotionScene(scene);
|
|
1607
|
+
let kept = 0;
|
|
1608
|
+
let rest = 0;
|
|
1609
|
+
timeline.shots.forEach((t) => keep.has(t.id) ? kept += t.end - t.start : rest += t.end - t.start);
|
|
1610
|
+
if (rest <= 0) throw new Error(keep.size ? "Every shot is kept, so nothing can change pace; keep fewer shots." : "The scene has no length to fit; add shots first.");
|
|
1611
|
+
if (ms <= kept) throw new Error(`The kept shots alone last ${Math.round(kept)} ms, so the scene cannot fit ${Math.round(ms)} ms; keep fewer shots or give at least ${Math.ceil(kept) + 1} ms.`);
|
|
1612
|
+
const k = (ms - kept) / rest;
|
|
1613
|
+
const speeds = {};
|
|
1614
|
+
const shots = scene.shots.map((shot) => {
|
|
1615
|
+
if (keep.has(shot.id)) return shot;
|
|
1616
|
+
const speed = Math.round((_nullishCoalesce(shot.speed, () => ( 1))) / k * 1e4) / 1e4;
|
|
1617
|
+
if (speed < MOTION_SPEED_RANGE[0] || speed > MOTION_SPEED_RANGE[1])
|
|
1618
|
+
throw new Error(`Fitting ${Math.round(ms)} ms would play shot "${shot.id}" at ${speed}\xD7 (speeds run ${MOTION_SPEED_RANGE[0]}\u2013${MOTION_SPEED_RANGE[1]}); aim nearer the current ${Math.round(timeline.duration)} ms.`);
|
|
1619
|
+
speeds[shot.id] = speed;
|
|
1620
|
+
const { speed: _old, ...rest2 } = shot;
|
|
1621
|
+
return speed === 1 ? rest2 : { ...rest2, speed };
|
|
1622
|
+
});
|
|
1623
|
+
const fitted = { ...scene, shots };
|
|
1624
|
+
return { scene: fitted, duration: compileMotionScene(fitted).duration, speeds };
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1518
1627
|
// src/animation-editor/flair.ts
|
|
1519
1628
|
var FLAIR_EASES = {
|
|
1520
1629
|
linear: (t) => t,
|
|
@@ -1627,7 +1736,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1627
1736
|
const wrapper = freshId(`${id}--follow`);
|
|
1628
1737
|
const centre = freshId(`${id}--follow-centre`);
|
|
1629
1738
|
const c = entry.center;
|
|
1630
|
-
const at = _optionalChain([motionNodeOffset, 'call',
|
|
1739
|
+
const at = _optionalChain([motionNodeOffset, 'call', _38 => _38(entry, timeline, 0, nodes), 'optionalAccess', _39 => _39.follow]);
|
|
1631
1740
|
const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
|
|
1632
1741
|
(wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
|
|
1633
1742
|
follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
|
|
@@ -1687,7 +1796,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1687
1796
|
exact: () => false,
|
|
1688
1797
|
value: (t) => {
|
|
1689
1798
|
const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
|
|
1690
|
-
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call',
|
|
1799
|
+
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _40 => _40(frame.document.nodes), 'access', _41 => _41.get, 'call', _42 => _42(id), 'optionalAccess', _43 => _43.node]));
|
|
1691
1800
|
if (!now || now.type !== "path") return "";
|
|
1692
1801
|
const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
|
|
1693
1802
|
return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
|
|
@@ -1708,8 +1817,8 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1708
1817
|
...delay > 0 ? { delay: round2(delay) } : {},
|
|
1709
1818
|
ms: round2(ms),
|
|
1710
1819
|
ease: written,
|
|
1711
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1712
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1820
|
+
..._optionalChain([segment, 'optionalAccess', _44 => _44.behavior]) ? { behavior: segment.behavior } : {},
|
|
1821
|
+
..._optionalChain([segment, 'optionalAccess', _45 => _45.steps]) ? { strobe: segment.steps } : {}
|
|
1713
1822
|
};
|
|
1714
1823
|
if (channel.kind === "view") animate.push({ ...base, view: to });
|
|
1715
1824
|
else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
|
|
@@ -1782,7 +1891,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1782
1891
|
if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
|
|
1783
1892
|
const lastChange = animate.reduce((m, a) => Math.max(m, (_nullishCoalesce(a.delay, () => ( 0))) + (_nullishCoalesce(a.ms, () => ( 0)))), 0);
|
|
1784
1893
|
let motion = { svg, animate };
|
|
1785
|
-
if (_optionalChain([scene, 'access',
|
|
1894
|
+
if (_optionalChain([scene, 'access', _46 => _46.controls, 'optionalAccess', _47 => _47.length])) {
|
|
1786
1895
|
const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
|
|
1787
1896
|
const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
|
|
1788
1897
|
svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
|
|
@@ -1804,7 +1913,7 @@ function toFlairVectorEffect(scene, options = {}) {
|
|
|
1804
1913
|
tags: _nullishCoalesce(options.tags, () => ( [])),
|
|
1805
1914
|
motion: out.motion,
|
|
1806
1915
|
hold: out.hold,
|
|
1807
|
-
..._optionalChain([scene, 'access',
|
|
1916
|
+
..._optionalChain([scene, 'access', _48 => _48.presets, 'optionalAccess', _49 => _49.length]) ? { presets: scene.presets } : {},
|
|
1808
1917
|
...options.trigger ? { trigger: options.trigger } : {}
|
|
1809
1918
|
};
|
|
1810
1919
|
}
|
|
@@ -1847,7 +1956,7 @@ function resolvePlaceholders(motion, notes) {
|
|
|
1847
1956
|
const filled = text.replace(PLACEHOLDER, (_, name) => {
|
|
1848
1957
|
const d = declared.get(name);
|
|
1849
1958
|
if (!d) unknown.add(name);
|
|
1850
|
-
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess',
|
|
1959
|
+
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _50 => _50.value]), () => ( ""))).replace(/"/g, '\\"');
|
|
1851
1960
|
});
|
|
1852
1961
|
for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
|
|
1853
1962
|
return { motion: JSON.parse(filled), controls };
|
|
@@ -1901,7 +2010,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
1901
2010
|
if (anim.along) {
|
|
1902
2011
|
const path = nodes.get(anim.along);
|
|
1903
2012
|
if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
|
|
1904
|
-
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access',
|
|
2013
|
+
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _51 => _51.at, 'optionalAccess', _52 => _52[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _53 => _53.at, 'optionalAccess', _54 => _54[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
1905
2014
|
}
|
|
1906
2015
|
for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
|
|
1907
2016
|
if (attr === "opacity") {
|
|
@@ -1912,7 +2021,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
1912
2021
|
out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
|
|
1913
2022
|
} else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
1914
2023
|
} else if (attr === "fill" || attr === "stroke") {
|
|
1915
|
-
if (_optionalChain([behavior, 'optionalAccess',
|
|
2024
|
+
if (_optionalChain([behavior, 'optionalAccess', _55 => _55.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
|
|
1916
2025
|
else {
|
|
1917
2026
|
if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
|
|
1918
2027
|
out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
@@ -2079,7 +2188,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2079
2188
|
time: p.time,
|
|
2080
2189
|
playing: p.playing,
|
|
2081
2190
|
loop: p.loop,
|
|
2082
|
-
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call',
|
|
2191
|
+
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _56 => _56(timeline, p.time), 'optionalAccess', _57 => _57.id]), () => ( null)),
|
|
2083
2192
|
selection: this.selection,
|
|
2084
2193
|
readOnly: this.readOnly,
|
|
2085
2194
|
error: this.error,
|
|
@@ -2118,7 +2227,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2118
2227
|
this.applying = false;
|
|
2119
2228
|
}
|
|
2120
2229
|
this.notify();
|
|
2121
|
-
_optionalChain([this, 'access',
|
|
2230
|
+
_optionalChain([this, 'access', _58 => _58.onChange, 'optionalCall', _59 => _59(scene)]);
|
|
2122
2231
|
}
|
|
2123
2232
|
commit(label, next) {
|
|
2124
2233
|
const before = this.scene;
|
|
@@ -2331,6 +2440,7 @@ var MOTION_COMMAND_SCHEMA = {
|
|
|
2331
2440
|
stroke: { type: "string", description: "CSS colour (hex, rgb(), rgba())." },
|
|
2332
2441
|
strokeWidth: { type: "number", minimum: 0, description: "Document units." }
|
|
2333
2442
|
}),
|
|
2443
|
+
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"]),
|
|
2334
2444
|
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"]),
|
|
2335
2445
|
command("follow", "Moves targets along a path node, centre on the path.", {
|
|
2336
2446
|
path: { type: "string", minLength: 1, description: "Id of a path node (it can be hidden)." },
|
|
@@ -2384,7 +2494,8 @@ var SHOT_FIELDS = {
|
|
|
2384
2494
|
label: { type: "string", description: "What happens, in a few words." },
|
|
2385
2495
|
camera: CAMERA,
|
|
2386
2496
|
run: { type: "array", items: MOTION_COMMAND_SCHEMA, description: "Commands, in order." },
|
|
2387
|
-
hold: MS("Hold after the commands (default 600)")
|
|
2497
|
+
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." }
|
|
2388
2499
|
};
|
|
2389
2500
|
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.");
|
|
2390
2501
|
var CONTROL = object(
|
|
@@ -2411,7 +2522,7 @@ var EDIT_OP = {
|
|
|
2411
2522
|
oneOf: [
|
|
2412
2523
|
object({ op: { const: "set_settings", description: "Like set_animation_settings." }, ...SETTINGS }, ["op"]),
|
|
2413
2524
|
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"]),
|
|
2414
|
-
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"] }, description: "Fields to clear." } }, ["op", "shot"]),
|
|
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"]),
|
|
2415
2526
|
object({ op: { const: "delete_shots", description: "Like delete_shots." }, shots: { type: "array", items: SHOT_ID, minItems: 1, description: "Shot ids." } }, ["op", "shots"]),
|
|
2416
2527
|
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"]),
|
|
2417
2528
|
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"]),
|
|
@@ -2658,6 +2769,27 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2658
2769
|
return outcome(applied, applied.ok ? { ...report, elements: describeMotionElements(result.scene) } : {});
|
|
2659
2770
|
}
|
|
2660
2771
|
};
|
|
2772
|
+
const fitAnimation = {
|
|
2773
|
+
name: "fit_animation",
|
|
2774
|
+
title: "Fit the animation to a length",
|
|
2775
|
+
description: `Makes the animation last a given time by setting its shots' speed (one undo step): shots in "keep" play as they are and the others stretch or squeeze together, keeping their proportions. Returns the new length, each changed shot's speed and the timing. With dry_run it only reports them.`,
|
|
2776
|
+
inputSchema: object(
|
|
2777
|
+
{
|
|
2778
|
+
seconds: { type: "number", exclusiveMinimum: 0, description: "The length to fit, in seconds." },
|
|
2779
|
+
keep: { type: "array", items: SHOT_ID, description: "Shots that keep their pace, e.g. a logo hold (default none)." },
|
|
2780
|
+
dry_run: { type: "boolean", description: "Report without changing anything (default false)." }
|
|
2781
|
+
},
|
|
2782
|
+
["seconds"]
|
|
2783
|
+
),
|
|
2784
|
+
annotations: { title: "Fit the animation to a length", ...WRITE_IDEMPOTENT },
|
|
2785
|
+
handler: ({ seconds, keep, dry_run = false }) => {
|
|
2786
|
+
const fit = fitMotionScene(controller.getState().scene, seconds * 1e3, { keep: _nullishCoalesce(keep, () => ( [])) });
|
|
2787
|
+
const report = { duration: fit.duration, speeds: fit.speeds };
|
|
2788
|
+
if (dry_run) return { dry_run: true, ...report, hint: "Nothing was changed; call again without dry_run to fit." };
|
|
2789
|
+
const edits = Object.entries(fit.speeds).map(([shot, speed]) => speed === 1 ? { op: "update_shot", shot, remove: ["speed"] } : { op: "update_shot", shot, set: { speed } });
|
|
2790
|
+
return apply(edits, "Fit to length", () => report);
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2661
2793
|
const insertShots = {
|
|
2662
2794
|
name: "insert_shots",
|
|
2663
2795
|
title: "Add shots",
|
|
@@ -2674,7 +2806,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2674
2806
|
{
|
|
2675
2807
|
shot: SHOT_ID,
|
|
2676
2808
|
set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."),
|
|
2677
|
-
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold"] }, description: "Fields to clear." }
|
|
2809
|
+
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." }
|
|
2678
2810
|
},
|
|
2679
2811
|
["shot"]
|
|
2680
2812
|
),
|
|
@@ -2704,7 +2836,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2704
2836
|
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"]),
|
|
2705
2837
|
annotations: { title: "Add commands to a shot", ...WRITE },
|
|
2706
2838
|
handler: ({ shot, commands, at }) => {
|
|
2707
|
-
const before = _nullishCoalesce(_optionalChain([scene, 'call',
|
|
2839
|
+
const before = _nullishCoalesce(_optionalChain([scene, 'call', _60 => _60(), 'access', _61 => _61.shots, 'access', _62 => _62.find, 'call', _63 => _63((s) => s.id === shot), 'optionalAccess', _64 => _64.run, 'access', _65 => _65.length]), () => ( 0));
|
|
2708
2840
|
const first = _nullishCoalesce(at, () => ( before));
|
|
2709
2841
|
return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
|
|
2710
2842
|
const shots = describeMotionShots(s);
|
|
@@ -2819,7 +2951,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2819
2951
|
}
|
|
2820
2952
|
};
|
|
2821
2953
|
const reads = [getAnimation, getShot, findElements, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
|
|
2822
|
-
const writes = [setArtworkSvg, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
2954
|
+
const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
2823
2955
|
return {
|
|
2824
2956
|
id: _nullishCoalesce(options.id, () => ( "animation")),
|
|
2825
2957
|
namespace: options.namespace,
|
|
@@ -2852,7 +2984,7 @@ Commands
|
|
|
2852
2984
|
- 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.
|
|
2853
2985
|
- 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.
|
|
2854
2986
|
- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
|
|
2855
|
-
- 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).
|
|
2987
|
+
- 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).
|
|
2856
2988
|
- 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).
|
|
2857
2989
|
- 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.
|
|
2858
2990
|
- 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.
|
|
@@ -2864,7 +2996,7 @@ Commands
|
|
|
2864
2996
|
Working method
|
|
2865
2997
|
1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).
|
|
2866
2998
|
2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).
|
|
2867
|
-
3. check_animation and fix every error and warning; render_animation_frame (shot + "end") to look at the result.
|
|
2999
|
+
3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + "end") to look at the result.
|
|
2868
3000
|
4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.
|
|
2869
3001
|
|
|
2870
3002
|
Keep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.`;
|
|
@@ -2951,5 +3083,9 @@ Keep motion purposeful: one thing moving at a time reads best, ease-out for arri
|
|
|
2951
3083
|
|
|
2952
3084
|
|
|
2953
3085
|
|
|
2954
|
-
|
|
3086
|
+
|
|
3087
|
+
|
|
3088
|
+
|
|
3089
|
+
|
|
3090
|
+
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.motionClipAttrs = motionClipAttrs; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandRepeat = motionCommandRepeat; 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;
|
|
2955
3091
|
//# sourceMappingURL=index.cjs.map
|