@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.
Files changed (92) hide show
  1. package/dist/agent/index.cjs +3 -3
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.js +3 -3
  4. package/dist/animation-editor/index.cjs +424 -85
  5. package/dist/animation-editor/index.cjs.map +1 -1
  6. package/dist/animation-editor/index.d.cts +174 -7
  7. package/dist/animation-editor/index.d.ts +174 -7
  8. package/dist/animation-editor/index.js +348 -9
  9. package/dist/animation-editor/index.js.map +1 -1
  10. package/dist/chunk-FJ3324TZ.js +59 -0
  11. package/dist/chunk-FJ3324TZ.js.map +1 -0
  12. package/dist/{chunk-5EO2IXQE.js → chunk-FPRIUR4T.js} +1 -1
  13. package/dist/chunk-FPRIUR4T.js.map +1 -0
  14. package/dist/{chunk-YVVSTD3B.js → chunk-G6WXWS4Y.js} +9 -18
  15. package/dist/chunk-G6WXWS4Y.js.map +1 -0
  16. package/dist/{chunk-NGYD3AMN.js → chunk-MPQAO33O.js} +2 -2
  17. package/dist/{chunk-PEHYWUVV.cjs → chunk-O5RVCDEC.cjs} +82 -82
  18. package/dist/{chunk-PEHYWUVV.cjs.map → chunk-O5RVCDEC.cjs.map} +1 -1
  19. package/dist/{chunk-TK53AB4Q.cjs → chunk-QDJKUBWE.cjs} +3 -3
  20. package/dist/{chunk-TK53AB4Q.cjs.map → chunk-QDJKUBWE.cjs.map} +1 -1
  21. package/dist/{chunk-5XXMLBH4.cjs → chunk-QQKTOA4U.cjs} +1 -1
  22. package/dist/chunk-QQKTOA4U.cjs.map +1 -0
  23. package/dist/{chunk-IWSMDQE5.cjs → chunk-UXFI3CDZ.cjs} +13 -22
  24. package/dist/chunk-UXFI3CDZ.cjs.map +1 -0
  25. package/dist/{chunk-WE25VLCO.cjs → chunk-V5YGPRCZ.cjs} +36 -36
  26. package/dist/{chunk-WE25VLCO.cjs.map → chunk-V5YGPRCZ.cjs.map} +1 -1
  27. package/dist/chunk-W6QTLHZ3.cjs +59 -0
  28. package/dist/chunk-W6QTLHZ3.cjs.map +1 -0
  29. package/dist/{chunk-WATAQ2Z2.js → chunk-WUDPFWAN.js} +2 -2
  30. package/dist/{chunk-VFAKN46Z.js → chunk-XZWOLXWA.js} +3 -3
  31. package/dist/{chunk-VFAKN46Z.js.map → chunk-XZWOLXWA.js.map} +1 -1
  32. package/dist/effects/index.cjs +51 -6
  33. package/dist/effects/index.cjs.map +1 -1
  34. package/dist/effects/index.d.cts +39 -8
  35. package/dist/effects/index.d.ts +39 -8
  36. package/dist/effects/index.js +48 -3
  37. package/dist/effects/index.js.map +1 -1
  38. package/dist/{export-TqXMOlDt.d.cts → export-Bp7ji2r4.d.cts} +4 -3
  39. package/dist/{export-BzSRXyH6.d.cts → export-C1s4eolZ.d.ts} +5 -3
  40. package/dist/{export-8RnlngAv.d.ts → export-DAyPY94f.d.ts} +4 -3
  41. package/dist/{export-DM3h7QEI.d.ts → export-DVl4fWy5.d.cts} +5 -3
  42. package/dist/generation/index.cjs +3 -3
  43. package/dist/generation/index.cjs.map +1 -1
  44. package/dist/generation/index.js +3 -3
  45. package/dist/helpers-BBLlekuM.d.ts +2009 -0
  46. package/dist/helpers-i95Ob6Vi.d.cts +2009 -0
  47. package/dist/image-editor/index.cjs +712 -721
  48. package/dist/image-editor/index.cjs.map +1 -1
  49. package/dist/image-editor/index.d.cts +5 -7
  50. package/dist/image-editor/index.d.ts +5 -7
  51. package/dist/image-editor/index.js +19 -28
  52. package/dist/image-editor/index.js.map +1 -1
  53. package/dist/paint-editor/index.cjs +12 -11
  54. package/dist/paint-editor/index.cjs.map +1 -1
  55. package/dist/paint-editor/index.d.cts +3 -5
  56. package/dist/paint-editor/index.d.ts +3 -5
  57. package/dist/paint-editor/index.js +6 -5
  58. package/dist/paint-editor/index.js.map +1 -1
  59. package/dist/{svg-BDrjRnL1.d.cts → svg-BkRL-fZJ.d.cts} +1 -1
  60. package/dist/{svg-C-vZ3q50.d.ts → svg-DJKriJ4A.d.ts} +1 -1
  61. package/dist/tools/index.cjs +2 -2
  62. package/dist/tools/index.cjs.map +1 -1
  63. package/dist/tools/index.js +3 -3
  64. package/dist/{tools-C7JtLYzo.d.ts → tools-CM8Oe8F3.d.ts} +1 -1
  65. package/dist/{tools-DHKGHiSK.d.cts → tools-DZCv2kX1.d.cts} +1 -1
  66. package/dist/vector/index.cjs +1032 -883
  67. package/dist/vector/index.cjs.map +1 -1
  68. package/dist/vector/index.d.cts +61 -6
  69. package/dist/vector/index.d.ts +61 -6
  70. package/dist/vector/index.js +155 -6
  71. package/dist/vector/index.js.map +1 -1
  72. package/dist/video-editor/index.cjs +271 -270
  73. package/dist/video-editor/index.cjs.map +1 -1
  74. package/dist/video-editor/index.d.cts +4 -6
  75. package/dist/video-editor/index.d.ts +4 -6
  76. package/dist/video-editor/index.js +6 -5
  77. package/dist/video-editor/index.js.map +1 -1
  78. package/package.json +1 -1
  79. package/skills/pixerate-animation-editor/SKILL.md +24 -1
  80. package/skills/pixerate-vector/SKILL.md +15 -0
  81. package/dist/chunk-5EO2IXQE.js.map +0 -1
  82. package/dist/chunk-5XXMLBH4.cjs.map +0 -1
  83. package/dist/chunk-IWSMDQE5.cjs.map +0 -1
  84. package/dist/chunk-YVVSTD3B.js.map +0 -1
  85. package/dist/flair-CxB3fOcU.d.ts +0 -739
  86. package/dist/flair-D7BxyBwL.d.cts +0 -739
  87. package/dist/helpers-777mC6dc.d.cts +0 -253
  88. package/dist/helpers-DX2_7L5t.d.ts +0 -253
  89. package/dist/types-BcX7sCsn.d.ts +0 -993
  90. package/dist/types-CznabG7m.d.cts +0 -993
  91. /package/dist/{chunk-NGYD3AMN.js.map → chunk-MPQAO33O.js.map} +0 -0
  92. /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-NGYD3AMN.js";
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-5EO2IXQE.js";
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.execute(command3);
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