Commit Graph
15 Commits
Author SHA1 Message Date
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
bflatastic 57b76bcc88 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 05:08:13 +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
tylerjenningsw f263a32ab7 Render: an effect parameter's history is read at its exact layer time (#1373)
FxHost::param_at mapped the requested layer time to comp time, and
EvalCtx::value mapped it back. Under a fractional stretch (33.3 %,
133.33 %) that round trip truncates a tick early, so a Hold key at
0.5 s read its previous value at the step starting there: a Birth Rate
cut to 0 at 0.5 s bore one more step of particles (six).

EvalCtx::value_at_layer_time reads the keyframes at the given layer
time exactly, and still runs an expression at the comp time.
EvalCtx::value now uses it, and param_at reads through it.

EffectHost::param_is_static says when a parameter has no keyframes and
no expression. CC Particle World then skips sampling its Birth Rate at
every step, since it's the same throughout.
2026-10-13 21:59:03 -04:00
bflatasticandClaude Opus 5.5 940bea3ba7 Effects: effect points on shape layers start at the layer centre (#227)
Shape and text layers have comp-sized effect bounds centred on their
origin, but effects measured positions from the origin: a point at its
default "layer centre" sat at the comp's bottom-right corner, so Twirl
on a centred shape changed nothing and Bulge only its bottom-right
quarter. As in After Effects, effects now work in effect space,
measured from the top-left of the layer's effect bounds.

The renderer moves the layer buffer into effect space for the whole
stack (Buf::rebase, CPU and GPU alike) and hands effects everything
else in it: masks, other layers' pixels for layer parameters (each in
its own effect space, so Set Matte and displacement maps see a whole
shape layer), the layer's own frames (layer_input, also what Puppet,
Roto Brush and the Layer panel read) and the comp camera
(CompScene::rebased). Layer bounds are now right for every effect, so
pin_rect and EffectEnv::bounds_origin go and layer_rect serves edge
pinning too. Adjustment layers still run on the comp below as it is.

effect_bounds / EvalCtx::effect_to_comp replace the repeated "comp size
when there is no source" code; the viewer's effect point handles and
eyedroppers, Puppet pins, the Layer panel (paint, Roto Brush) and the
tracker's Corner Pin output use them.

Project schema 2: opening a schema 1 project moves positions held by
effects on shape and text layers by half the comp size, so set points
keep their place (point parameters with keyframes, Paint paths and
clone positions, Puppet seeds and pins, Liquify and Roto Brush
strokes). Points still at their default become the layer centre;
expression controls, adjustment layers and expressions are left as
they are (docs/architecture.md). The disk cache's render version is
bumped.

Tests: default_effect_points_sit_at_the_centre_of_a_shape_layer and
layer_parameters_see_a_whole_shape_layer (render, both fail before),
effect_points_on_a_shape_layer (GPU vs CPU),
opening_a_schema_1_project_moves_shape_layer_effect_points_into_effect_space
(engine), and the effects migration tests.

