@pixerate/editor 0.124.1 → 0.126.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.cjs +3 -3
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +3 -3
- package/dist/animation-editor/index.cjs +422 -85
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +174 -7
- package/dist/animation-editor/index.d.ts +174 -7
- package/dist/animation-editor/index.js +346 -9
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/{chunk-ORHMVUR6.js → chunk-FICSDDDF.js} +5 -3
- package/dist/{chunk-ORHMVUR6.js.map → chunk-FICSDDDF.js.map} +1 -1
- package/dist/chunk-FJ3324TZ.js +59 -0
- package/dist/chunk-FJ3324TZ.js.map +1 -0
- package/dist/{chunk-5EO2IXQE.js → chunk-FPRIUR4T.js} +1 -1
- package/dist/chunk-FPRIUR4T.js.map +1 -0
- package/dist/{chunk-FHYDQOPC.cjs → chunk-JODTP4DP.cjs} +178 -7
- package/dist/chunk-JODTP4DP.cjs.map +1 -0
- package/dist/{chunk-NGYD3AMN.js → chunk-MPQAO33O.js} +2 -2
- package/dist/{chunk-PEHYWUVV.cjs → chunk-O5RVCDEC.cjs} +82 -82
- package/dist/{chunk-PEHYWUVV.cjs.map → chunk-O5RVCDEC.cjs.map} +1 -1
- package/dist/{chunk-JRXJTPES.js → chunk-QFSTQHZY.js} +176 -5
- package/dist/chunk-QFSTQHZY.js.map +1 -0
- package/dist/{chunk-5XXMLBH4.cjs → chunk-QQKTOA4U.cjs} +1 -1
- package/dist/chunk-QQKTOA4U.cjs.map +1 -0
- package/dist/{chunk-4QQZV5Y5.cjs → chunk-SFCPGMCT.cjs} +11 -9
- package/dist/chunk-SFCPGMCT.cjs.map +1 -0
- package/dist/{chunk-WE25VLCO.cjs → chunk-V5YGPRCZ.cjs} +36 -36
- package/dist/{chunk-WE25VLCO.cjs.map → chunk-V5YGPRCZ.cjs.map} +1 -1
- package/dist/chunk-W6QTLHZ3.cjs +59 -0
- package/dist/chunk-W6QTLHZ3.cjs.map +1 -0
- package/dist/{chunk-WATAQ2Z2.js → chunk-WUDPFWAN.js} +2 -2
- package/dist/effects/index.cjs +59 -33
- package/dist/effects/index.cjs.map +1 -1
- package/dist/effects/index.d.cts +4 -5
- package/dist/effects/index.d.ts +4 -5
- package/dist/effects/index.js +33 -7
- package/dist/effects/index.js.map +1 -1
- package/dist/{export-WmYoWmLM.d.cts → export-Bp7ji2r4.d.cts} +1 -2
- package/dist/{export-BdfY_lH0.d.ts → export-C1s4eolZ.d.ts} +1 -1
- package/dist/{export-BGtXgMJ3.d.ts → export-DAyPY94f.d.ts} +1 -2
- package/dist/{export-CbpjwxaI.d.cts → export-DVl4fWy5.d.cts} +1 -1
- package/dist/generation/index.cjs +3 -3
- package/dist/generation/index.cjs.map +1 -1
- package/dist/generation/index.js +3 -3
- package/dist/helpers-BBLlekuM.d.ts +2009 -0
- package/dist/helpers-i95Ob6Vi.d.cts +2009 -0
- package/dist/image-editor/index.cjs +627 -627
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +5 -7
- package/dist/image-editor/index.d.ts +5 -7
- package/dist/image-editor/index.js +13 -13
- package/dist/paint-editor/index.cjs +736 -115
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +248 -13
- package/dist/paint-editor/index.d.ts +248 -13
- package/dist/paint-editor/index.js +634 -13
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/{svg-BDrjRnL1.d.cts → svg-BkRL-fZJ.d.cts} +1 -1
- package/dist/{svg-C-vZ3q50.d.ts → svg-DJKriJ4A.d.ts} +1 -1
- package/dist/tools/index.cjs +2 -2
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/{tools-C7JtLYzo.d.ts → tools-CM8Oe8F3.d.ts} +1 -1
- package/dist/{tools-DHKGHiSK.d.cts → tools-DZCv2kX1.d.cts} +1 -1
- package/dist/vector/index.cjs +1030 -883
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +61 -6
- package/dist/vector/index.d.ts +61 -6
- package/dist/vector/index.js +153 -6
- package/dist/vector/index.js.map +1 -1
- package/dist/video-editor/index.cjs +272 -273
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +4 -6
- package/dist/video-editor/index.d.ts +4 -6
- package/dist/video-editor/index.js +6 -7
- package/dist/video-editor/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-animation-editor/SKILL.md +24 -1
- package/skills/pixerate-paint-editor/SKILL.md +40 -1
- package/skills/pixerate-vector/SKILL.md +15 -0
- package/dist/chunk-4QQZV5Y5.cjs.map +0 -1
- package/dist/chunk-5EO2IXQE.js.map +0 -1
- package/dist/chunk-5XXMLBH4.cjs.map +0 -1
- package/dist/chunk-CN4PJHUG.cjs +0 -234
- package/dist/chunk-CN4PJHUG.cjs.map +0 -1
- package/dist/chunk-FHYDQOPC.cjs.map +0 -1
- package/dist/chunk-JRXJTPES.js.map +0 -1
- package/dist/chunk-QEG3GRN2.js +0 -234
- package/dist/chunk-QEG3GRN2.js.map +0 -1
- package/dist/flair-CxB3fOcU.d.ts +0 -739
- package/dist/flair-D7BxyBwL.d.cts +0 -739
- package/dist/helpers-777mC6dc.d.cts +0 -253
- package/dist/helpers-DX2_7L5t.d.ts +0 -253
- package/dist/types-BcX7sCsn.d.ts +0 -993
- package/dist/types-CznabG7m.d.cts +0 -993
- /package/dist/{chunk-NGYD3AMN.js.map → chunk-MPQAO33O.js.map} +0 -0
- /package/dist/{chunk-WATAQ2Z2.js.map → chunk-WUDPFWAN.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createVectorMorph
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-MPQAO33O.js";
|
|
4
4
|
import {
|
|
5
5
|
MOTION_ISSUE_CODES,
|
|
6
6
|
motionIssue,
|
|
@@ -31,7 +31,14 @@ import {
|
|
|
31
31
|
vectorNodeBounds,
|
|
32
32
|
vectorPathWorldBezier,
|
|
33
33
|
vectorTextHasOutlines
|
|
34
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-FPRIUR4T.js";
|
|
35
|
+
import {
|
|
36
|
+
EffectUnsupportedError,
|
|
37
|
+
EffectsSlot,
|
|
38
|
+
createEffectToolProvider,
|
|
39
|
+
isEffectError,
|
|
40
|
+
resolveEffectValues
|
|
41
|
+
} from "../chunk-QFSTQHZY.js";
|
|
35
42
|
import {
|
|
36
43
|
bezierPathLength,
|
|
37
44
|
bezierPathPointAtLength,
|
|
@@ -40,6 +47,7 @@ import {
|
|
|
40
47
|
transformBezierPath
|
|
41
48
|
} from "../chunk-FG274FYT.js";
|
|
42
49
|
import "../chunk-GLWZXQ4Q.js";
|
|
50
|
+
import "../chunk-4FX5ACVG.js";
|
|
43
51
|
import {
|
|
44
52
|
readZip
|
|
45
53
|
} from "../chunk-33AHNSEB.js";
|
|
@@ -166,7 +174,7 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
|
|
|
166
174
|
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "clips", "drivers", "motionBlur", "loop", "trigger", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
|
|
167
175
|
var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
|
|
168
176
|
var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
|
|
169
|
-
var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps", "split", "cascade"];
|
|
177
|
+
var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps", "split", "cascade", "source"];
|
|
170
178
|
var DRAW_ORDER_KEYS = ["order", "pace", "align", "sortBy", "origin"];
|
|
171
179
|
var COMMAND_KEYS = {
|
|
172
180
|
enter: [...BASE_KEYS, ...DRAW_ORDER_KEYS, "style", "direction", "distance", "scale", "shape", "orientation"],
|
|
@@ -224,6 +232,11 @@ function commandShapeProblems(command3, where) {
|
|
|
224
232
|
if (command3.origin !== void 0 && !isPoint(command3.origin)) p(`"origin" must be { "x": number, "y": number } (document units).`);
|
|
225
233
|
}
|
|
226
234
|
if (command3.note !== void 0 && typeof command3.note !== "string") p(`"note" must be a string.`);
|
|
235
|
+
if (command3.source !== void 0) {
|
|
236
|
+
const src = command3.source;
|
|
237
|
+
if (!isObject(src) || typeof src.instance !== "string" || !src.instance || typeof src.effect !== "string" || !src.effect) p(`"source" must be { instance, effect, provider?, name?, values? } (set by applyMotionEffect).`);
|
|
238
|
+
else unknownKeys(src, ["instance", "effect", "provider", "name", "values"], `${where} (${type}) source`, problems);
|
|
239
|
+
}
|
|
227
240
|
if (type === "wait") {
|
|
228
241
|
if (!isNum(command3.duration) || command3.duration < 0) p(`"duration" must be a number of milliseconds \u2265 0.`);
|
|
229
242
|
return problems;
|
|
@@ -3484,11 +3497,103 @@ ${problems.join("\n")}`);
|
|
|
3484
3497
|
return { scene, notes: [...new Set(ctx.notes)], issues: checkMotionScene(scene), slides: slidePaths.length };
|
|
3485
3498
|
}
|
|
3486
3499
|
|
|
3500
|
+
// src/animation-editor/effects.ts
|
|
3501
|
+
var sourceOf = (c) => c.source;
|
|
3502
|
+
function motionSceneEffects(scene) {
|
|
3503
|
+
const out = /* @__PURE__ */ new Map();
|
|
3504
|
+
const groups = new Set(scene.artwork.nodes.filter((n) => n.type === "group").map((n) => n.id));
|
|
3505
|
+
for (const shot of scene.shots) {
|
|
3506
|
+
for (const c of shot.run) {
|
|
3507
|
+
const s = sourceOf(c);
|
|
3508
|
+
if (!s) continue;
|
|
3509
|
+
let e = out.get(s.instance);
|
|
3510
|
+
if (!e) {
|
|
3511
|
+
const vector = groups.has(s.instance);
|
|
3512
|
+
e = { instance: s.instance, effect: s.effect, ...s.provider ? { provider: s.provider } : {}, ...s.name ? { name: s.name } : {}, values: { ...s.values ?? {} }, kind: vector ? "vector" : "motion", targets: vector ? [s.instance] : [], shots: [], ...vector ? { group: s.instance } : {} };
|
|
3513
|
+
out.set(s.instance, e);
|
|
3514
|
+
}
|
|
3515
|
+
if (!e.shots.includes(shot.id)) e.shots.push(shot.id);
|
|
3516
|
+
if (e.kind === "motion" && "targets" in c) {
|
|
3517
|
+
for (const t of c.targets) if (!e.targets.includes(t)) e.targets.push(t);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
return [...out.values()];
|
|
3522
|
+
}
|
|
3523
|
+
function nextMotionEffectInstance(scene) {
|
|
3524
|
+
const used = /* @__PURE__ */ new Set([...motionSceneEffects(scene).map((e) => e.instance), ...scene.shots.map((s) => s.id)]);
|
|
3525
|
+
const walk = (nodes) => nodes.forEach((n) => (used.add(n.id), n.type === "group" && walk(n.children)));
|
|
3526
|
+
walk(scene.artwork.nodes);
|
|
3527
|
+
for (let i = 1; ; i++) if (!used.has(`fx${i}`)) return `fx${i}`;
|
|
3528
|
+
}
|
|
3529
|
+
var motionArtworkSvg = (artwork) => vectorDocumentToSvg(artwork);
|
|
3530
|
+
var tag = (commands, source) => commands.map((c) => c.type === "wait" ? c : { ...c, source });
|
|
3531
|
+
function motionCommandsFromEffect(artwork, motion, source) {
|
|
3532
|
+
const imported = fromFlairSvgMotion({ svg: motionArtworkSvg(artwork), animate: [...motion.animate ?? []] });
|
|
3533
|
+
const commands = [];
|
|
3534
|
+
imported.scene.shots.forEach((shot, i) => {
|
|
3535
|
+
shot.run.forEach((c, j) => commands.push(i > 0 && j === 0 ? { ...c, with: "after" } : c));
|
|
3536
|
+
});
|
|
3537
|
+
return { commands: tag(commands, source), notes: imported.notes };
|
|
3538
|
+
}
|
|
3539
|
+
function prefixed(nodes, shots, prefix) {
|
|
3540
|
+
const map = /* @__PURE__ */ new Map();
|
|
3541
|
+
const rename = (list) => list.map((n) => {
|
|
3542
|
+
map.set(n.id, prefix + n.id);
|
|
3543
|
+
const next = { ...n, id: prefix + n.id };
|
|
3544
|
+
if (next.type === "group") next.children = rename(n.children);
|
|
3545
|
+
return next;
|
|
3546
|
+
});
|
|
3547
|
+
const out = rename(nodes);
|
|
3548
|
+
const id = (v) => typeof v === "string" && map.has(v) ? map.get(v) : v;
|
|
3549
|
+
const remapped = shots.map((s) => ({
|
|
3550
|
+
...s,
|
|
3551
|
+
run: s.run.map((c) => {
|
|
3552
|
+
const next = { ...c };
|
|
3553
|
+
if (Array.isArray(next.targets)) next.targets = next.targets.map((t) => id(t));
|
|
3554
|
+
for (const k of ["path", "into"]) if (k in next) next[k] = id(next[k]);
|
|
3555
|
+
return next;
|
|
3556
|
+
})
|
|
3557
|
+
}));
|
|
3558
|
+
return { nodes: out, shots: remapped };
|
|
3559
|
+
}
|
|
3560
|
+
function motionPartsFromVectorEffect(artwork, motion, source, box = { x: 0, y: 0, width: artwork.width, height: artwork.height }) {
|
|
3561
|
+
const imported = fromFlairSvgMotion({ svg: motion.svg, animate: [...motion.animate ?? []] }, { hold: 0 });
|
|
3562
|
+
const art = imported.scene.artwork;
|
|
3563
|
+
const prefix = `${source.instance}-`;
|
|
3564
|
+
const { nodes, shots } = prefixed(art.nodes, imported.scene.shots, prefix);
|
|
3565
|
+
const k = Math.min(box.width / art.width, box.height / art.height);
|
|
3566
|
+
const dx = box.x + (box.width - art.width * k) / 2;
|
|
3567
|
+
const dy = box.y + (box.height - art.height * k) / 2;
|
|
3568
|
+
const group = { type: "group", id: source.instance, name: source.name ?? source.effect, children: nodes, transform: [k, 0, 0, k, dx, dy] };
|
|
3569
|
+
const tagged = shots.map((s, i) => ({ ...s, id: i === 0 ? source.instance + "-shot" : `${source.instance}-shot-${i + 1}`, run: tag(s.run, source) }));
|
|
3570
|
+
return { group, shots: tagged.map((s) => s.hold === 0 ? (({ hold: _h, ...rest }) => rest)(s) : s), notes: imported.notes };
|
|
3571
|
+
}
|
|
3572
|
+
function withoutMotionEffect(scene, instance) {
|
|
3573
|
+
let place = null;
|
|
3574
|
+
const removedShots = [];
|
|
3575
|
+
const shots = [];
|
|
3576
|
+
for (const shot of scene.shots) {
|
|
3577
|
+
const run = shot.run.filter((c, i) => {
|
|
3578
|
+
const mine = sourceOf(c)?.instance === instance;
|
|
3579
|
+
if (mine && !place) place = { shot: shot.id, index: i };
|
|
3580
|
+
return !mine;
|
|
3581
|
+
});
|
|
3582
|
+
if (run.length === shot.run.length) shots.push(shot);
|
|
3583
|
+
else if (run.length === 0) removedShots.push(shot.id);
|
|
3584
|
+
else shots.push({ ...shot, run });
|
|
3585
|
+
}
|
|
3586
|
+
const artwork = { ...scene.artwork, nodes: scene.artwork.nodes.filter((n) => n.id !== instance) };
|
|
3587
|
+
return { scene: { ...scene, shots, artwork }, place, removedShots };
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3487
3590
|
// src/animation-editor/controller.ts
|
|
3488
3591
|
var BLANK = { width: 800, height: 450, background: "#ffffff", nodes: [] };
|
|
3489
3592
|
var AnimationEditorController = class {
|
|
3490
3593
|
history;
|
|
3491
3594
|
player;
|
|
3595
|
+
/** The effects provider slot (see `setEffectsProvider`). */
|
|
3596
|
+
effects = new EffectsSlot();
|
|
3492
3597
|
scene;
|
|
3493
3598
|
issues;
|
|
3494
3599
|
selection = null;
|
|
@@ -3506,6 +3611,7 @@ var AnimationEditorController = class {
|
|
|
3506
3611
|
this.issues = checkMotionScene(this.scene);
|
|
3507
3612
|
this.readOnly = options.readOnly ?? false;
|
|
3508
3613
|
this.onChange = options.onChange;
|
|
3614
|
+
if (options.effects) this.effects.set(options.effects);
|
|
3509
3615
|
this.player = new AnimationPlayer({ clock: options.clock, duration: () => compileMotionScene(this.scene).duration, loop: this.scene.loop ?? "none" });
|
|
3510
3616
|
this.state = this.computeState();
|
|
3511
3617
|
this.connect();
|
|
@@ -3516,7 +3622,8 @@ var AnimationEditorController = class {
|
|
|
3516
3622
|
this.player.subscribe(() => this.notify()),
|
|
3517
3623
|
this.history.subscribe(() => {
|
|
3518
3624
|
if (!this.applying) this.notify();
|
|
3519
|
-
})
|
|
3625
|
+
}),
|
|
3626
|
+
this.effects.subscribe(() => this.notify())
|
|
3520
3627
|
);
|
|
3521
3628
|
}
|
|
3522
3629
|
/**
|
|
@@ -3559,7 +3666,8 @@ var AnimationEditorController = class {
|
|
|
3559
3666
|
canRedo: h.canRedo,
|
|
3560
3667
|
undoLabel: h.undoName,
|
|
3561
3668
|
redoLabel: h.redoName,
|
|
3562
|
-
revision: this.revision
|
|
3669
|
+
revision: this.revision,
|
|
3670
|
+
effectsProvider: this.effects.provider
|
|
3563
3671
|
};
|
|
3564
3672
|
}
|
|
3565
3673
|
notify() {
|
|
@@ -3592,7 +3700,7 @@ var AnimationEditorController = class {
|
|
|
3592
3700
|
this.notify();
|
|
3593
3701
|
this.onChange?.(scene);
|
|
3594
3702
|
}
|
|
3595
|
-
commit(label, next) {
|
|
3703
|
+
commit(label, next, merge) {
|
|
3596
3704
|
const before = this.scene;
|
|
3597
3705
|
this.error = null;
|
|
3598
3706
|
if (next === before) {
|
|
@@ -3606,7 +3714,8 @@ var AnimationEditorController = class {
|
|
|
3606
3714
|
undo: () => this.setScene(before),
|
|
3607
3715
|
redo: () => this.setScene(next)
|
|
3608
3716
|
};
|
|
3609
|
-
this.history.
|
|
3717
|
+
if (merge) this.history.executeMerged(command3, { key: merge });
|
|
3718
|
+
else this.history.execute(command3);
|
|
3610
3719
|
}
|
|
3611
3720
|
refuse(message) {
|
|
3612
3721
|
this.error = message;
|
|
@@ -3626,13 +3735,155 @@ var AnimationEditorController = class {
|
|
|
3626
3735
|
if (reason) return this.refuse(reason);
|
|
3627
3736
|
const result = applyMotionEdits(this.scene, edits);
|
|
3628
3737
|
if (!result.ok) return options.dryRun ? result : this.refuse(result.error);
|
|
3629
|
-
if (!options.dryRun) this.commit(label, result.scene);
|
|
3738
|
+
if (!options.dryRun) this.commit(label, result.scene, options.merge);
|
|
3630
3739
|
return result;
|
|
3631
3740
|
}
|
|
3632
3741
|
/** Replaces the artwork (e.g. from a vector editor's `onChange`), as one undo step. */
|
|
3633
3742
|
setArtwork(artwork, label = "Change artwork") {
|
|
3634
3743
|
return this.apply({ op: "set_artwork", artwork }, label);
|
|
3635
3744
|
}
|
|
3745
|
+
// ---------------------------------------------------------------------------------------
|
|
3746
|
+
// Effects
|
|
3747
|
+
// ---------------------------------------------------------------------------------------
|
|
3748
|
+
/** The registered effects provider, or `null`. */
|
|
3749
|
+
get effectsProvider() {
|
|
3750
|
+
return this.effects.provider;
|
|
3751
|
+
}
|
|
3752
|
+
/** Registers (or with `null` removes) the effects provider. Not an edit: no undo step. */
|
|
3753
|
+
setEffectsProvider(provider) {
|
|
3754
|
+
this.effects.set(provider);
|
|
3755
|
+
}
|
|
3756
|
+
/** Registers `provider` and returns a function that removes it again (if it is still the one set). */
|
|
3757
|
+
useEffects(provider) {
|
|
3758
|
+
return this.effects.use(provider);
|
|
3759
|
+
}
|
|
3760
|
+
/** The provider effects applied in the scene. */
|
|
3761
|
+
appliedEffects() {
|
|
3762
|
+
return motionSceneEffects(this.scene);
|
|
3763
|
+
}
|
|
3764
|
+
async effectEdit(work, merge) {
|
|
3765
|
+
const reason = this.readOnlyReason();
|
|
3766
|
+
if (reason) return this.refuse(reason);
|
|
3767
|
+
let plan;
|
|
3768
|
+
try {
|
|
3769
|
+
plan = await work();
|
|
3770
|
+
} catch (error) {
|
|
3771
|
+
if (isEffectError(error) || error instanceof Error) return this.refuse(error.message);
|
|
3772
|
+
throw error;
|
|
3773
|
+
}
|
|
3774
|
+
const r = this.apply({ op: "replace_scene", scene: plan.scene }, plan.label, { merge });
|
|
3775
|
+
if (!r.ok) return r;
|
|
3776
|
+
const effect = motionSceneEffects(this.scene).find((e) => e.instance === plan.instance);
|
|
3777
|
+
if (!effect) return this.refuse("The effect made no changes the scene can play.");
|
|
3778
|
+
return { ok: true, effect, notes: plan.notes };
|
|
3779
|
+
}
|
|
3780
|
+
async describeFor(effect, kinds) {
|
|
3781
|
+
const provider = this.effects.require();
|
|
3782
|
+
if (!provider.applyVectorEffect) throw new EffectUnsupportedError(`${provider.name} has no vector or motion effects.`);
|
|
3783
|
+
const d = await provider.describe(effect);
|
|
3784
|
+
if (!kinds.includes(d.kind)) {
|
|
3785
|
+
const hint = d.kind === "motion" ? "apply it to nodes with apply_effect" : d.kind === "vector" ? "add it with add_vector_effect" : "it draws pixels, which scenes cannot hold";
|
|
3786
|
+
throw new EffectUnsupportedError(`"${d.id}" (${d.name}) is a ${d.kind} effect: ${hint}.`, d.id);
|
|
3787
|
+
}
|
|
3788
|
+
return d;
|
|
3789
|
+
}
|
|
3790
|
+
knownNodes(ids) {
|
|
3791
|
+
const all = /* @__PURE__ */ new Set();
|
|
3792
|
+
const walk = (nodes) => nodes.forEach((n) => (all.add(n.id), n.type === "group" && walk(n.children)));
|
|
3793
|
+
walk(this.scene.artwork.nodes);
|
|
3794
|
+
const missing = ids.filter((id) => !all.has(id));
|
|
3795
|
+
if (missing.length) throw new Error(`The artwork has no node ${missing.map((m) => `"${m}"`).join(", ")}; get_animation lists the node ids.`);
|
|
3796
|
+
if (!ids.length) throw new Error("Give at least one node to animate.");
|
|
3797
|
+
}
|
|
3798
|
+
/**
|
|
3799
|
+
* Applies a provider motion effect to artwork nodes: the provider animates them and the changes
|
|
3800
|
+
* become commands, in a new shot (default) or added to the end of `shot`. One undo step.
|
|
3801
|
+
* Returns the applied effect (its `instance` id changes or removes it).
|
|
3802
|
+
*/
|
|
3803
|
+
applyMotionEffect(effect, targets, options = {}) {
|
|
3804
|
+
return this.effectEdit(async () => {
|
|
3805
|
+
this.knownNodes(targets);
|
|
3806
|
+
const d = await this.describeFor(effect, ["motion"]);
|
|
3807
|
+
const values = resolveEffectValues(d, options.values);
|
|
3808
|
+
const instance = options.instance ?? nextMotionEffectInstance(this.scene);
|
|
3809
|
+
const motion = await this.effects.require().applyVectorEffect(d.id, { svg: motionArtworkSvg(this.scene.artwork), nodeIds: [...targets] }, values);
|
|
3810
|
+
const source = { instance, effect: d.id, provider: this.effects.require().id, name: d.name, values };
|
|
3811
|
+
const { commands, notes } = motionCommandsFromEffect(this.scene.artwork, motion, source);
|
|
3812
|
+
if (!commands.length) throw new Error(`"${d.name}" made no changes the scene can play${notes.length ? `: ${notes.join(" ")}` : ""}.`);
|
|
3813
|
+
let shots = this.scene.shots;
|
|
3814
|
+
if (options.shot) {
|
|
3815
|
+
if (!shots.some((s) => s.id === options.shot)) throw new Error(`There is no shot "${options.shot}"; the shots are ${shots.map((s) => `"${s.id}"`).join(", ")}.`);
|
|
3816
|
+
shots = shots.map((s) => s.id === options.shot ? { ...s, run: [...s.run, ...commands] } : s);
|
|
3817
|
+
} else shots = [...shots, { id: instance, label: d.name, run: commands }];
|
|
3818
|
+
return { scene: { ...this.scene, shots }, label: `Apply ${d.name}`, instance, notes };
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Adds a provider vector effect (one with its own picture: a lower third, a loader): its SVG as
|
|
3823
|
+
* an artwork group (id: the instance) fitted into `box` (default the whole artwork), and its
|
|
3824
|
+
* motion as shots after the last one. One undo step.
|
|
3825
|
+
*/
|
|
3826
|
+
addVectorEffect(effect, options = {}) {
|
|
3827
|
+
return this.effectEdit(async () => {
|
|
3828
|
+
const d = await this.describeFor(effect, ["vector"]);
|
|
3829
|
+
const values = resolveEffectValues(d, options.values);
|
|
3830
|
+
const instance = options.instance ?? nextMotionEffectInstance(this.scene);
|
|
3831
|
+
const motion = await this.effects.require().applyVectorEffect(d.id, motionArtworkSvg(this.scene.artwork), values);
|
|
3832
|
+
const source = { instance, effect: d.id, provider: this.effects.require().id, name: d.name, values };
|
|
3833
|
+
const { group, shots, notes } = motionPartsFromVectorEffect(this.scene.artwork, motion, source, options.box);
|
|
3834
|
+
return { scene: { ...this.scene, artwork: { ...this.scene.artwork, nodes: [...this.scene.artwork.nodes, group] }, shots: [...this.scene.shots, ...shots] }, label: `Add ${d.name}`, instance, notes };
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* Makes an applied effect again with new `values` (merged over its own; `null` resets a
|
|
3839
|
+
* control), in the same place. One undo step.
|
|
3840
|
+
*/
|
|
3841
|
+
updateMotionEffect(instance, values, options = {}) {
|
|
3842
|
+
return this.effectEdit(async () => {
|
|
3843
|
+
const was = motionSceneEffects(this.scene).find((e) => e.instance === instance);
|
|
3844
|
+
if (!was) throw new Error(`There is no applied effect "${instance}"; list_applied_effects lists them.`);
|
|
3845
|
+
const d = await this.describeFor(was.effect, [was.kind]);
|
|
3846
|
+
const base = { ...was.values };
|
|
3847
|
+
const given = {};
|
|
3848
|
+
for (const [k, v] of Object.entries(values)) {
|
|
3849
|
+
if (v !== null) given[k] = v;
|
|
3850
|
+
else if (k in d.controls.properties) base[k] = d.defaults[k];
|
|
3851
|
+
else throw new Error(`"${k}" is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ")}.`);
|
|
3852
|
+
}
|
|
3853
|
+
const merged = { ...base, ...resolveEffectValues({ ...d, defaults: base }, given) };
|
|
3854
|
+
const { scene: bare, place } = withoutMotionEffect(this.scene, instance);
|
|
3855
|
+
const provider = this.effects.require();
|
|
3856
|
+
const source = { instance, effect: d.id, provider: provider.id, name: d.name, values: merged };
|
|
3857
|
+
if (was.kind === "vector") {
|
|
3858
|
+
const old = this.scene.artwork.nodes.find((n) => n.id === instance);
|
|
3859
|
+
const motion2 = await provider.applyVectorEffect(d.id, motionArtworkSvg(bare.artwork), merged);
|
|
3860
|
+
const { group, shots: shots2, notes: notes2 } = motionPartsFromVectorEffect(bare.artwork, motion2, source);
|
|
3861
|
+
const index = this.scene.artwork.nodes.findIndex((n) => n.id === instance);
|
|
3862
|
+
const nodes = [...bare.artwork.nodes];
|
|
3863
|
+
nodes.splice(Math.max(0, index), 0, old?.transform ? { ...group, transform: old.transform } : group);
|
|
3864
|
+
const at = this.scene.shots.findIndex((s) => was.shots.includes(s.id));
|
|
3865
|
+
const kept = bare.shots;
|
|
3866
|
+
const nextShots = [...kept.slice(0, Math.max(0, at)), ...shots2, ...kept.slice(Math.max(0, at))];
|
|
3867
|
+
return { scene: { ...bare, artwork: { ...bare.artwork, nodes }, shots: nextShots }, label: `Change ${d.name}`, instance, notes: notes2 };
|
|
3868
|
+
}
|
|
3869
|
+
const motion = await provider.applyVectorEffect(d.id, { svg: motionArtworkSvg(bare.artwork), nodeIds: was.targets }, merged);
|
|
3870
|
+
const { commands, notes } = motionCommandsFromEffect(bare.artwork, motion, source);
|
|
3871
|
+
let shots = bare.shots;
|
|
3872
|
+
if (place && shots.some((s) => s.id === place.shot)) shots = shots.map((s) => s.id === place.shot ? { ...s, run: [...s.run.slice(0, place.index), ...commands, ...s.run.slice(place.index)] } : s);
|
|
3873
|
+
else {
|
|
3874
|
+
const at = this.scene.shots.findIndex((s) => s.id === place?.shot);
|
|
3875
|
+
shots = [...shots.slice(0, Math.max(0, at)), { id: instance, label: d.name, run: commands }, ...shots.slice(Math.max(0, at))];
|
|
3876
|
+
}
|
|
3877
|
+
return { scene: { ...bare, shots }, label: `Change ${d.name}`, instance, notes };
|
|
3878
|
+
}, options.merge);
|
|
3879
|
+
}
|
|
3880
|
+
/** Removes an applied effect: its commands, shots it alone filled and a vector effect's group. One undo step. Works without a provider. */
|
|
3881
|
+
removeMotionEffect(instance) {
|
|
3882
|
+
const was = motionSceneEffects(this.scene).find((e) => e.instance === instance);
|
|
3883
|
+
if (!was) return this.refuse(`There is no applied effect "${instance}"; list_applied_effects lists them.`);
|
|
3884
|
+
const r = this.apply({ op: "replace_scene", scene: withoutMotionEffect(this.scene, instance).scene }, `Remove ${was.name ?? was.effect}`);
|
|
3885
|
+
return r.ok ? { ok: true, effect: was, notes: [] } : r;
|
|
3886
|
+
}
|
|
3636
3887
|
/** Opens a scene (JSON or object). `clearHistory` drops the undo stack (opening a file); otherwise it is one undo step. */
|
|
3637
3888
|
load(scene, options = {}) {
|
|
3638
3889
|
let parsed;
|
|
@@ -4970,6 +5221,85 @@ function parseMotionDrivers(text) {
|
|
|
4970
5221
|
}
|
|
4971
5222
|
return { drivers };
|
|
4972
5223
|
}
|
|
5224
|
+
|
|
5225
|
+
// src/animation-editor/effect-tools.ts
|
|
5226
|
+
var info = (e) => ({ id: e.instance, effect: e.effect, ...e.name ? { name: e.name } : {}, ...e.provider ? { provider: e.provider } : {}, role: e.kind === "vector" ? "vector picture" : "motion", enabled: true, values: e.values, targets: e.targets, shots: e.shots });
|
|
5227
|
+
function createAnimationEffectsToolProvider(controller, options = {}) {
|
|
5228
|
+
const readOnlyReason = () => {
|
|
5229
|
+
const ro = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
|
|
5230
|
+
if (ro) return typeof ro === "string" ? ro : "The animation is read-only.";
|
|
5231
|
+
return controller.readOnlyReason();
|
|
5232
|
+
};
|
|
5233
|
+
const done = (r, target) => {
|
|
5234
|
+
if (!r.ok) throw new Error(r.error);
|
|
5235
|
+
return { target, effect: info(r.effect), effects: controller.appliedEffects().filter((e) => e.targets.includes(target)).map(info), ...r.notes.length ? { notes: r.notes } : {} };
|
|
5236
|
+
};
|
|
5237
|
+
return createEffectToolProvider({
|
|
5238
|
+
id: options.id ?? "animation-effects",
|
|
5239
|
+
namespace: options.namespace,
|
|
5240
|
+
target: { noun: "node", id: "A node id of the artwork from get_animation (a group animates as one)." },
|
|
5241
|
+
applyUses: ["motion"],
|
|
5242
|
+
applyDetails: "The provider animates the node and its changes become ordinary commands (enter, fade, move, pulse\u2026), in a new shot after the last one, tagged with the effect so it can be changed or removed as a whole. They preview, play and export to Flair like any command. Vector effects with their own picture go through add_vector_effect. index, enabled and opacity do not apply here.",
|
|
5243
|
+
provider: () => controller.effectsProvider,
|
|
5244
|
+
readOnlyReason,
|
|
5245
|
+
list: (target) => {
|
|
5246
|
+
const all = controller.appliedEffects();
|
|
5247
|
+
if (target) return [{ target, effects: all.filter((e) => e.targets.includes(target)).map(info) }];
|
|
5248
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
5249
|
+
for (const e of all) {
|
|
5250
|
+
const key = e.targets[0] ?? e.instance;
|
|
5251
|
+
byTarget.set(key, [...byTarget.get(key) ?? [], info(e)]);
|
|
5252
|
+
}
|
|
5253
|
+
return [...byTarget].map(([t, effects]) => ({ target: t, effects }));
|
|
5254
|
+
},
|
|
5255
|
+
apply: async (target, effect, args) => {
|
|
5256
|
+
if (args.index !== void 0 || args.opacity !== void 0 || args.enabled === false) throw new Error("index, opacity and enabled do not apply to motion effects; the effect becomes commands in a new shot.");
|
|
5257
|
+
return done(await controller.applyMotionEffect(effect, [target], { values: args.values }), target);
|
|
5258
|
+
},
|
|
5259
|
+
update: async (target, instance, changes) => {
|
|
5260
|
+
const extra = ["enabled", "opacity", "index"].filter((k) => changes[k] !== void 0);
|
|
5261
|
+
if (extra.length) throw new Error(`A motion effect only takes values; ${extra.join(", ")} do not apply (move or delete its commands with edit_animation).`);
|
|
5262
|
+
return done(await controller.updateMotionEffect(instance, changes.values ?? {}), target);
|
|
5263
|
+
},
|
|
5264
|
+
remove: async (target, instance) => done(controller.removeMotionEffect(instance), target),
|
|
5265
|
+
subscribe: (notify) => controller.subscribe(() => notify()),
|
|
5266
|
+
extraTools: (ctx) => [
|
|
5267
|
+
{
|
|
5268
|
+
name: "add_vector_effect",
|
|
5269
|
+
title: "Add a vector effect",
|
|
5270
|
+
description: "Adds a provider vector effect that brings its own picture (search_effects with kind vector: lower thirds, loaders, stickers): its SVG becomes an artwork group, fitted into box (default the whole artwork), and its motion becomes shots after the last one. Change it with update_effect (target and effect: the returned instance) or remove it with remove_effect. One undo step. Returns the instance, the group id and any notes on what could not be carried over.",
|
|
5271
|
+
inputSchema: {
|
|
5272
|
+
type: "object",
|
|
5273
|
+
properties: {
|
|
5274
|
+
effect: { type: "string", minLength: 1, description: "A vector effect id from search_effects (kind vector)." },
|
|
5275
|
+
values: ctx.valuesSchema("Control values over the effect's defaults"),
|
|
5276
|
+
box: {
|
|
5277
|
+
type: "object",
|
|
5278
|
+
properties: {
|
|
5279
|
+
x: { type: "number", description: "Left edge, artwork units." },
|
|
5280
|
+
y: { type: "number", description: "Top edge, artwork units." },
|
|
5281
|
+
width: { type: "number", exclusiveMinimum: 0, description: "Width, artwork units." },
|
|
5282
|
+
height: { type: "number", exclusiveMinimum: 0, description: "Height, artwork units." }
|
|
5283
|
+
},
|
|
5284
|
+
required: ["x", "y", "width", "height"],
|
|
5285
|
+
additionalProperties: false,
|
|
5286
|
+
description: "Where the picture goes in the artwork, fitted keeping its proportions (default the whole artwork)."
|
|
5287
|
+
}
|
|
5288
|
+
},
|
|
5289
|
+
required: ["effect"],
|
|
5290
|
+
additionalProperties: false
|
|
5291
|
+
},
|
|
5292
|
+
annotations: { title: "Add a vector effect", ...ctx.annotations.write },
|
|
5293
|
+
handler: async (args) => {
|
|
5294
|
+
ctx.requireProvider();
|
|
5295
|
+
const r = await controller.addVectorEffect(args.effect, { values: args.values, box: args.box });
|
|
5296
|
+
if (!r.ok) throw new Error(r.error);
|
|
5297
|
+
return { instance: r.effect.instance, group: r.effect.group, effect: info(r.effect), ...r.notes.length ? { notes: r.notes } : {} };
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
]
|
|
5301
|
+
});
|
|
5302
|
+
}
|
|
4973
5303
|
export {
|
|
4974
5304
|
ANIMATION_EDITOR_AGENT_INSTRUCTIONS,
|
|
4975
5305
|
AnimationEditorController,
|
|
@@ -5014,6 +5344,7 @@ export {
|
|
|
5014
5344
|
commandShapeProblems,
|
|
5015
5345
|
compileMotionScene,
|
|
5016
5346
|
createAnimationEditorToolProvider,
|
|
5347
|
+
createAnimationEffectsToolProvider,
|
|
5017
5348
|
createMotionScene,
|
|
5018
5349
|
defaultMotionCommand,
|
|
5019
5350
|
describeMotionElements,
|
|
@@ -5039,6 +5370,7 @@ export {
|
|
|
5039
5370
|
isMotionShot,
|
|
5040
5371
|
matchMotionNodes,
|
|
5041
5372
|
motionAddTrackKey,
|
|
5373
|
+
motionArtworkSvg,
|
|
5042
5374
|
motionBeatAt,
|
|
5043
5375
|
motionBeatTime,
|
|
5044
5376
|
motionBlurTimes,
|
|
@@ -5051,6 +5383,7 @@ export {
|
|
|
5051
5383
|
motionCommandRows,
|
|
5052
5384
|
motionCommandSlots,
|
|
5053
5385
|
motionCommandSummary,
|
|
5386
|
+
motionCommandsFromEffect,
|
|
5054
5387
|
motionContactSheetSvg,
|
|
5055
5388
|
motionCueChoices,
|
|
5056
5389
|
motionCueFromKey,
|
|
@@ -5077,6 +5410,7 @@ export {
|
|
|
5077
5410
|
motionNodeAtElement,
|
|
5078
5411
|
motionNodeClips,
|
|
5079
5412
|
motionNodeOffset,
|
|
5413
|
+
motionPartsFromVectorEffect,
|
|
5080
5414
|
motionPath,
|
|
5081
5415
|
motionPathLength,
|
|
5082
5416
|
motionPathsSvg,
|
|
@@ -5085,6 +5419,7 @@ export {
|
|
|
5085
5419
|
motionRevealClip,
|
|
5086
5420
|
motionRevealPadding,
|
|
5087
5421
|
motionRulerTicks,
|
|
5422
|
+
motionSceneEffects,
|
|
5088
5423
|
motionSceneShapeProblems,
|
|
5089
5424
|
motionSceneToJson,
|
|
5090
5425
|
motionShotAt,
|
|
@@ -5095,6 +5430,7 @@ export {
|
|
|
5095
5430
|
motionTextUnits,
|
|
5096
5431
|
motionTrackKey,
|
|
5097
5432
|
namedWithSuggestion,
|
|
5433
|
+
nextMotionEffectInstance,
|
|
5098
5434
|
parseMotionDrivers,
|
|
5099
5435
|
parseMotionFieldInput,
|
|
5100
5436
|
parseMotionNarration,
|
|
@@ -5113,6 +5449,7 @@ export {
|
|
|
5113
5449
|
toLottie,
|
|
5114
5450
|
trackValue,
|
|
5115
5451
|
withMotionLayers,
|
|
5116
|
-
withViewBox
|
|
5452
|
+
withViewBox,
|
|
5453
|
+
withoutMotionEffect
|
|
5117
5454
|
};
|
|
5118
5455
|
//# sourceMappingURL=index.js.map
|