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