Commit Graph
14 Commits
Author SHA1 Message Date
a439b1bb19 Effect ▸ Install Effect Plug-in… and plug-in bundles (wasm + ScriptUI panel in one go) (#2749)
* plugins: install effect plug-in bundles (wasm + ScriptUI panels) in one go

* plugins: address review - keep listing errors, refuse replacing an installed plug-in

* plugins: report unreadable bundle entries when their metadata cannot be read

* plugins: surface partial install errors in the menu bar, cover the install flow with tests

* plugins: surface partial install errors for async dialog answers too

* plugins: show result errors as compact status text

* plugins: harden installs - confined automation, no panel overwrite, size cap, cleanup errors

* plugins: bound install reads from one handle, detect unreadable panels, clarify install docs

* plugins: drop an unneeded mut in the bounded read

* plugins: saturate the bounded read limit

* plugins: install into the session's Plug-ins folder, refuse hidden single installs and case-variant panels

- Install .wasm files into Session::plugins_dir() (the folder the Plug-in Library
  from #2524 uses), not a path rebuilt from the settings folder; the host tests
  now point both at their temp folder so they never touch the real settings.
- A single install of a hidden file (".x.wasm") was skipped and reported as a
  success that installed nothing: it is now an error.
- Installed names must be bare file names (and not Windows device names on
  Windows), as file.installScript requires, so every installed panel is listed
  and can be uninstalled.
- A panel whose name differs from an installed one only in case replaced it on
  Windows and macOS: compare ignoring case and check the file on disk too.
- The loaded plug-in's source is the installed copy, not the file it came from.
- Document the `bundle` menu parameter, so checked execution accepts it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* plugins: drop needless to_vec in the bundle install test (clippy)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* plugins: keep the loader test's byte vectors, drop to_vec in the install test (clippy)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: STAR Web Studio <hello.starwebstudio@gmail.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:35:54 +01:00
611a282bea Plug-ins: external effect helpers (safe, out-of-process native effects) + plug-in API 2 (#889)
* Plug-ins: API 2 (PluginHost, padding, layer_span)

Split out of the OpenFX work so it can be reviewed on its own. Additive; the WebAssembly ABI
stays at 1.

- point3 / layer / text / hidden parameters, integer sliders, parameter groups.
- EffectPlugin::padding (transparent pixels around the buffer, at most 4096 per side).
- EffectPlugin::render_v2 with a PluginHost: the input at other times, other layers, parameters
  at other times, frame rate, per-instance key, the layer's source range.
- EffectEnv::layer_span from the renderer; MAX_PLUGINS 1024.
- plugin_error: a plug-in effect's last render error, for the UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxacwTpUwqyfpcLgKW8yjS

* Plug-ins: external effect helpers (crates/helper-host)

Native plug-in formats run in separate helper programs, never in EffectCraft's process
(docs/adr/0001-external-effect-helpers.md). New safe-Rust crate effectcraft-helper-host (L3,
native only, unsafe forbidden): it finds helper programs, starts each as a child process and
registers its effects through plug-in API 2, so the UI and command surface need nothing new.

- Protocol v1 (docs/external-helpers.md): JSON lines over stdin / stdout (helper replies are
  prefixed so plug-in chatter is ignored); pixels go through a frame exchange file (/dev/shm on
  Linux), never through the pipe. Render-time callbacks for the input at other times, other layers
  and parameters at other times, with nested renders through the same helper.
- A helper that exits, hangs past its timeout or breaks the protocol is stopped. The render fails
  with a message, the frame is left as it was, and the helper restarts for the next render. An
  effect that stops its helper 3 times is turned off (helper_blocklist.txt).
- Opt-in: nothing starts helpers unless the user asks (Effect > Scan for Effect Helpers), or opts
  in to starting them at launch with EFFECTCRAFT_HELPERS=1, an environment variable, so writing
  to the settings folder alone can't run code. effect.helpers.scan / load are refused for
  confined agent sessions (confine.rs), like file.executeFile.
- Discovery: helpers/ next to the app (Contents/Helpers on macOS), helpers/ in the settings
  folder, EFFECTCRAFT_HELPER_PATH.
- Commands effect.helpers.list / scan / load; Effect menu entries (translated); the last render
  error of an effect shows as a warning sign after its name in Effect Controls.
- serve(): the helper side for Rust helpers. tests/it/main.rs runs the whole protocol (render,
  padding, callbacks, nesting, threads, crash, restart, block list, hang) against a fake helper,
  with nothing to install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxacwTpUwqyfpcLgKW8yjS

* Effect helpers: address review (start failures, frame file mode, nextest listing)

- A helper that can't start (not installed any more, restarting too often, no frame exchange
  file) is reported but no longer charged to the effect being rendered: it is Failure::Refused,
  not Failure::Broken, so it never counts toward turning that effect off. New check in
  tests/it: once the helper is out of restarts, renders of an effect fail without blocking it
  (fails without the fix: the effect was turned off after 3 such renders).
- The frame exchange file is created owner-only (0o600) on Unix: it holds the user's frames and
  /dev/shm is shared. Asserted in the frames unit test.
- tests/it (harness = false) speaks the libtest listing protocol cargo-nextest uses:
  `--list --format terse` prints `helper_protocol: test` (nothing with `--ignored`), and
  `helper_protocol --exact` runs the checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxacwTpUwqyfpcLgKW8yjS

* Effect helpers: bound writes to a helper, drain stderr, keep callbacks in step

- Writes to a helper go through a writer thread with the request timeout: a
  helper that stops reading its stdin (hung with a large callback answer or
  render request pending) no longer blocks the render thread forever while
  holding the helper.
- stderr is drained as bytes (lossy, in 64 KiB pieces) to the end: output that
  isn't UTF-8 stopped the reader, closing the pipe under the helper (EPIPE /
  SIGPIPE on its next write); lines were unbounded.
- Callback images that can't be written are answered with `null` instead of
  failing the render while the helper waits (which left the protocol out of
  step); a frame that can't be written before the request is sent is not
  charged to the effect.
- Mid-render calls use the render's own process: no respawn or heartbeat ping
  while the helper is waiting for an answer.
- Waiting for a busy helper is bounded (load timeout) instead of forever;
  status() no longer blocks on a running render.
- Reap the child when start-up fails after spawning; cap the block list read.
- Scripts start helpers only with Allow Scripts to Write Files and Access
  Network on (they run native code), as for File.execute().
- helper-host declares the workspace's no-panic crate lints; the desktop
  main() keeps its doc comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Effect helpers: rustfmt the fake provider, build the test session in its initializer (clippy)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plug-in Library: offer WebAssembly API 1 releases only; a helper already stopped mid-render isn't charged again

- WasmPlugin loads only modules at the WebAssembly ABI version, which stays 1 while
  PLUGIN_API_VERSION is 2: share it as effects::plugin::WASM_API_VERSION and have the
  library's compatibility check use it, so it never installs an API 2 release that
  can't load.
- with_running: a process that is already gone was stopped by what broke it (a nested
  render, charged there), so this render fails without charging its own effect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* frames: plug-in effects count as reading other frames (plug-in API 2)

With main's narrowed frame invalidation (#2795), an edit inside a precomp under an
effect that reads its layer at other times must reach the layer's whole span.
Plug-in API 2 gives every plug-in effect the input at other times, so any effect
outside ec. counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 11:11:57 +01:00
Matej Kozlovský 6b1f91ffb8 Cryptomatte: read an ID's coverage from the kept probe (#473)
Session::crypto_coverage returns the coverage of one Cryptomatte ID over the layer, read from
the probe Session::crypto_probe_job keeps: at most max_side samples on the long side, each at
the centre of its cell, or None until the probe is built. It is what the Cryptomatte
eyedropper's hover highlight will draw.

Tests cover the coverage of an ID from the kept probe and the cost of a highlight with six
ranks.
2026-11-03 07:22:46 +01:00
Jerome CAMUSandClaude Sonnet 5.5 edb984a75a Fix #2091 copy/paste masks between layers
Edit > Copy with masks selected copied the layer, so Paste duplicated it
instead of adding the mask. Masks now go to their own clipboard and Paste
appends them, with unique names, to the selected unlocked layers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 07:55:23 +01:00
Marco Arnulfo 86268fc4cc MCP: Text Path Options ▸ Path lists the layer's masks as its options, like the Timeline 2026-10-30 00:44:58 +01:00
Marco Arnulfo 701d9df8a5 MCP: get_property and get_layer list an enum property's options (#2085) 2026-10-30 00:44:58 +01:00
bflatastic 57b76bcc88 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 05:08:13 +07:00
bflatastic 6a30a8b8d5 Merge main into #2452; resolve crates/engine/src/cmd/animation.rs crates/engine/src/tests/effects.rs 2026-10-30 01:24:52 +07:00
BFlat b42cbcdb5e Merge pull request #1921 from tylerjenningsw/enhance/keyframe-text-clipboard
Clipboard: export keyframes as After Effects-compatible text
2026-10-30 01:22:04 +07:00
Camille Moine df0a88b795 Fix collapsed precomp Essential Property overrides (#1747)
Carry property, Font and Media Replacement overrides into the collapsed nested EvalCtx so nested layers render beyond the precomp frame instead of falling back to a clipped, flattened image. Key the layer cache on instance overrides to keep distinct collapsed instances isolated.
2026-10-29 15:21:16 +01:00
Matej Kozlovský 4407e82c62 Cryptomatte pick: keep a hover probe, built off the UI thread (#473)
effect.cryptomatteAt rendered the effect's input on every call to learn its pixel grid (up
to ~1 s per hover on a 3D precomp). The aux channels and that grid are now kept in a
CryptoProbe on the Session, keyed by the project revision, layer, effect, frame, scale and
render prefs (nested switches, draft shadows). On footage with nothing animated before the
Cryptomatte effect the frame is the source frame, so a still or a held frame keeps one
probe for every comp time. The effect's own Selection doesn't count: the probe never reads
it, so an animated Selection keeps the probe too.

Session::crypto_probe_ready says whether a query answers from the kept probe and changes
nothing. Session::crypto_probe_job builds it on another thread for the viewer; asking for a
job is starting it. Each started job has a generation: it stores its result only while it
is the newest, a newer job or an edit cancels it, and a panic in the build becomes the
probe's error. A cancelled build checks its generation between its stages (reading the IDs,
rendering the effect's input), so it stops early and lets go of what it read. `probeOnly`
makes cryptomatteAt / pickCryptomatte answer {pending} instead of building on the caller.

A pick re-keys the probe (it only edits the Selection), and undo, redo and history jumps
over picks keep it too (each pick is labelled "Pick Cryptomatte"). Any other edit, footage
changes, Open / New and Session::drop_crypto_probe (the eyedropper disarmed) let it go. The
slot's type is crate-private (the Session field stays public, doc(hidden), so other crates
can still build a Session); crypto_probe_stats and crypto_probe_jobs (jobs run, cancelled
ones included) are doc(hidden), for tests.
2026-10-28 12:29:41 +01:00
tylerjenningsw ecc3e673bc Merge remote-tracking branch 'origin/main' into enhance/keyframe-text-clipboard 2026-10-27 14:13:38 -04:00
tylerjenningsw 337a6c2053 Animation presets: saved effect keyframes start at the playhead (#1971) 2026-10-27 13:44:36 -04:00
aiongg a3ea645fc6 refactor(engine): move commands/ to cmd/, keeping commands as an alias 2026-10-26 21:49:10 +00:00