Part of #227

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:33:03 +07:00
bflatasticandClaude Opus 5.5 0fe6db00e8 M4.9: Preserve frame rate when nested or in render queue, Preserve resolution when nested
Composition Settings ▸ Advanced has both options (they were missing from the model, the
renderer and the dialog):
- Preserve frame rate: nested in another comp, the comp shows only its own frames
  (`EvalCtx::nested_time` floors the precomp layer's source time to the nested comp's frame
  rate; used for the nested frame, its proxy, aux channels, collapsed transformations and the
  viewer's internal wireframes), and the Render Queue renders it at its own rate whatever the
  Frame Rate override (`RenderSettings::rate`).
- Preserve resolution: nested in a comp rendered at Half / Third / Quarter, it renders at full
  size and the buffer's scale places it.
- `comp.settings {preserveFrameRate, preserveResolution}`, `comp.info` reports them, scripts
  read and set `CompItem.preserveNestedFrameRate` / `preserveNestedResolution`, Pre-compose
  copies them, and the dialog registers `dialog.comp.preserveFrameRate` /
  `dialog.comp.preserveResolution`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:33:14 +07:00
Brandon ThomasandClaude Opus 5.5 c33b80614a PRJ-8: proxies — Set Proxy / None / Use Proxy / Create Proxy / Interpret Proxy, Proxy Use; PRJ-3 interpret fields, PAR, alpha guess
Items (footage and comps) carry a proxy rendered in their place at the source's size when
its switch and Render Settings > Proxy Use allow it; Create Proxy queues a half-resolution
render whose post-render action attaches it. Interpret Footage gains Separate Fields
(upper/lower first, fields become frames at twice the rate), Guess alpha with matte colour,
explicit matte colour, Invert Alpha, Interpret As Linear Light; non-square footage, solids
and precomps are stretched by their pixel aspect in the comp. The four proxy stubs are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:11:03 -04:00
Brandon ThomasandClaude Opus 5.5 6d0d7de922 ANM-5: sampleImage, data footage (JSON/CSV/TSV) with footage().sourceData, thisProject, error list, Expression Language menu
sampleImage renders the sampled layer through the evaluating renderer's footage source
(EvalCtx::footage), cached per thread and frame. File > Import reads .json/.csv/.tsv as data
footage kept in the project. expr.errors collects failing expressions for the error bar;
expr.languageMenu lists the categorised Expression Language menu. Marker keys expose
protectedRegion and cue points; colorDepth, timeToFeetAndFrames, comp.activeCamera.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:58:04 -04:00
Brandon ThomasandClaude Opus 5.5 abd39f5fd8 CMP-7: Essential Graphics controls, master properties, .ectemplate templates, Responsive Design — Time
Comps expose controls (properties typed as text/color/slider/checkbox/point/angle/dropdown,
Media Replacement, groups, comments); precomp layers get an Essential Properties group whose
edits override per instance (Push to Comp / Revert); templates export/import as an open ZIP
(manifest.json + project subset + media + poster). Stretched precomps keep protected regions
at their original speed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:44:06 -04:00
Brandon ThomasandClaude Opus 5.5 c5cf189e1a M9.6: text animation depth (selectors, animator properties, per-char 3D, path text, presets)
Range selector complete (units, based on, modes, shapes, smoothness, ease
high/low, randomize), Wiggly and Expression selectors combined by mode;
all AE animator properties incl. line anchor, character offset/value,
skew axis, tracking type, fill/stroke HSB+opacity, blur; More Options
(anchor grouping, fill & stroke order, inter-character blending); Path
Options (text on a mask path); per-character 3D rendered as planes in
the 3D compositor; text animation commands and original presets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:01:19 -04:00
Brandon ThomasandClaude Opus 5.5 83aabf0a87 Merge main into M7 3D branch
Resolve conflicts with perf (layer cache / Arc<Buf>), export (RenderOpts.view: None
for output), timeline (separate dimensions + one-node POI visibility), mask/pen
viewer gestures (alongside camera tools and view-aware layer matrices), key
dialogs (alongside Camera/Light Settings), render queue + MCP command modules.
3D path tangent honours separated position; DOF blurs a per-frame copy so cached
layer buffers stay shared. New test: cached renders match uncached after camera,
focus, light and 3D view changes. Fix a clone-on-copy lint from main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:24:08 -04:00
Brandon ThomasandClaude Opus 5.5 37c07ba5b1 M7.3: Auto-Orient (along path, towards camera), Draft 3D, 3D switch off drops Z
- layer.autoOrient {mode: off|alongPath|towardsCamera|towardsPointOfInterest}
  (Layer > Transform > Auto-Orient…, Cmd+Alt+O); render support for Orient
  Along Path (2D and 3D) and Orient Towards Camera (with recursion guard).
- Layers parented to cameras/lights follow their full rotation.
- Draft 3D turns off lights and shadows (and DOF).
- Turning the 3D switch off discards Z values, Orientation and X/Y Rotation.
- Tests for all of the above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:42:27 -04:00
Brandon ThomasandClaude Opus 5.5 951ed1fa3d M5.2–M5.5: keyframe clipboard, easy ease, velocity/interpolation dialogs, roving, time remap/stretch/reverse/freeze, graph editor, separate dimensions, pick-whip
Engine (all commands with undo/redo + tests in engine/src/tests_timeline.rs):
- keys.copy/paste (Edit Copy/Cut/Paste route to keys when keys are selected;
  relative timing kept, paste at CTI, cross-property paste of same type),
  keys.selectAll (Cmd+Alt+A), keys.nudge* (Alt+Arrow, Alt+Shift+Arrow),
  keys.set / keys.setEase (graph editor drags), keys.info (dialog data),
  keys.timeReverse; keys.interpolation gains continuous/auto Bezier, spatial
  interpolation and roving; keys.velocity takes per-dimension values and
  Continuous.
- Roving keys (keyframe::retime_roving): constant speed along the motion path.
- Layer > Time: layer.timeStretch (percent|duration, hold in/current/out),
  layer.timeReverse (Cmd+Alt+R), layer.enableTimeRemap (Cmd+Alt+T),
  layer.freezeFrame, layer.freezeOnLastFrame; render uses Time Remap as the
  source time for precomp/footage layers.
- prop.separateDimensions (X/Y/Z Position; prop.set on Position writes them).
- layer.setParent compensates so the layer stays in place (compensate:false
  to opt out).
- prop.pickWhip writes AE-style reference expressions (thisComp.layer(..),
  effect(..)(..), mask(..).maskFeather, temp=…;[temp,temp], …[0]).
- prop.setExpression: syntax errors keep the text but disable the expression.
- expr: arrays of properties returned by an expression are resolved.

UI: Keyframe Velocity, Keyframe Interpolation and Time Stretch dialogs;
Graph Editor (value + speed graphs, draggable keys and Bezier/influence
handles, auto-zoom, fit, show selected, separate dimensions, hold/linear/
auto Bezier/Easy Ease buttons); key + layer context menus. Clears the
workspace clippy -D warnings failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:47:29 -04:00
Brandon ThomasandClaude Opus 5.5 76ac3f74f7 M7.1: Classic 3D renderer, cameras, lights, 3D views and camera-tool commands
- render/three_d: camera eval (one/two-node, DOF), default 50mm comp camera,
  orthographic Front/Left/Top/Back/Right/Bottom + perspective Custom Views,
  orbit/pan/dolly rig math; lights (parallel/spot/point/ambient, falloff,
  cone feather) and material shading; per-pixel depth-sorted compositor for
  3D runs (exact intersections, perspective-correct sampling, blend modes,
  track mattes, motion blur), ray-cast planar shadows with transmission and
  diffusion, per-layer DOF blur. RenderOpts.view camera override.
- project: camera iris/highlight props, aperture per comp width, light
  orientation/rotations, two-node auto-orient defaults.
- engine: layer.newCamera/newLight with presets & settings,
  layer.cameraSettings/lightSettings (Layer Settings dispatches),
  view.set3DView + View > Switch 3D View items (F10/F11/F12), last/reset view,
  camera.orbit/pan/dolly, view.get3D; EditorState.views3d.
- demo: "3D Showcase" comp (camera move, key spot + ambient fill, shadows,
  DOF, intersecting planes, gridded floor).
- tests: projection, depth order, intersections, lighting, shadows, DOF,
  views, rig math; command undo/redo. Fix a few pre-existing clippy lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:09:21 -04:00
Brandon ThomasandClaude Opus 5.5 808cd490cd M0.1–M0.7: workspace, time/geom/color/keyframe/project/raster/path/text/effects/render crates
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:05:20 -04:00