* 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>
Add After Effects project import with bounded parsing, explicit loss reports, and regression coverage.
Fetch the cleaned external test corpus at commit 97262276f625afdeeb61787e4cbb74b3f17af510 and verify each fixture against the pinned SHA-256 manifest.
Unselected layers parented to, or track-matted by, a layer that moved into the
new composition kept ids that no longer exist in the outer comp. The command's
default name was always "Pre-comp 1"; it now picks the lowest unused number.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDvxCS8xcR28VArjzn1x7U
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.
A 3D model layer keeps its file's materials, so it has no Base Color,
Metallic, Roughness or Emissive. material.set refused them with "no
Material Options property matched (..., baseColor, metallic, roughness,
...)", naming the parameters it had just refused, and next to a
parameter the layer has it dropped them and reported success.
A parameter that none of the given layers has now fails the command
(nothing is set), naming it and the properties the first layer has. A
value of the wrong type is its own error. The parameter list says which
layer kinds take which.
Cut the fix down: a click off every mesh makes nothing only when the art it resolves to
already has a mesh with that outline. The nearest-art fallback in `region` stays, so a
near miss beside separate art still meshes it. `mesh::outline` is as it was and the
`newMesh` option of puppet.addPin is back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
animator_uid matches a uid before an index, so an animator whose uid
equals an index is picked over the one at that position. The four
commands that take `animator` (layer.addTextAnimatorProperty,
layer.addTextSelector, text.addSelector, text.animatorFontAxes) now say
so; text.addSelector also said only `uid|index` before. The test gives a
second animator uid 1 and checks that `animator: 1` picks it.
layer.addTextAnimatorProperty, layer.addTextSelector and
text.animatorFontAxes take `animator: uid|index`, counted from 1 as the
code and `#n` references do, but the parameters didn't say so and
`animator: 0` failed with only "no animator 0". The parameters now say
1-based, and the error says what the value can be.
camera.solveStatus said `solved: true` for any stored solve, but the solver
keeps the frames it cannot resect as unsolved and interpolates the camera
there. `solved` now needs every frame, the same check camera.exportBlender
makes, and `partial` marks a solve with gaps, next to `solvedFrames`.
- shortcuts::pick chooses the binding for a key press: narrowest scope, then a
physical-key binding, then the first. When two other panels (or two active
contexts) claim the chord at the same rank, neither runs and the status bar
says which panels own it.
- Physical and logical bindings compete by scope rank in one pass, so a
narrower logical binding beats a physical one of an unrelated panel.
- An additional viewer (PanelKind::Viewer) ranks as the Composition panel.
- Two contexts on the same keys are reported as a conflict.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty layers list cleared the composition's markers too (CodeRabbit);
the composition-marker menu uses comp.deleteAllMarkers instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shortcut belongs to the application, a panel or a context (editing text, a paint or
camera tool active, a mask selected). The narrowest active scope runs a chord: an active
context, the focused panel, the panel sharing its shortcuts (Timeline and Composition),
the application. A context's shortcuts never run outside it.
- shortcuts.rs: scope table for engine commands and menu entries, scope_rank,
scope_label; UiCommand can state its scope.
- Conflicts: the same chord in two scopes is an override (both keep working), reported
with overrides: true; shortcuts.conflicts lists same-scope pairs only.
- Scoped now, keys unchanged: Remember / Apply Interpretation and Delete Without
Confirmation (Project), Duplicate Render Item (Render Queue).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds Swedish (Svenska) as an interface language in the menu, panel and
effect catalogs, written from the English source. The effect rows are
partial like Japanese and Georgian: an effect without a Swedish row draws
its English name, so a new effect does not block other PRs (#2244).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>