@pixerate/editor 0.63.0 → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/dist/animation-editor/index.cjs +460 -26
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +158 -3
- package/dist/animation-editor/index.d.ts +158 -3
- package/dist/animation-editor/index.js +440 -6
- package/dist/animation-editor/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,6 +24,7 @@ var _chunkYJLCBA7Vcjs = require('../chunk-YJLCBA7V.cjs');
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
|
|
27
28
|
var _chunkO2UH7LZEcjs = require('../chunk-O2UH7LZE.cjs');
|
|
28
29
|
|
|
29
30
|
|
|
@@ -1257,10 +1258,92 @@ function withViewBox(svg, box) {
|
|
|
1257
1258
|
return svg.replace(/viewBox="[^"]*"/, `viewBox="${f(box.x)} ${f(box.y)} ${f(box.width)} ${f(box.height)}"`);
|
|
1258
1259
|
}
|
|
1259
1260
|
|
|
1261
|
+
// src/animation-editor/onion.ts
|
|
1262
|
+
function motionGhosts(scene, time, onion = {}) {
|
|
1263
|
+
const before = Math.max(0, Math.floor(_nullishCoalesce(onion.before, () => ( 2))));
|
|
1264
|
+
const after = Math.max(0, Math.floor(_nullishCoalesce(onion.after, () => ( 0))));
|
|
1265
|
+
const base = Math.min(1, Math.max(0, _nullishCoalesce(onion.opacity, () => ( 0.35))));
|
|
1266
|
+
const timeline = compileMotionScene(scene);
|
|
1267
|
+
const ghosts = [];
|
|
1268
|
+
const push = (t, offset) => ghosts.push({ time: t, offset, opacity: Math.round(base / Math.abs(offset) * 1e3) / 1e3 });
|
|
1269
|
+
if (onion.spacing === "commands") {
|
|
1270
|
+
const marks = [...new Set(timeline.commands.flatMap((c) => [c.start, c.end]))].sort((a, b) => a - b);
|
|
1271
|
+
marks.filter((t) => t < time - 1e-6).reverse().slice(0, before).forEach((t, i) => push(t, -(i + 1)));
|
|
1272
|
+
marks.filter((t) => t > time + 1e-6).slice(0, after).forEach((t, i) => push(t, i + 1));
|
|
1273
|
+
return ghosts;
|
|
1274
|
+
}
|
|
1275
|
+
const spacing = typeof onion.spacing === "number" && onion.spacing > 0 ? onion.spacing : 1e3 / 12;
|
|
1276
|
+
for (let i = 1; i <= before && time - i * spacing >= 0; i++) push(time - i * spacing, -i);
|
|
1277
|
+
for (let i = 1; i <= after && time + i * spacing <= timeline.duration; i++) push(time + i * spacing, i);
|
|
1278
|
+
return ghosts;
|
|
1279
|
+
}
|
|
1280
|
+
var MOVING = ["x", "y", "rotation", "scaleX", "scaleY", "follow", "shape"];
|
|
1281
|
+
function motionMovingIds(scene) {
|
|
1282
|
+
const timeline = compileMotionScene(scene);
|
|
1283
|
+
const nodes = indexMotionNodes(scene.artwork.nodes);
|
|
1284
|
+
const moving = new Set(timeline.segments.filter((s) => s.node && MOVING.includes(s.property)).map((s) => s.node));
|
|
1285
|
+
return [...nodes.values()].filter((e) => e.node.visible !== false && (moving.has(e.node.id) || e.ancestors.some((a) => moving.has(a)))).map((e) => e.node.id);
|
|
1286
|
+
}
|
|
1287
|
+
function motionPath(scene, id, options = {}) {
|
|
1288
|
+
const nodes = indexMotionNodes(scene.artwork.nodes);
|
|
1289
|
+
const entry = nodes.get(id);
|
|
1290
|
+
if (!entry) throw new Error(`There is no element "${id}" in the artwork.`);
|
|
1291
|
+
const timeline = compileMotionScene(scene);
|
|
1292
|
+
const from = Math.max(0, _nullishCoalesce(options.from, () => ( 0)));
|
|
1293
|
+
const to = Math.min(timeline.duration, _nullishCoalesce(options.to, () => ( timeline.duration)));
|
|
1294
|
+
let step = options.step && options.step > 0 ? options.step : 1e3 / 30;
|
|
1295
|
+
if ((to - from) / step > 400) step = (to - from) / 400;
|
|
1296
|
+
const r = (n) => Math.round(n * 100) / 100;
|
|
1297
|
+
const centreAt = (t) => {
|
|
1298
|
+
const node = indexMotionNodes(motionFrameAtCache(scene, t)).get(id);
|
|
1299
|
+
const b = _optionalChain([node, 'optionalAccess', _34 => _34.bounds]);
|
|
1300
|
+
return b ? { t: r(t), x: r(b.x + b.width / 2), y: r(b.y + b.height / 2) } : null;
|
|
1301
|
+
};
|
|
1302
|
+
const points = [];
|
|
1303
|
+
for (let t = from; t <= to + 1e-6; t += step) {
|
|
1304
|
+
const p = centreAt(Math.min(t, to));
|
|
1305
|
+
if (p) points.push(p);
|
|
1306
|
+
}
|
|
1307
|
+
const owners = /* @__PURE__ */ new Set([id, ...entry.ancestors]);
|
|
1308
|
+
const times = [...new Set(timeline.segments.filter((s) => owners.has(s.node) && MOVING.includes(s.property)).flatMap((s) => [s.start, s.end]))].filter((t) => t >= from && t <= to).sort((a, b) => a - b);
|
|
1309
|
+
const keys = times.map(centreAt).filter((p) => p !== null);
|
|
1310
|
+
return { id, points, keys };
|
|
1311
|
+
}
|
|
1312
|
+
function motionFrameAtCache(scene, t) {
|
|
1313
|
+
return motionFrameAt(scene, t).document.nodes;
|
|
1314
|
+
}
|
|
1315
|
+
function motionPathsSvg(paths, camera, color = "#f43f5e") {
|
|
1316
|
+
const w = Math.max(camera.width, camera.height) / 400;
|
|
1317
|
+
const f = (n) => String(Math.round(n * 100) / 100);
|
|
1318
|
+
return paths.filter((p) => p.points.length > 1).map(
|
|
1319
|
+
(p) => `<g data-motion-path="${p.id.replace(/"/g, """)}" fill="none" stroke="${color}" stroke-width="${f(w)}" stroke-linecap="round" stroke-linejoin="round" opacity="0.8"><polyline points="${p.points.map((q) => `${f(q.x)},${f(q.y)}`).join(" ")}" stroke-dasharray="${f(w * 3)} ${f(w * 2)}"/>${p.keys.map((k) => `<circle cx="${f(k.x)}" cy="${f(k.y)}" r="${f(w * 2.5)}" fill="${color}" stroke="none"/>`).join("")}</g>`
|
|
1320
|
+
).join("");
|
|
1321
|
+
}
|
|
1322
|
+
var innerSvg = (svg) => svg.slice(svg.indexOf(">") + 1, svg.lastIndexOf("</svg>"));
|
|
1323
|
+
function motionStageSvg(scene, time, options = {}) {
|
|
1324
|
+
const { onionSkin, motionPaths, motionPathColor, ...svgOptions } = options;
|
|
1325
|
+
const current = motionFrameToSvg(scene, time, svgOptions);
|
|
1326
|
+
if (!onionSkin && !motionPaths) return current;
|
|
1327
|
+
const prefix = _nullishCoalesce(svgOptions.idPrefix, () => ( "vg-"));
|
|
1328
|
+
const ghosts = onionSkin ? motionGhosts(scene, time, onionSkin).map((g, i) => {
|
|
1329
|
+
const own = `${prefix}ghost${i}-`;
|
|
1330
|
+
const inner = innerSvg(motionFrameToSvg(scene, g.time, { ...svgOptions, idPrefix: own, background: false })).replace(new RegExp(` id="(?!${own.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})[^"]*"`, "g"), "");
|
|
1331
|
+
return `<g data-onion="${g.offset < 0 ? "before" : "after"}" opacity="${g.opacity}">${inner}</g>`;
|
|
1332
|
+
}) : [];
|
|
1333
|
+
const camera = motionFrameAt(scene, time).camera;
|
|
1334
|
+
const ids = motionPaths === true ? motionMovingIds(scene) : motionPaths ? [...motionPaths] : [];
|
|
1335
|
+
const paths = ids.length ? motionPathsSvg(ids.map((id) => motionPath(scene, id)), camera, motionPathColor) : "";
|
|
1336
|
+
const open = current.indexOf(">") + 1;
|
|
1337
|
+
const close = current.lastIndexOf("</svg>");
|
|
1338
|
+
const body = current.slice(open, close);
|
|
1339
|
+
const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _35 => _35.exec, 'call', _36 => _36(body), 'optionalAccess', _37 => _37[0]]), () => ( "")) : "";
|
|
1340
|
+
return `${current.slice(0, open)}${bg}${ghosts.join("")}${body.slice(bg.length)}${paths}</svg>`;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1260
1343
|
// src/animation-editor/check.ts
|
|
1261
1344
|
var MOTION_MIN_CHANGE_MS = 120;
|
|
1262
1345
|
var NUMERIC_PARAMS = (node, effectId) => {
|
|
1263
|
-
const effect = _optionalChain([node, 'access',
|
|
1346
|
+
const effect = _optionalChain([node, 'access', _38 => _38.effects, 'optionalAccess', _39 => _39.find, 'call', _40 => _40((e) => e.id === effectId)]);
|
|
1264
1347
|
if (!effect) return null;
|
|
1265
1348
|
return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
|
|
1266
1349
|
};
|
|
@@ -1413,7 +1496,7 @@ function checkMotionScene(scene) {
|
|
|
1413
1496
|
if (!shot.at) return;
|
|
1414
1497
|
const timing = timeline.shots[i];
|
|
1415
1498
|
if (timing.cue === void 0) {
|
|
1416
|
-
const what = shot.at.beat !== void 0 ? `beat ${shot.at.beat}, but the scene has no beat grid; set one with set_animation_settings ({ "beat": { "bpm": \u2026 } })` : `word ${shot.at.word}, but the narration has ${_nullishCoalesce(_optionalChain([scene, 'access',
|
|
1499
|
+
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', _41 => _41.narration, 'optionalAccess', _42 => _42.words, 'access', _43 => _43.length]), () => ( 0))} words (indexes from 0)`;
|
|
1417
1500
|
issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
|
|
1418
1501
|
} else if (timing.missedBy !== void 0) {
|
|
1419
1502
|
const late = Math.round(timing.missedBy);
|
|
@@ -1446,7 +1529,7 @@ function checkMotionScene(scene) {
|
|
|
1446
1529
|
const cam = frame.camera;
|
|
1447
1530
|
const docIndex = indexMotionNodes(frame.document.nodes);
|
|
1448
1531
|
for (const id of command2.targets) {
|
|
1449
|
-
const b = _optionalChain([docIndex, 'access',
|
|
1532
|
+
const b = _optionalChain([docIndex, 'access', _44 => _44.get, 'call', _45 => _45(id), 'optionalAccess', _46 => _46.bounds]);
|
|
1450
1533
|
if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
|
|
1451
1534
|
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;
|
|
1452
1535
|
if (outside) {
|
|
@@ -1636,9 +1719,9 @@ function applyMotionEdits(scene, edits) {
|
|
|
1636
1719
|
let next = scene;
|
|
1637
1720
|
for (let i = 0; i < list.length; i++) {
|
|
1638
1721
|
const edit = list[i];
|
|
1639
|
-
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess',
|
|
1722
|
+
const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _47 => _47.op])}): ` : "";
|
|
1640
1723
|
if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
|
|
1641
|
-
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess',
|
|
1724
|
+
return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _48 => _48.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
|
|
1642
1725
|
try {
|
|
1643
1726
|
next = applyOne(next, edit);
|
|
1644
1727
|
} catch (error) {
|
|
@@ -1798,7 +1881,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1798
1881
|
const wrapper = freshId(`${id}--follow`);
|
|
1799
1882
|
const centre = freshId(`${id}--follow-centre`);
|
|
1800
1883
|
const c = entry.center;
|
|
1801
|
-
const at = _optionalChain([motionNodeOffset, 'call',
|
|
1884
|
+
const at = _optionalChain([motionNodeOffset, 'call', _49 => _49(entry, timeline, 0, nodes), 'optionalAccess', _50 => _50.follow]);
|
|
1802
1885
|
const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
|
|
1803
1886
|
(wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
|
|
1804
1887
|
follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
|
|
@@ -1858,7 +1941,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1858
1941
|
exact: () => false,
|
|
1859
1942
|
value: (t) => {
|
|
1860
1943
|
const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
|
|
1861
|
-
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call',
|
|
1944
|
+
const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _51 => _51(frame.document.nodes), 'access', _52 => _52.get, 'call', _53 => _53(id), 'optionalAccess', _54 => _54.node]));
|
|
1862
1945
|
if (!now || now.type !== "path") return "";
|
|
1863
1946
|
const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
|
|
1864
1947
|
return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
|
|
@@ -1879,8 +1962,8 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1879
1962
|
...delay > 0 ? { delay: round2(delay) } : {},
|
|
1880
1963
|
ms: round2(ms),
|
|
1881
1964
|
ease: written,
|
|
1882
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1883
|
-
..._optionalChain([segment, 'optionalAccess',
|
|
1965
|
+
..._optionalChain([segment, 'optionalAccess', _55 => _55.behavior]) ? { behavior: segment.behavior } : {},
|
|
1966
|
+
..._optionalChain([segment, 'optionalAccess', _56 => _56.steps]) ? { strobe: segment.steps } : {}
|
|
1884
1967
|
};
|
|
1885
1968
|
if (channel.kind === "view") animate.push({ ...base, view: to });
|
|
1886
1969
|
else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
|
|
@@ -1953,7 +2036,7 @@ function toFlairSvgMotion(scene, options = {}) {
|
|
|
1953
2036
|
if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
|
|
1954
2037
|
const lastChange = animate.reduce((m, a) => Math.max(m, (_nullishCoalesce(a.delay, () => ( 0))) + (_nullishCoalesce(a.ms, () => ( 0)))), 0);
|
|
1955
2038
|
let motion = { svg, animate };
|
|
1956
|
-
if (_optionalChain([scene, 'access',
|
|
2039
|
+
if (_optionalChain([scene, 'access', _57 => _57.controls, 'optionalAccess', _58 => _58.length])) {
|
|
1957
2040
|
const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
|
|
1958
2041
|
const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
|
|
1959
2042
|
svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
|
|
@@ -1975,7 +2058,7 @@ function toFlairVectorEffect(scene, options = {}) {
|
|
|
1975
2058
|
tags: _nullishCoalesce(options.tags, () => ( [])),
|
|
1976
2059
|
motion: out.motion,
|
|
1977
2060
|
hold: out.hold,
|
|
1978
|
-
..._optionalChain([scene, 'access',
|
|
2061
|
+
..._optionalChain([scene, 'access', _59 => _59.presets, 'optionalAccess', _60 => _60.length]) ? { presets: scene.presets } : {},
|
|
1979
2062
|
...options.trigger ? { trigger: options.trigger } : {}
|
|
1980
2063
|
};
|
|
1981
2064
|
}
|
|
@@ -2018,7 +2101,7 @@ function resolvePlaceholders(motion, notes) {
|
|
|
2018
2101
|
const filled = text.replace(PLACEHOLDER, (_, name) => {
|
|
2019
2102
|
const d = declared.get(name);
|
|
2020
2103
|
if (!d) unknown.add(name);
|
|
2021
|
-
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess',
|
|
2104
|
+
return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _61 => _61.value]), () => ( ""))).replace(/"/g, '\\"');
|
|
2022
2105
|
});
|
|
2023
2106
|
for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
|
|
2024
2107
|
return { motion: JSON.parse(filled), controls };
|
|
@@ -2072,7 +2155,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
2072
2155
|
if (anim.along) {
|
|
2073
2156
|
const path = nodes.get(anim.along);
|
|
2074
2157
|
if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
|
|
2075
|
-
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access',
|
|
2158
|
+
else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _62 => _62.at, 'optionalAccess', _63 => _63[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _64 => _64.at, 'optionalAccess', _65 => _65[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
2076
2159
|
}
|
|
2077
2160
|
for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
|
|
2078
2161
|
if (attr === "opacity") {
|
|
@@ -2083,7 +2166,7 @@ function commandsFor(anim, nodes, entered, notes) {
|
|
|
2083
2166
|
out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
|
|
2084
2167
|
} else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
2085
2168
|
} else if (attr === "fill" || attr === "stroke") {
|
|
2086
|
-
if (_optionalChain([behavior, 'optionalAccess',
|
|
2169
|
+
if (_optionalChain([behavior, 'optionalAccess', _66 => _66.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
|
|
2087
2170
|
else {
|
|
2088
2171
|
if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
|
|
2089
2172
|
out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
|
|
@@ -2183,6 +2266,311 @@ function fromFlairVectorEffect(effect, options = {}) {
|
|
|
2183
2266
|
return { scene, notes, issues: checkMotionScene(scene) };
|
|
2184
2267
|
}
|
|
2185
2268
|
|
|
2269
|
+
// src/animation-editor/lottie.ts
|
|
2270
|
+
var STATIC_TR = () => ({
|
|
2271
|
+
ty: "tr",
|
|
2272
|
+
p: { a: 0, k: [0, 0] },
|
|
2273
|
+
a: { a: 0, k: [0, 0] },
|
|
2274
|
+
s: { a: 0, k: [100, 100] },
|
|
2275
|
+
r: { a: 0, k: 0 },
|
|
2276
|
+
o: { a: 0, k: 100 },
|
|
2277
|
+
sk: { a: 0, k: 0 },
|
|
2278
|
+
sa: { a: 0, k: 0 }
|
|
2279
|
+
});
|
|
2280
|
+
function rgba(color) {
|
|
2281
|
+
const hex = typeof color === "string" ? _chunkA6PHCAGXcjs.normalizeTokenColor.call(void 0, color) : null;
|
|
2282
|
+
if (!hex) return null;
|
|
2283
|
+
const n = (i) => parseInt(hex.slice(i, i + 2), 16) / 255;
|
|
2284
|
+
return [n(1), n(3), n(5), hex.length >= 9 ? parseInt(hex.slice(7, 9), 16) / 255 : 1];
|
|
2285
|
+
}
|
|
2286
|
+
function lottieContours(path, round2) {
|
|
2287
|
+
const out = [];
|
|
2288
|
+
let cur = null;
|
|
2289
|
+
let px = 0;
|
|
2290
|
+
let py = 0;
|
|
2291
|
+
const add = (x, y, inX, inY) => {
|
|
2292
|
+
cur.v.push([x, y]);
|
|
2293
|
+
cur.i.push([inX - x, inY - y]);
|
|
2294
|
+
cur.o.push([0, 0]);
|
|
2295
|
+
px = x;
|
|
2296
|
+
py = y;
|
|
2297
|
+
};
|
|
2298
|
+
for (const cmd of path) {
|
|
2299
|
+
if (cmd.type === "M") {
|
|
2300
|
+
if (cur) out.push(cur);
|
|
2301
|
+
cur = { c: false, v: [], i: [], o: [] };
|
|
2302
|
+
add(cmd.x, cmd.y, cmd.x, cmd.y);
|
|
2303
|
+
} else if (!cur) continue;
|
|
2304
|
+
else if (cmd.type === "L") add(cmd.x, cmd.y, cmd.x, cmd.y);
|
|
2305
|
+
else if (cmd.type === "Q") {
|
|
2306
|
+
const c1 = [px + 2 / 3 * (cmd.x1 - px), py + 2 / 3 * (cmd.y1 - py)];
|
|
2307
|
+
const c2 = [cmd.x + 2 / 3 * (cmd.x1 - cmd.x), cmd.y + 2 / 3 * (cmd.y1 - cmd.y)];
|
|
2308
|
+
cur.o[cur.o.length - 1] = [c1[0] - px, c1[1] - py];
|
|
2309
|
+
add(cmd.x, cmd.y, c2[0], c2[1]);
|
|
2310
|
+
} else if (cmd.type === "C") {
|
|
2311
|
+
cur.o[cur.o.length - 1] = [cmd.x1 - px, cmd.y1 - py];
|
|
2312
|
+
add(cmd.x, cmd.y, cmd.x2, cmd.y2);
|
|
2313
|
+
} else if (cmd.type === "Z") {
|
|
2314
|
+
cur.c = true;
|
|
2315
|
+
const n = cur.v.length;
|
|
2316
|
+
if (n > 1 && Math.abs(cur.v[n - 1][0] - cur.v[0][0]) < 1e-9 && Math.abs(cur.v[n - 1][1] - cur.v[0][1]) < 1e-9) {
|
|
2317
|
+
cur.i[0] = cur.i[n - 1];
|
|
2318
|
+
cur.v.pop();
|
|
2319
|
+
cur.i.pop();
|
|
2320
|
+
cur.o.pop();
|
|
2321
|
+
}
|
|
2322
|
+
out.push(cur);
|
|
2323
|
+
cur = null;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (cur) out.push(cur);
|
|
2327
|
+
return out.map((c) => ({ c: c.c, v: c.v.map((p) => p.map(round2)), i: c.i.map((p) => p.map(round2)), o: c.o.map((p) => p.map(round2)) }));
|
|
2328
|
+
}
|
|
2329
|
+
function staticParts(m) {
|
|
2330
|
+
const sx = Math.hypot(m[0], m[1]);
|
|
2331
|
+
if (!(sx > 1e-12)) return null;
|
|
2332
|
+
const sy = (m[0] * m[3] - m[1] * m[2]) / sx;
|
|
2333
|
+
if (Math.abs(m[2] + m[1] / sx * sy) > 1e-6 || Math.abs(m[3] - m[0] / sx * sy) > 1e-6) return null;
|
|
2334
|
+
return { p: [m[4], m[5]], s: [sx * 100, sy * 100], r: Math.atan2(m[1], m[0]) * 180 / Math.PI };
|
|
2335
|
+
}
|
|
2336
|
+
function toLottie(scene, options = {}) {
|
|
2337
|
+
const fps = _nullishCoalesce(_nullishCoalesce(options.fps, () => ( scene.fps)), () => ( 30));
|
|
2338
|
+
const precision = _nullishCoalesce(options.precision, () => ( 3));
|
|
2339
|
+
const round2 = (n) => Math.round(n * 10 ** precision) / 10 ** precision;
|
|
2340
|
+
const frame = (ms) => round2(ms * fps / 1e3);
|
|
2341
|
+
const timeline = compileMotionScene(scene);
|
|
2342
|
+
const nodes = indexMotionNodes(scene.artwork.nodes);
|
|
2343
|
+
const notes = [];
|
|
2344
|
+
let sampled = 0;
|
|
2345
|
+
const track = (id, p) => _nullishCoalesce(timeline.tracks.get(motionTrackKey(id, p)), () => ( []));
|
|
2346
|
+
const layers = (id, p) => _nullishCoalesce(timeline.tracks.get(`${motionTrackKey(id, p)}\0add`), () => ( []));
|
|
2347
|
+
const end = timeline.duration;
|
|
2348
|
+
const curveOf = (s) => {
|
|
2349
|
+
if (s.behavior || s.steps || s.geometric) return void 0;
|
|
2350
|
+
const e = s.reverse ? _chunk2FHLHZALcjs.reverseCubicBezier.call(void 0, s.ease) : _chunk2FHLHZALcjs.resolveEasing.call(void 0, s.ease);
|
|
2351
|
+
if (!e || e === "hold" || e.type !== "cubic-bezier") return void 0;
|
|
2352
|
+
return [e.x1, e.y1, e.x2, e.y2];
|
|
2353
|
+
};
|
|
2354
|
+
const animate = (segments, value, exact = true) => {
|
|
2355
|
+
const rest = value(0).map(round2);
|
|
2356
|
+
if (!segments.length) return { a: 0, k: rest.length === 1 ? rest[0] : rest };
|
|
2357
|
+
const keys = [];
|
|
2358
|
+
const key = (t, v, ease) => {
|
|
2359
|
+
const k = { t: frame(t), s: v.map(round2) };
|
|
2360
|
+
if (ease === "hold") k.h = 1;
|
|
2361
|
+
else {
|
|
2362
|
+
const [x1, y1, x2, y2] = ease === "linear" || !ease ? [0, 0, 1, 1] : ease;
|
|
2363
|
+
k.o = { x: [round2(x1)], y: [round2(y1)] };
|
|
2364
|
+
k.i = { x: [round2(x2)], y: [round2(y2)] };
|
|
2365
|
+
}
|
|
2366
|
+
if (keys.length && keys[keys.length - 1].t === k.t) keys[keys.length - 1] = k;
|
|
2367
|
+
else keys.push(k);
|
|
2368
|
+
};
|
|
2369
|
+
const times = [.../* @__PURE__ */ new Set([0, ...segments.flatMap((s) => [s.start, s.end]), end])].filter((t) => t >= 0 && t <= end).sort((a, b) => a - b);
|
|
2370
|
+
for (let n = 0; n + 1 < times.length; n++) {
|
|
2371
|
+
const a = times[n];
|
|
2372
|
+
const b = times[n + 1];
|
|
2373
|
+
const active = segments.filter((s) => s.start < b && s.end > a);
|
|
2374
|
+
if (!active.length) {
|
|
2375
|
+
key(a, value(a), "hold");
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
const first = active[0];
|
|
2379
|
+
const curve = exact && active.every((s) => s.start === a && s.end === b && JSON.stringify(curveOf(s)) === JSON.stringify(curveOf(first))) ? curveOf(first) : void 0;
|
|
2380
|
+
if (curve) {
|
|
2381
|
+
key(a, value(a), curve);
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
const count = Math.max(1, Math.ceil((b - a) * fps / 1e3));
|
|
2385
|
+
for (let k = 0; k < count; k++) {
|
|
2386
|
+
const t = a + (b - a) * k / count;
|
|
2387
|
+
key(t, value(t), "linear");
|
|
2388
|
+
sampled++;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
for (const s of segments) if (s.end <= s.start) key(s.start, value(s.start), "hold");
|
|
2392
|
+
keys.sort((x, y) => x.t - y.t);
|
|
2393
|
+
key(end, value(end), "hold");
|
|
2394
|
+
const last = keys[keys.length - 1];
|
|
2395
|
+
delete last.o;
|
|
2396
|
+
delete last.i;
|
|
2397
|
+
delete last.h;
|
|
2398
|
+
return { a: 1, k: keys };
|
|
2399
|
+
};
|
|
2400
|
+
const poseAt = (entry, t) => {
|
|
2401
|
+
const o = motionNodeOffset(entry, timeline, t, nodes);
|
|
2402
|
+
if (!o) return { p: [entry.center.x, entry.center.y], s: [100, 100], r: 0 };
|
|
2403
|
+
const m = o.matrix;
|
|
2404
|
+
const sx = Math.hypot(m[0], m[1]);
|
|
2405
|
+
const sy = sx > 1e-12 ? (m[0] * m[3] - m[1] * m[2]) / sx : 0;
|
|
2406
|
+
const c = entry.center;
|
|
2407
|
+
return { p: [m[0] * c.x + m[2] * c.y + m[4], m[1] * c.x + m[3] * c.y + m[5]], s: [sx * 100, sy * 100], r: Math.atan2(m[1], m[0]) * 180 / Math.PI };
|
|
2408
|
+
};
|
|
2409
|
+
const poseTransform = (entry) => {
|
|
2410
|
+
const id = entry.node.id;
|
|
2411
|
+
const tr = STATIC_TR();
|
|
2412
|
+
const c = [entry.center.x, entry.center.y].map(round2);
|
|
2413
|
+
const own = (p) => track(id, p);
|
|
2414
|
+
const layered = ["x", "y", "rotation", "scaleX", "scaleY"].some((p) => layers(id, p).length);
|
|
2415
|
+
const moving = ["x", "y", "rotation", "scaleX", "scaleY", "follow"].some((p) => own(p).length) || layered;
|
|
2416
|
+
if (moving) {
|
|
2417
|
+
const mixed = own("follow").length > 0 || layered;
|
|
2418
|
+
tr.a = { a: 0, k: c };
|
|
2419
|
+
tr.p = animate(mixed ? [...own("x"), ...own("y"), ...own("follow"), ...layers(id, "x"), ...layers(id, "y")] : [...own("x"), ...own("y")], (t) => poseAt(entry, t).p, !mixed);
|
|
2420
|
+
tr.r = animate(mixed ? [...own("rotation"), ...own("follow"), ...layers(id, "rotation")] : own("rotation"), (t) => [poseAt(entry, t).r], !mixed);
|
|
2421
|
+
tr.s = animate(mixed ? [...own("scaleX"), ...own("scaleY"), ...layers(id, "scaleX"), ...layers(id, "scaleY")] : [...own("scaleX"), ...own("scaleY")], (t) => poseAt(entry, t).s, !mixed);
|
|
2422
|
+
if (tr.p.a === 0) tr.p = { a: 0, k: poseAt(entry, 0).p.map(round2) };
|
|
2423
|
+
}
|
|
2424
|
+
const opacity = own("opacity");
|
|
2425
|
+
const rest = _nullishCoalesce(entry.node.opacity, () => ( 1));
|
|
2426
|
+
tr.o = opacity.length ? animate(opacity, (t) => [Math.min(1, Math.max(0, Number(_nullishCoalesce(trackValue(opacity, t), () => ( rest))))) * 100]) : { a: 0, k: round2(rest * 100) };
|
|
2427
|
+
return tr;
|
|
2428
|
+
};
|
|
2429
|
+
const owner = (path, p) => {
|
|
2430
|
+
const entry = nodes.get(path.id);
|
|
2431
|
+
if (!entry) return void 0;
|
|
2432
|
+
return [...entry.ancestors, path.id].find((id) => timeline.tracks.has(motionTrackKey(id, p)));
|
|
2433
|
+
};
|
|
2434
|
+
const paintOf = (paint, what, id) => {
|
|
2435
|
+
if (paint === void 0 || paint === null || paint === "none") return null;
|
|
2436
|
+
const c = rgba(paint);
|
|
2437
|
+
if (!c) notes.push(`"${id}": its ${what} is a gradient, which is not exported; it is left unpainted.`);
|
|
2438
|
+
return c;
|
|
2439
|
+
};
|
|
2440
|
+
const pathShapes = (path) => {
|
|
2441
|
+
const items = [];
|
|
2442
|
+
const shapeSegments = [...timeline.tracks.entries()].filter(([k]) => k.startsWith(`${path.id}\0`) && (k.endsWith("\0shape") || k.includes("\0effect:"))).flatMap(([, v]) => v);
|
|
2443
|
+
const geometryAt = (t) => {
|
|
2444
|
+
if (!shapeSegments.length) return _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
|
|
2445
|
+
const now = _optionalChain([indexMotionNodes, 'call', _67 => _67(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _68 => _68.get, 'call', _69 => _69(path.id), 'optionalAccess', _70 => _70.node]);
|
|
2446
|
+
return now && now.type === "path" ? _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, [now])[0]) : _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
|
|
2447
|
+
};
|
|
2448
|
+
const rest = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(0)), round2);
|
|
2449
|
+
rest.forEach((contour, n) => {
|
|
2450
|
+
let ks = { a: 0, k: contour };
|
|
2451
|
+
if (shapeSegments.length) {
|
|
2452
|
+
const keys = [];
|
|
2453
|
+
let fits = true;
|
|
2454
|
+
const count = Math.max(1, Math.ceil(end * fps / 1e3));
|
|
2455
|
+
for (let k = 0; k <= count && fits; k++) {
|
|
2456
|
+
const t = end * k / count;
|
|
2457
|
+
const c = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(t)), round2)[n];
|
|
2458
|
+
if (!c || c.v.length !== contour.v.length || c.c !== contour.c) fits = false;
|
|
2459
|
+
else keys.push({ t: frame(t), s: [c], ...k < count ? { o: { x: [0], y: [0] }, i: { x: [1], y: [1] } } : {} });
|
|
2460
|
+
}
|
|
2461
|
+
if (fits) {
|
|
2462
|
+
ks = { a: 1, k: keys };
|
|
2463
|
+
sampled += keys.length;
|
|
2464
|
+
} else notes.push(`"${path.id}": its morph changes how many points the outline has, which Lottie cannot tween; it shows its starting shape.`);
|
|
2465
|
+
}
|
|
2466
|
+
items.push({ ty: "sh", nm: `${path.id} ${n + 1}`, ks });
|
|
2467
|
+
});
|
|
2468
|
+
const fillFrom = owner(path, "fill");
|
|
2469
|
+
const strokeFrom = owner(path, "stroke");
|
|
2470
|
+
const widthFrom = owner(path, "strokeWidth");
|
|
2471
|
+
const fadeFrom = owner(path, "fillOpacity");
|
|
2472
|
+
const drawFrom = owner(path, "drawEnd");
|
|
2473
|
+
if (drawFrom) {
|
|
2474
|
+
const draw = track(drawFrom, "drawEnd");
|
|
2475
|
+
items.push({ ty: "tm", nm: "Draw on", s: { a: 0, k: 0 }, e: animate(draw, (t) => [Math.min(1, Math.max(0, Number(_nullishCoalesce(trackValue(draw, t), () => ( 1))))) * 100]), o: { a: 0, k: 0 }, m: 1 });
|
|
2476
|
+
}
|
|
2477
|
+
if (owner(path, "drawStart")) notes.push(`"${path.id}": drawing from a start other than 0 is not exported.`);
|
|
2478
|
+
const stroke = paintOf(path.stroke, "stroke", path.id);
|
|
2479
|
+
if (stroke || strokeFrom) {
|
|
2480
|
+
const colour = strokeFrom ? track(strokeFrom, "stroke") : [];
|
|
2481
|
+
items.push({
|
|
2482
|
+
ty: "st",
|
|
2483
|
+
nm: "Stroke",
|
|
2484
|
+
c: colour.length ? animate(colour, (t) => _nullishCoalesce(_nullishCoalesce(rgba(trackValue(colour, t)), () => ( stroke)), () => ( [0, 0, 0, 1]))) : { a: 0, k: (_nullishCoalesce(stroke, () => ( [0, 0, 0, 1]))).map(round2) },
|
|
2485
|
+
o: { a: 0, k: 100 },
|
|
2486
|
+
w: widthFrom ? animate(track(widthFrom, "strokeWidth"), (t) => [Math.max(0, Number(_nullishCoalesce(_nullishCoalesce(trackValue(track(widthFrom, "strokeWidth"), t), () => ( path.strokeWidth)), () => ( 1))))]) : { a: 0, k: round2(_nullishCoalesce(path.strokeWidth, () => ( 1))) },
|
|
2487
|
+
lc: path.strokeLinecap === "round" ? 2 : path.strokeLinecap === "square" ? 3 : 1,
|
|
2488
|
+
lj: path.strokeLinejoin === "round" ? 2 : path.strokeLinejoin === "bevel" ? 3 : 1,
|
|
2489
|
+
ml: 4
|
|
2490
|
+
});
|
|
2491
|
+
if (_optionalChain([path, 'access', _71 => _71.strokeDasharray, 'optionalAccess', _72 => _72.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
|
|
2492
|
+
if (path.strokeAlign && path.strokeAlign !== "center") notes.push(`"${path.id}": its stroke is drawn centred (Lottie has no inside or outside strokes).`);
|
|
2493
|
+
}
|
|
2494
|
+
const fill = paintOf(path.fill, "fill", path.id);
|
|
2495
|
+
if (fill || fillFrom) {
|
|
2496
|
+
const colour = fillFrom ? track(fillFrom, "fill") : [];
|
|
2497
|
+
const fade = fadeFrom ? track(fadeFrom, "fillOpacity") : [];
|
|
2498
|
+
items.push({
|
|
2499
|
+
ty: "fl",
|
|
2500
|
+
nm: "Fill",
|
|
2501
|
+
c: colour.length ? animate(colour, (t) => _nullishCoalesce(_nullishCoalesce(rgba(trackValue(colour, t)), () => ( fill)), () => ( [0, 0, 0, 1]))) : { a: 0, k: (_nullishCoalesce(fill, () => ( [0, 0, 0, 1]))).map(round2) },
|
|
2502
|
+
o: fade.length ? animate(fade, (t) => [Math.min(1, Math.max(0, Number(_nullishCoalesce(trackValue(fade, t), () => ( 1))))) * 100]) : { a: 0, k: 100 },
|
|
2503
|
+
r: path.fillRule === "evenodd" ? 2 : 1
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
return items;
|
|
2507
|
+
};
|
|
2508
|
+
const nodeGroup = (node) => {
|
|
2509
|
+
const entry = nodes.get(node.id);
|
|
2510
|
+
if (timeline.tracks.has(motionTrackKey(node.id, "clipRect")) || timeline.tracks.has(motionTrackKey(node.id, "clipCircle")))
|
|
2511
|
+
notes.push(`"${node.id}": its wipe, iris or split reveal is not exported (Lottie masks belong to layers); it fades with its opacity only.`);
|
|
2512
|
+
let inner;
|
|
2513
|
+
const innerTr = STATIC_TR();
|
|
2514
|
+
if (node.type === "path") inner = [...pathShapes(node)];
|
|
2515
|
+
else {
|
|
2516
|
+
inner = [...node.children].reverse().map(nodeGroup);
|
|
2517
|
+
const m = _chunkYJLCBA7Vcjs.nodeMatrix.call(void 0, node);
|
|
2518
|
+
if (m !== _chunkYJLCBA7Vcjs.IDENTITY_MATRIX) {
|
|
2519
|
+
const parts = staticParts(m);
|
|
2520
|
+
if (!parts) notes.push(`"${node.id}": its transform skews, which is not exported; it is drawn without the skew.`);
|
|
2521
|
+
else {
|
|
2522
|
+
innerTr.p = { a: 0, k: parts.p.map(round2) };
|
|
2523
|
+
innerTr.s = { a: 0, k: parts.s.map(round2) };
|
|
2524
|
+
innerTr.r = { a: 0, k: round2(parts.r) };
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
const body = { ty: "gr", nm: `${node.id} (own)`, it: [...inner, innerTr] };
|
|
2529
|
+
return { ty: "gr", nm: _nullishCoalesce(node.name, () => ( node.id)), ...node.visible === false ? { hd: true } : {}, it: [body, poseTransform(entry)] };
|
|
2530
|
+
};
|
|
2531
|
+
const artwork = { ...scene.artwork, nodes: _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, scene.artwork.nodes) };
|
|
2532
|
+
const content = [...artwork.nodes].reverse().map(nodeGroup);
|
|
2533
|
+
if (artwork.background && rgba(artwork.background))
|
|
2534
|
+
content.push({ ty: "gr", nm: "Background", it: [{ ty: "rc", nm: "Background", p: { a: 0, k: [artwork.width / 2, artwork.height / 2] }, s: { a: 0, k: [artwork.width, artwork.height] }, r: { a: 0, k: 0 } }, { ty: "fl", c: { a: 0, k: rgba(artwork.background).map(round2) }, o: { a: 0, k: 100 }, r: 1 }, STATIC_TR()] });
|
|
2535
|
+
const camera = track("", "camera");
|
|
2536
|
+
const root = STATIC_TR();
|
|
2537
|
+
if (camera.length) {
|
|
2538
|
+
const box = (t) => _nullishCoalesce(trackValue(camera, t), () => ( [0, 0, artwork.width, artwork.height]));
|
|
2539
|
+
root.a = animate(camera, (t) => box(t).slice(0, 2), false);
|
|
2540
|
+
root.s = animate(camera, (t) => [artwork.width / box(t)[2] * 100, artwork.height / box(t)[3] * 100], false);
|
|
2541
|
+
}
|
|
2542
|
+
const op = Math.max(1, Math.ceil(end * fps / 1e3));
|
|
2543
|
+
const lottie = {
|
|
2544
|
+
v: "5.7.4",
|
|
2545
|
+
fr: fps,
|
|
2546
|
+
ip: 0,
|
|
2547
|
+
op,
|
|
2548
|
+
w: artwork.width,
|
|
2549
|
+
h: artwork.height,
|
|
2550
|
+
nm: scene.name,
|
|
2551
|
+
ddd: 0,
|
|
2552
|
+
assets: [],
|
|
2553
|
+
layers: [
|
|
2554
|
+
{
|
|
2555
|
+
ddd: 0,
|
|
2556
|
+
ind: 1,
|
|
2557
|
+
ty: 4,
|
|
2558
|
+
nm: scene.name,
|
|
2559
|
+
sr: 1,
|
|
2560
|
+
ks: { o: { a: 0, k: 100 }, r: { a: 0, k: 0 }, p: { a: 0, k: [0, 0, 0] }, a: { a: 0, k: [0, 0, 0] }, s: { a: 0, k: [100, 100, 100] } },
|
|
2561
|
+
ao: 0,
|
|
2562
|
+
shapes: [{ ty: "gr", nm: "Camera", it: [...content, root] }],
|
|
2563
|
+
ip: 0,
|
|
2564
|
+
op,
|
|
2565
|
+
st: 0,
|
|
2566
|
+
bm: 0
|
|
2567
|
+
}
|
|
2568
|
+
],
|
|
2569
|
+
markers: timeline.shots.map((s) => ({ tm: frame(s.start), cm: _nullishCoalesce(s.label, () => ( s.id)), dr: frame(s.end - s.start) }))
|
|
2570
|
+
};
|
|
2571
|
+
return { lottie, sampled, notes: [...new Set(notes)] };
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2186
2574
|
// src/animation-editor/controller.ts
|
|
2187
2575
|
var BLANK = { width: 800, height: 450, background: "#ffffff", nodes: [] };
|
|
2188
2576
|
var AnimationEditorController = (_class = class {
|
|
@@ -2250,7 +2638,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2250
2638
|
time: p.time,
|
|
2251
2639
|
playing: p.playing,
|
|
2252
2640
|
loop: p.loop,
|
|
2253
|
-
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call',
|
|
2641
|
+
currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _73 => _73(timeline, p.time), 'optionalAccess', _74 => _74.id]), () => ( null)),
|
|
2254
2642
|
selection: this.selection,
|
|
2255
2643
|
readOnly: this.readOnly,
|
|
2256
2644
|
error: this.error,
|
|
@@ -2289,7 +2677,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2289
2677
|
this.applying = false;
|
|
2290
2678
|
}
|
|
2291
2679
|
this.notify();
|
|
2292
|
-
_optionalChain([this, 'access',
|
|
2680
|
+
_optionalChain([this, 'access', _75 => _75.onChange, 'optionalCall', _76 => _76(scene)]);
|
|
2293
2681
|
}
|
|
2294
2682
|
commit(label, next) {
|
|
2295
2683
|
const before = this.scene;
|
|
@@ -2395,7 +2783,7 @@ var AnimationEditorController = (_class = class {
|
|
|
2395
2783
|
}
|
|
2396
2784
|
/** The scene at `time` (default: the playhead) as SVG. */
|
|
2397
2785
|
toSvg(time = this.player.currentTime, options = {}) {
|
|
2398
|
-
return
|
|
2786
|
+
return motionStageSvg(this.scene, time, options);
|
|
2399
2787
|
}
|
|
2400
2788
|
/** The scene as a Flair `SvgMotion`. */
|
|
2401
2789
|
toFlairMotion(options = {}) {
|
|
@@ -2755,10 +3143,20 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2755
3143
|
time: MS("Time from the start"),
|
|
2756
3144
|
shot: { ...SHOT_ID, description: 'Render a moment of this shot instead of "time".' },
|
|
2757
3145
|
at: { type: "string", enum: ["start", "middle", "end"], description: "With shot: which moment (default end: everything drawn, before the next shot)." },
|
|
2758
|
-
format: { type: "string", enum: ["svg", "png"], description: "svg (default) or png (only when the host supports it)." }
|
|
3146
|
+
format: { type: "string", enum: ["svg", "png"], description: "svg (default) or png (only when the host supports it)." },
|
|
3147
|
+
onion_skin: object(
|
|
3148
|
+
{
|
|
3149
|
+
before: { type: "integer", minimum: 0, maximum: 12, description: "Ghost frames before the moment (default 2)." },
|
|
3150
|
+
after: { type: "integer", minimum: 0, maximum: 12, description: "Ghost frames after it (default 0)." },
|
|
3151
|
+
spacing: { anyOf: [{ type: "number", exclusiveMinimum: 0 }, { type: "string", enum: ["commands"] }], description: 'Ms between ghosts (default 83, every other frame at 24 fps), or "commands" for the moments commands start and end.' }
|
|
3152
|
+
},
|
|
3153
|
+
[],
|
|
3154
|
+
"Draw faded frames either side of the moment, to see motion in one picture."
|
|
3155
|
+
),
|
|
3156
|
+
motion_paths: { anyOf: [{ type: "boolean" }, { type: "array", items: { type: "string", minLength: 1 } }], description: "Draw where nodes travel (dashed, with a dot where each change starts or ends): true for every node whose place changes, or a list of ids." }
|
|
2759
3157
|
}),
|
|
2760
3158
|
annotations: { title: "Render a frame", ...READ },
|
|
2761
|
-
handler: async ({ time, shot, at = "end", format = "svg" }) => {
|
|
3159
|
+
handler: async ({ time, shot, at = "end", format = "svg", onion_skin, motion_paths }) => {
|
|
2762
3160
|
const s = scene();
|
|
2763
3161
|
let t = _nullishCoalesce(time, () => ( controller.getState().time));
|
|
2764
3162
|
if (shot !== void 0) {
|
|
@@ -2766,7 +3164,11 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2766
3164
|
if (!timing) throw new Error(`There is no shot "${shot}"; the shots are ${s.shots.map((x) => `"${x.id}"`).join(", ") || "none yet"}.`);
|
|
2767
3165
|
t = at === "start" ? timing.start : at === "middle" ? (timing.commandsStart + timing.commandsEnd) / 2 : Math.max(timing.start, timing.end - 1);
|
|
2768
3166
|
}
|
|
2769
|
-
|
|
3167
|
+
if (Array.isArray(motion_paths)) {
|
|
3168
|
+
const known = new Set(describeMotionElements(s).map((e) => e.id));
|
|
3169
|
+
for (const id of motion_paths) if (!known.has(id)) throw new Error(`There is no element ${namedWithSuggestion(id, known)} to draw a motion path for.`);
|
|
3170
|
+
}
|
|
3171
|
+
const svg = controller.toSvg(t, { pretty: false, ...onion_skin ? { onionSkin: onion_skin } : {}, ...motion_paths ? { motionPaths: motion_paths } : {} });
|
|
2770
3172
|
if (format === "png") {
|
|
2771
3173
|
if (!options.rasterize) throw new Error('This host cannot make PNG images; call render_animation_frame with format "svg".');
|
|
2772
3174
|
const image = await options.rasterize(svg, { width: s.artwork.width, height: s.artwork.height });
|
|
@@ -2791,9 +3193,9 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2791
3193
|
handler: ({ from = 0, to, limit = 200 }) => {
|
|
2792
3194
|
const s = scene();
|
|
2793
3195
|
const timeline = compileMotionScene(s);
|
|
2794
|
-
const words = _nullishCoalesce(_optionalChain([s, 'access',
|
|
3196
|
+
const words = _nullishCoalesce(_optionalChain([s, 'access', _77 => _77.narration, 'optionalAccess', _78 => _78.words]), () => ( []));
|
|
2795
3197
|
const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
|
|
2796
|
-
const shotAt = (ms) => _optionalChain([timeline, 'access',
|
|
3198
|
+
const shotAt = (ms) => _optionalChain([timeline, 'access', _79 => _79.shots, 'access', _80 => _80.find, 'call', _81 => _81((x) => ms >= x.start && ms < x.end), 'optionalAccess', _82 => _82.id]);
|
|
2797
3199
|
const beats = [];
|
|
2798
3200
|
if (s.beat && s.beat.bpm > 0) {
|
|
2799
3201
|
const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
|
|
@@ -2814,6 +3216,24 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2814
3216
|
};
|
|
2815
3217
|
}
|
|
2816
3218
|
};
|
|
3219
|
+
const getMotionPath = {
|
|
3220
|
+
name: "get_motion_path",
|
|
3221
|
+
title: "Read where a node travels",
|
|
3222
|
+
description: "Returns where a node's centre is over time, in document units (sampled), and where it is each time a change that moves it starts or ends. Use it to check a path, spacing or arc without reading pictures; render_animation_frame with motion_paths draws it.",
|
|
3223
|
+
inputSchema: object({
|
|
3224
|
+
node: { type: "string", minLength: 1, description: "Element id from get_animation." },
|
|
3225
|
+
from: { type: "number", minimum: 0, description: "From this time, ms (default 0)." },
|
|
3226
|
+
to: { type: "number", minimum: 0, description: "To this time, ms (default the end)." },
|
|
3227
|
+
step: { type: "number", exclusiveMinimum: 0, description: "Ms between samples (default 33; at most 400 samples)." }
|
|
3228
|
+
}, ["node"]),
|
|
3229
|
+
annotations: { title: "Read where a node travels", ...READ },
|
|
3230
|
+
handler: ({ node, from, to, step }) => {
|
|
3231
|
+
const s = scene();
|
|
3232
|
+
const ids = describeMotionElements(s).map((e) => e.id);
|
|
3233
|
+
if (!ids.includes(node)) throw new Error(`There is no element ${namedWithSuggestion(node, ids)}.`);
|
|
3234
|
+
return motionPath(s, node, { from, to, step });
|
|
3235
|
+
}
|
|
3236
|
+
};
|
|
2817
3237
|
const exportFlairMotion = {
|
|
2818
3238
|
name: "export_flair_motion",
|
|
2819
3239
|
title: "Export as Flair vector motion",
|
|
@@ -2827,6 +3247,14 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2827
3247
|
annotations: { title: "Export as Flair vector motion", ...READ },
|
|
2828
3248
|
handler: ({ fps, curves, behaviors, flair }) => controller.toFlairMotion({ ...fps ? { fps } : {}, ...curves === void 0 ? {} : { curves }, ...behaviors === void 0 ? {} : { behaviors }, ...flair ? { flair } : {} })
|
|
2829
3249
|
};
|
|
3250
|
+
const exportLottie = {
|
|
3251
|
+
name: "export_lottie",
|
|
3252
|
+
title: "Export as Lottie",
|
|
3253
|
+
description: "Returns the animation as Lottie (Bodymovin JSON, v5) for lottie-web and the mobile Lottie players: one shape layer whose groups mirror the artwork, a marker per shot, eased changes as keyframes with bezier easing and the rest sampled. Also returns how many keyframes were sampled and notes on anything not carried over (gradients, dashes, reveal clips, morphs that change point counts).",
|
|
3254
|
+
inputSchema: object({ fps: { type: "number", minimum: 1, maximum: 120, description: "Frame rate (default: the scene's fps, else 30)." } }),
|
|
3255
|
+
annotations: { title: "Export as Lottie", ...READ },
|
|
3256
|
+
handler: ({ fps }) => toLottie(scene(), fps ? { fps } : {})
|
|
3257
|
+
};
|
|
2830
3258
|
const exportFlairEffect = {
|
|
2831
3259
|
name: "export_flair_effect",
|
|
2832
3260
|
title: "Export as a Flair effect",
|
|
@@ -2955,7 +3383,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2955
3383
|
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"]),
|
|
2956
3384
|
annotations: { title: "Add commands to a shot", ...WRITE },
|
|
2957
3385
|
handler: ({ shot, commands, at }) => {
|
|
2958
|
-
const before = _nullishCoalesce(_optionalChain([scene, 'call',
|
|
3386
|
+
const before = _nullishCoalesce(_optionalChain([scene, 'call', _83 => _83(), 'access', _84 => _84.shots, 'access', _85 => _85.find, 'call', _86 => _86((s) => s.id === shot), 'optionalAccess', _87 => _87.run, 'access', _88 => _88.length]), () => ( 0));
|
|
2959
3387
|
const first = _nullishCoalesce(at, () => ( before));
|
|
2960
3388
|
return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
|
|
2961
3389
|
const shots = describeMotionShots(s);
|
|
@@ -3069,7 +3497,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
3069
3497
|
return { redone, ...redone ? { label } : {} };
|
|
3070
3498
|
}
|
|
3071
3499
|
};
|
|
3072
|
-
const reads = [getAnimation, getShot, findElements, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
|
|
3500
|
+
const reads = [getAnimation, getShot, findElements, getMotionPath, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, exportLottie, seekAnimation];
|
|
3073
3501
|
const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
3074
3502
|
return {
|
|
3075
3503
|
id: _nullishCoalesce(options.id, () => ( "animation")),
|
|
@@ -3116,7 +3544,7 @@ Commands
|
|
|
3116
3544
|
Working method
|
|
3117
3545
|
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).
|
|
3118
3546
|
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).
|
|
3119
|
-
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.
|
|
3547
|
+
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; add onion_skin or motion_paths to see the motion in one picture, or get_motion_path for the numbers.
|
|
3120
3548
|
4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.
|
|
3121
3549
|
|
|
3122
3550
|
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.`;
|
|
@@ -3210,5 +3638,11 @@ Keep motion purposeful: one thing moving at a time reads best, ease-out for arri
|
|
|
3210
3638
|
|
|
3211
3639
|
|
|
3212
3640
|
|
|
3213
|
-
|
|
3641
|
+
|
|
3642
|
+
|
|
3643
|
+
|
|
3644
|
+
|
|
3645
|
+
|
|
3646
|
+
|
|
3647
|
+
exports.ANIMATION_EDITOR_AGENT_INSTRUCTIONS = ANIMATION_EDITOR_AGENT_INSTRUCTIONS; exports.AnimationEditorController = AnimationEditorController; exports.FLAIR_EASES = FLAIR_EASES; exports.FLAIR_EXPORT_VERSION = FLAIR_EXPORT_VERSION; exports.MOTION_ADDITIVE_PROPERTIES = MOTION_ADDITIVE_PROPERTIES; exports.MOTION_BLENDS = MOTION_BLENDS; exports.MOTION_CAMERA_MS = MOTION_CAMERA_MS; exports.MOTION_COMMAND_SCHEMA = MOTION_COMMAND_SCHEMA; exports.MOTION_COMMAND_TYPES = MOTION_COMMAND_TYPES; exports.MOTION_DEFAULT_DURATIONS = MOTION_DEFAULT_DURATIONS; exports.MOTION_DEFAULT_HOLD = MOTION_DEFAULT_HOLD; exports.MOTION_DIRECTIONS = MOTION_DIRECTIONS; exports.MOTION_DRAW_EFFECT_ID = MOTION_DRAW_EFFECT_ID; exports.MOTION_DRAW_MS_PER_UNIT = MOTION_DRAW_MS_PER_UNIT; exports.MOTION_DRAW_RANGE = MOTION_DRAW_RANGE; exports.MOTION_EASE_IN_OUT = MOTION_EASE_IN_OUT; exports.MOTION_EASE_OUT = MOTION_EASE_OUT; exports.MOTION_EDIT_OPS = MOTION_EDIT_OPS; exports.MOTION_ENTER_STYLES = MOTION_ENTER_STYLES; exports.MOTION_EXIT_STYLES = MOTION_EXIT_STYLES; exports.MOTION_IRIS_SHAPES = MOTION_IRIS_SHAPES; exports.MOTION_ISSUE_CODES = MOTION_ISSUE_CODES; exports.MOTION_MIN_CHANGE_MS = MOTION_MIN_CHANGE_MS; exports.MOTION_REPEAT_MODES = MOTION_REPEAT_MODES; exports.MOTION_SPEED_RANGE = MOTION_SPEED_RANGE; exports.MOTION_SPLIT_ORIENTATIONS = MOTION_SPLIT_ORIENTATIONS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionBeatAt = motionBeatAt; exports.motionBeatTime = motionBeatTime; exports.motionClipAttrs = motionClipAttrs; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionGhosts = motionGhosts; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionMovingIds = motionMovingIds; 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.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotTimeScale = motionShotTimeScale; exports.motionStageSvg = motionStageSvg; 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.toLottie = toLottie; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
|
|
3214
3648
|
//# sourceMappingURL=index.cjs.map
|