mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
Merge upstream/main into rfx-masks-shapes
render: upstream moved the tests_*.rs modules into crates/render/src/tests/; the branch's tests_mask_feather.rs now lives at tests/mask_feather.rs and is registered in tests.rs. path: rect() keeps the branch's explicit-outline version with an added NaN guard on the roundness clamp bound (main's fix for the same panic). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-4
@@ -180,6 +180,7 @@ Short recipes; every step is one tool call.
|
||||
| Animate | `add_keyframe {"layer":2,"path":"transform/scale","keys":[{"time":0,"value":[0,0]},{"time":0.5,"value":[100,100]}],"interpolation":"easyEase"}` |
|
||||
| Effect with settings | `list_effects {"filter":"glow"}` → `add_effect {"layer":2,"effect":"Glow","values":{"threshold":40,"radius":30}}` → `set_property {"layer":2,"path":"effects/#1/intensity","value":2}` |
|
||||
| Key a green screen | `add_effect {"layer":2,"effect":"Keylight (1.2)"}` → `execute_command effect.pickColor {"layer":2,"effect":1,"param":"screenColour","x":20,"y":20,"average":true}` (a screen pixel, layer space; samples the effect's input) → `set_property {"layer":2,"path":"effects/#1/screenMatte/clipBlack","value":10}` → `render_frame {"transparent":true}` |
|
||||
| Key objects by Cryptomatte | `add_effect {"layer":2,"effect":"Cryptomatte"}` → `execute_command effect.pickCryptomatte {"layer":2,"effect":1,"x":120,"y":80}` (the ID under that layer pixel replaces the Selection; `"mode":"add"` / `"remove"` edit it; `effect.cryptomatteAt` with the same point only reports the name) → `render_frame` |
|
||||
| Drive with an expression | `set_property {"layer":2,"path":"transform/rotation","expression":"time*90"}` |
|
||||
| Inspect a layer | `get_layer {"layer":2,"flat":true}` (every property with its `path`, value, key count) |
|
||||
| Check the result | `render_frame {"time":1.5,"max_side":640}`; in bridge mode `screenshot {"panel":"Timeline"}` |
|
||||
@@ -216,7 +217,7 @@ Things that tripped up a long agent-driven session (a multi-scene 3D piece built
|
||||
|
||||
### Cookbook (from end-to-end QA)
|
||||
|
||||
The scenarios in `crates/automation/tests/qa/` and `apps/effectcraft-cli/tests/qa_template.rs`
|
||||
The scenarios in `crates/automation/tests/qa/` and `apps/effectcraft-cli/tests/it/qa_template.rs`
|
||||
build real projects through these interfaces; they are worked examples of everything below.
|
||||
|
||||
* **Name things when you create them**: `layer.newText`, `layer.newShape`, `layer.newSolid`,
|
||||
@@ -323,8 +324,8 @@ build real projects through these interfaces; they are worked examples of everyt
|
||||
### Effect positions
|
||||
|
||||
Effect points, Puppet pins (`puppet.addPin`, `puppet.recordPin`), Paint, Liquify and Roto Brush
|
||||
strokes and `effect.pickColor` are in **effect space**: layer pixels from the top-left of the
|
||||
layer's effect bounds. For footage, solids and precomps that is the layer's own pixel grid. Shape
|
||||
strokes, `effect.pickColor` and `effect.pickCryptomatte` are in **effect space**: layer pixels
|
||||
from the top-left of the layer's effect bounds. For footage, solids and precomps that is the layer's own pixel grid. Shape
|
||||
and text layers have comp-sized bounds centred on their content's origin, so in a `w` × `h` comp
|
||||
the point `[w / 2, h / 2]` is that origin (a new effect's default point). `prop.get` / `prop.set`
|
||||
read and write these values as they are.
|
||||
@@ -532,7 +533,10 @@ indices): character attributes
|
||||
starts editing (`position` instead of `box` makes point text; `vertical: true` vertical type).
|
||||
2. `text.insert {"text":"Hello world"}` types at the caret (replacing the selection);
|
||||
`text.setSelection {"start":6,"end":11}` selects "world"; `text.moveCaret {"to":"wordLeft",
|
||||
"extend":true}` moves like the arrow keys; `text.delete {"word":true}` is Alt+Backspace.
|
||||
"extend":true}` moves like the arrow keys; `text.delete {"word":true}` is Alt+Backspace;
|
||||
`text.deleteSurrounding {"before":1,"after":2,"bytes":true}` deletes the characters around the
|
||||
selection without deleting it (what an input method's replacement asks for: the selection stays
|
||||
for the commit that follows; a byte count ending inside a character leaves that character).
|
||||
3. `layer.setText {"range":[6,11], "size":40, "fill":"#ff5500"}` styles the selection (the
|
||||
Character panel does the same); with an empty range it sets the style the next typed text takes.
|
||||
4. `edit.copy`, `edit.paste`, `edit.pasteTextMatchFormatting` and `edit.pasteTextFormattingOnly`
|
||||
@@ -595,6 +599,12 @@ Extension points and the bundled extensions: [plugins.md](plugins.md).
|
||||
properties, FF missing effects, EE Expressions, LL Waveform, UU everything changed from its
|
||||
default. Shift+key adds or removes a set; the same key again hides it; Ctrl/Cmd+` twirls the
|
||||
selected layers open or closed. With no layer selected they apply to every layer.
|
||||
* A row control clicked on one of several selected layers applies to every selected layer, as in
|
||||
After Effects: the A/V switches (video, audio, solo, lock), the Switches column (shy, collapse,
|
||||
quality, fx, frame blending, motion blur, adjustment, 3D), the label swatch and the Modes column
|
||||
(blending mode, Preserve Transparency), one undo step, with the layers the switch doesn't apply
|
||||
to left out. A control on an unselected row changes that layer alone; Alt-clicking an A/V feature
|
||||
still sets the selected layers apart from the rest.
|
||||
* `keys.toggleTransform {"layers"?: [...], "prop": "position"}` (Alt+Shift+A/P/S/R/T) adds or
|
||||
removes a keyframe at the current time on that Transform property of each layer, in one undo
|
||||
step (separated X/Y/Z Position; Orientation and X/Y/Z Rotation on 3D layers).
|
||||
|
||||
@@ -356,7 +356,7 @@ to the preview cache. Scoped allocation failures retain the adaptive budget/CPU
|
||||
|
||||
**Recovery boundary:** the host installs handlers through eframe's CreationContext, after egui's
|
||||
presentation pipelines but before EffectCraft's pipelines. This does not cover that earlier egui
|
||||
startup window. eframe 0.36.2 supports lost-surface recreation, not replacing its live device and
|
||||
startup window. eframe 0.37 supports lost-surface recreation, not replacing its live device and
|
||||
all presentation resources. Device loss therefore requires restarting the presentation backend;
|
||||
CPU compositing alone cannot restore that display. No automatic restart or persistent safe-mode
|
||||
sentinel is implemented here. Browser scope futures are not synchronously awaited; the browser page
|
||||
|
||||
+62
-19
@@ -38,7 +38,7 @@ printf '{"id":1,"method":"engine.execute","params":{"command":"comp.new","params
|
||||
| `engine.execute` | `{command, params?}` | The command's result. Every engine command (`effectcraft-cli commands`) runs exactly as it does headless, so it never opens a dialog: `comp.new {}` creates a default comp. UI-only ids (`tool.*`, `view.*`, `window.*`, `playback.*`, `timeline.*`, `app.*`) fall through to the menu dispatcher. Unknown top-level params are rejected, and the error lists the accepted keys. |
|
||||
| `ui.menu.invoke` | `{id, params?}` | Like a menu click: commands with empty params may open their dialog (New Composition, Solid Settings, file pickers). |
|
||||
| Copy a Media Browser source path | `ui.menu.invoke {id: "mediaBrowser.copySourcePath", params: {path}}` | Copies the exact non-empty path string to the clipboard and returns `{path}`. Supports file/folder paths and virtual source identifiers. Missing, empty or non-string paths return an error. |
|
||||
| UI commands (via either) | | `timeline.column {column, visible?}` (av, keys, label, num, comment, switches, modes, parent, in, out, duration, stretch), `timeline.sourceName {value?}`, `timeline.search {query}`, `timeline.reveal.<kind>` / `timeline.revealAdd.<kind>` (the property shortcuts and Shift+shortcut: position, scale, rotation, opacity, anchor, effects, masks, feather, levels, animated, and the double-press sets maskPath, maskOpacity, timeRemap, expressions, material, paint, selected, missingEffects, waveform), `timeline.keyAt.<anchor\|position\|scale\|rotation\|opacity>` (Alt+Shift+A/P/S/R/T), `timeline.twirlSelected` (Ctrl/Cmd+`), `timeline.collapseAll`, `flowchart.options {layers?, effects?, solids?, direction?: lr\|tb, comp?}`, `flowchart.graph {comp?}` → `{nodes, edges}`, `window.workspace {name}` (all After Effects workspaces). |
|
||||
| UI commands (via either) | | `timeline.column {column, visible?}` (av, keys, label, num, comment, switches, modes, parent, in, out, duration, stretch), `timeline.sourceName {value?}`, `timeline.search {query}`, `timeline.reveal.<kind>` / `timeline.revealAdd.<kind>` (the property shortcuts and Shift+shortcut: position, scale, rotation, opacity, anchor, effects, masks, feather, levels, animated, and the double-press sets maskPath, maskOpacity, timeRemap, expressions, material, paint, selected, missingEffects, waveform), `timeline.keyAt.<anchor\|position\|scale\|rotation\|opacity>` (Alt+Shift+A/P/S/R/T), `timeline.twirlSelected` ``(Ctrl/Cmd+`)``, `timeline.collapseAll`, `flowchart.options {layers?, effects?, solids?, direction?: lr\|tb, comp?}`, `flowchart.graph {comp?}` → `{nodes, edges}`, `window.workspace {name}` (all After Effects workspaces). |
|
||||
| `engine.commands` | `{filter?, enabledOnly?}` | `[{id, label, menu, shortcut, params, enabled, why}]` |
|
||||
| `ui.menu.list` | `{}` | The menu tree with enablement. |
|
||||
| `render.frame` | `{comp?, time?, max_side?, path?, base64?}` | Renders a comp frame through the session, independent of the viewer zoom or resolution. `comp` is an id or name (default: the active comp), `time` is in comp seconds (default: the CTI), `max_side` caps the longest side (0 = full size). Writes a PNG to `path` (default: a temp file) and returns `{comp, time, width, height, path}`. With `base64: true` it returns `{…, png: "<base64>"}` and writes no file. |
|
||||
@@ -51,7 +51,7 @@ export formats, parity summary), `jobs.list` (Progress panel jobs, e.g. the back
|
||||
`footage.check` after File ▸ Open).
|
||||
|
||||
Every engine command is reachable through `engine.execute`, and every interactive widget of every
|
||||
panel registers an element id; both are enforced by `crates/ui-egui/tests/ui_agent_audit.rs`.
|
||||
panel registers an element id; both are enforced by `crates/ui-egui/tests/it/ui_agent_audit.rs`.
|
||||
|
||||
## UI state
|
||||
|
||||
@@ -126,7 +126,7 @@ frames and can be assigned through `shortcuts.set`.
|
||||
|
||||
| Method | Params | Result |
|
||||
|---|---|---|
|
||||
| `ui.screenshot` | `{path?, panel?, id?}` | `{path, width, height}`: a PNG of the window, or cropped to one panel or element. |
|
||||
| `ui.screenshot` | `{path?, panel?, id?}` | `{path, width, height}`: a PNG of the window, or cropped to one panel or element. The panel or element rectangle is resolved when the capture is issued (after the UI settles), so it follows a panel that moved; a panel or id that is not on screen is retried and then reported, never replaced by a whole-window shot. |
|
||||
|
||||
Element ids are stable, for example `tools.Selection`, `panel.Timeline`, `panel.tab.EffectControls`,
|
||||
`panel.tab.Timeline.<comp>` (one Timeline tab per open comp; `panel.tab.Timeline` is the shown
|
||||
@@ -186,20 +186,63 @@ including outside the layer's `[in, out)` range. Timing metadata carries outside
|
||||
`prop.get.time` is comp seconds. For these commands, `path` or `prop` identifies a property, not a group.
|
||||
Set a layer's offset with `layer.timing {layer, start}` (comp seconds).
|
||||
|
||||
Panel moves, tab ordering, edge splits and floating geometry use shared docking transactions.
|
||||
`window.floatPanel` requires exactly four finite numbers when `rect` is supplied. Invalid
|
||||
geometry or missing source/anchor panels leaves the layout unchanged. The persisted workspace
|
||||
shape is unchanged: accordion open states and explicit heights, floating groups, and dynamic
|
||||
viewer/ScriptUI identities remain app data. Pointer drops use the same shared placement rules
|
||||
as commands, with floating groups resolved in their actual window stacking order.
|
||||
Panels dock in craft-ui's shared docking (`craft_ui::docking`): split frames, tab groups,
|
||||
stacked panels and floating windows. Every change, by pointer, menu or command, is one
|
||||
transaction that checks the whole result; an invalid one returns an error and leaves the layout
|
||||
unchanged. No move or float may leave the docked area empty; closing its last panel is allowed.
|
||||
Panels name themselves by id (`"EffectControls"`), by title (`"Effect Controls"`) where a command
|
||||
takes a name, or as `{"Viewer":42}` and `{"ScriptPanel":77}` for viewers and ScriptUI panels.
|
||||
|
||||
`window.dockAction` accepts `{action}` using the shared docking action's JSON form. It
|
||||
covers tab moves and ordering, split and floating geometry, activation, and closing.
|
||||
For example, `{"action":{"ResizeSplit":{"path":[],"size":{"FixedFirst":360}}}}`
|
||||
resizes the root split; path entries use `false` for the first child and `true` for
|
||||
the second. `{"action":{"MoveFloating":{"panel":"Info","rect":[45,65,420,320]}}}`
|
||||
repositions and resizes the floating group containing Info. Invalid actions leave the
|
||||
layout unchanged. Pointer gestures dispatch through this same catalog command.
|
||||
EffectCraft also supports `SetStackOpen` and `ResizeStack`. Dynamic panel identities use
|
||||
`{"Viewer":42}` or `{"ScriptPanel":77}` in the action. Closing runs the existing viewer
|
||||
and ScriptUI lifecycle callbacks; opening uses the existing panel creation commands.
|
||||
- `window.dockAction {action}` applies one craft-ui `Action` in its JSON form. For example,
|
||||
`{"action":{"ResizeSplit":{"path":[],"size":{"FixedFirst":360}}}}` resizes the root split
|
||||
(path entries are `false` for the first child and `true` for the second), and
|
||||
`{"action":{"MoveFloating":{"panel":"Info","rect":[45,65,420,320]}}}` moves and resizes the
|
||||
floating window holding Info. `Open` and `OpenAt` are refused: panels open through the panel
|
||||
commands (`window.panel`, `ui.panel.show`), which run the viewer and ScriptUI lifecycles.
|
||||
Closing runs them too. Pointer gestures dispatch through this command.
|
||||
- craft-ui's commands: `window.panel.move`, `.float`, `.dock`, `.activate`, `.close`,
|
||||
`.closeGroup`, `.closeOthers`, `.minimize`, `.iconic`, `.window` and `.layout`, with the params
|
||||
of craft-ui's `DockCommand` (`docs/ui-docking.md` in craft-libs). `window.panel.<Panel>`
|
||||
(`window.panel.Align`) opens and shows that panel. `window.panel.activate` on a closed panel
|
||||
reopens it where it was.
|
||||
- `ui.dock {operation, panel, …}` or `ui.dock {action}`: craft-ui's `ui.dock` operations
|
||||
(`open`, `close`, `activate`, `float {rect}`, `moveFloating {rect}`,
|
||||
`move {target, zone?, before?}`, `setStackOpen {open}`, `resizeStack {height}`,
|
||||
`resizeSplit {path, size}`; `rect` is `[x, y, width, height]`). It returns
|
||||
`{"docking": workspace}`. `ui.dock.reset` returns the current workspace to its saved or
|
||||
built-in layout and returns the same.
|
||||
- `window.dockPanel {panel, anchor?, zone?, before?}` docks as `window.panel.dock`: `zone` is
|
||||
`center`, `tab`, `left`, `right`, `top`, `bottom`, `above` or `below`; without an anchor a
|
||||
side zone docks along that edge of the whole workspace. `window.floatPanel {panel, rect?}`
|
||||
requires exactly four finite numbers when `rect` is given and otherwise floats a 420x320
|
||||
window in the middle of the app window. `window.closePanel {panel}` closes it.
|
||||
- `window.panelWindow {panel, window?}` puts a panel in a floating window of its own with a
|
||||
desktop place over the panel's place in the main window, and returns `{"window": bool}`. A
|
||||
docked panel floats first. `window` false docks it again, as a tab of the first docked group;
|
||||
without `window` it toggles.
|
||||
|
||||
On desktop every floating window is an OS window of its own (craft-ui's `native-windows`), which
|
||||
can go on another monitor; on Wayland, on the web and where egui embeds viewports, floating
|
||||
windows stay in the main window. Keys pressed in a floating window that none of its widgets uses
|
||||
run the main window's shortcuts, and files dropped on one import as on the main window. While a
|
||||
modal dialog is open, floating windows take no input.
|
||||
|
||||
The automation ids of the docking chrome are `panel.tab.<Panel>`, `panel.drag.<Panel>` (a
|
||||
group's drag bar), `panel.tabs.more.<Panel>`, `panel.menu.<Panel>` (≡), `panel.header.<Panel>`
|
||||
(a stacked panel's open and close button), `panel.tab.<Panel>.lock` (viewer lock),
|
||||
`panel.tab.Timeline.<comp>` and `panel.tab.Timeline.close` (the Timeline's comp tabs),
|
||||
`panel.float.<n>.move`, `.close`, `.resize` and `.resize.<Edge>` for floating window `n`,
|
||||
`dock.gutter.<path>` for dividers (`a`/`b` per split level; a stack's gap below entry `i` adds
|
||||
`s<i>`) and `dock.corner.<outer>.<inner>` where two dividers meet.
|
||||
|
||||
Saved workspaces (`workspaces.json`, version 2) hold craft-ui `Workspace`s: the layout, the
|
||||
closed panels with where each goes back, the lock, and each floating window's desktop place. A
|
||||
version 1 file (EffectCraft 0.10 and earlier) and the older unversioned record of
|
||||
named workspaces load with every panel in place: the current layout, the named workspaces,
|
||||
floating windows and their native window places. A column of stacked frames becomes tab groups
|
||||
split top to bottom: each open frame keeps all its tabs and its height (as a fixed size), and a
|
||||
closed frame's tabs join the open frame above it. A native window's place moves from the OS
|
||||
window's outer position to its content's, by the main window's title bar, before the first save.
|
||||
Panels that were closed stay closed and reopen at their default place. The first save writes version 2 and keeps
|
||||
the earlier file as `workspaces.old.json`. A damaged, unknown or newer file loads as the default
|
||||
layout and is left unchanged.
|
||||
|
||||
+16
-5
@@ -15,7 +15,7 @@ After Effects', and does the preset library exist. The per-effect catalogue is t
|
||||
| Parameter names, order, popups, units, defaults | aligned in M9.12 | Agent-checked against After Effects' documentation, not against a live dump |
|
||||
| On the GPU | **280** (92%) | Measured: registry flags; GPU vs CPU agreement tested per effect |
|
||||
| 32-bit float, no clamping | 306 | Measured: registry flags |
|
||||
| **Rendered output matches After Effects** | **unmeasured** | No effect is compared with an After Effects render yet. `effectcraft_effects::fidelity::compare_frames` exists for the corpus ([fidelity/README.md](fidelity/README.md)); simulation effects are pinned by golden hashes of our own output (M13.27) |
|
||||
| **Rendered output matches After Effects** | **partly measured** | Some colour effects are checked against After Effects 2026 renders of our own test projects: Tint, Tritone, Black & White, Exposure, Levels, Curves, Luma Key, Extract, 4-Color Gradient and Fill within 1–3/255, Brightness & Contrast up to 6.2/255 (Color Correction, Generate and Keying rows below); every other effect is unmeasured. `effectcraft_effects::fidelity::compare_frames` exists for the corpus ([fidelity/README.md](fidelity/README.md)); simulation effects are pinned by golden hashes of our own output (M13.27) |
|
||||
| Animation presets | **54** original presets (8 text-animator, 23 Transitions, 23 Backgrounds) against After Effects' **621** `.ffx` presets | Measured: `crates/engine/presets/text_animators.json`, `crates/engine/presets/transitions.json`, `crates/engine/presets/backgrounds.json`; the After Effects `Presets` folder |
|
||||
| 26.x additions | Curl Noise (26.3) present (#463 open); effect colour labels (26.5) present in Effect Controls only, not in the Timeline; the rebuilt Effect Controls (26.5) missing | Release notes |
|
||||
|
||||
@@ -30,15 +30,15 @@ corpus compares renders, each effect is assumed to differ somewhere, and users k
|
||||
| Audio | 13 | 0 | CPU by design (applied in the mixdown) |
|
||||
| Blur & Sharpen | 16 | 15 | Gaussian Blur interaction and output to be measured first ([fidelity/ae-2026-acceptance.md](fidelity/ae-2026-acceptance.md)) |
|
||||
| Channel | 13 | 13 | — |
|
||||
| Color Correction | 40 | 40 | Tint has Swap Colors (#963); ACES 2.0 / OCIO 2.5 (26.5); Apply Color LUT (#514) and 32-bpc blending (#494) are closed |
|
||||
| Color Correction | 40 | 40 | Tint, Tritone and Black & White (sliders and Tint) match After Effects 2026 renders (Rec. 601 luminance); Levels (Individual Controls) matches After Effects 2026 in 32 bpc (channel controls before RGB, "Off for 32 bpc Color" unclipped; plain Levels' channel order not measured); Brightness & Contrast (non-legacy) close to After Effects 2026 ramps, not identical (up to 6.2/255 off; the test allows 7/255) (negative settings invert the positive curves); Curves interpolates a natural cubic spline through its points, as After Effects 2026 (exact on its grey steps); Tint has Swap Colors (#963); ACES 2.0 / OCIO 2.5 (26.5); Apply Color LUT (#514) and 32-bpc blending (#494) are closed |
|
||||
| Distort | 39 | 35 | Liquify has no viewer brush; #303 (Mesh Warp drag) and #534 (CC Power Pin Unstretch) are closed |
|
||||
| Expression Controls | 9 | 9 | — |
|
||||
| Generate | 26 | 25 | Fractal on the CPU (needs f64); #526 (Reset) and #536 (Write-on marks) are closed |
|
||||
| Generate | 26 | 25 | 4-Color Gradient's Blend matches After Effects 2026 (weights 1 / (d² + Blend²)); Fill's Opacity is the filled layer's opacity, as After Effects 2026 renders it (with a Fill Mask it still mixes with the original, not measured); Fractal on the CPU (needs f64); #526 (Reset) and #536 (Write-on marks) are closed |
|
||||
| Immersive Video | 12 | 12 | — |
|
||||
| Keying | 12 | 12 | Key Cleaner approximated; #533 (wire-removal Frame Offset) is closed |
|
||||
| Keying | 12 | 12 | Luma Key and Extract key on Rec. 601 luminance, as After Effects 2026; Key Cleaner approximated; #533 (wire-removal Frame Offset) is closed |
|
||||
| Matte | 6 | 4 | Mocha shape reads our documented JSON, not Mocha's format |
|
||||
| Noise & Grain | 13 | 12 | Curl Noise (#463); Match Grain on the CPU |
|
||||
| Obsolete | 9 | 9 | — |
|
||||
| Obsolete | 9 | 9 | Luma Key matches After Effects 2026 on a grey ramp (hard cut, Tolerance only for Similar / Dissimilar) |
|
||||
| Paint | 1 | 0 | Tablet pressure not read ([hardware-parity.md](hardware-parity.md)) |
|
||||
| Perspective | 10 | 9 | Chromatic Aberrations pan/tilt crash (#580) |
|
||||
| Simulation | 18 | 18 | Particle Playground lacks Particle Exploder, text particles, ephemeral mapper; CC Particle World camera fixed (#397) |
|
||||
@@ -64,6 +64,17 @@ the GPU is performance, not parity.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-19 | minor | Tritone uses Rec. 601 luminance, measured against After Effects 2026 renders of a red solid and a grey ramp (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Black & White's Tint keeps the gray's luminosity (Color blend), measured against After Effects 2026 renders (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Levels: "Off for 32 bpc Color" no longer clips in 32 bpc projects, unclipped values extrapolate below Output Black; Levels (Individual Controls) applies the channel controls before the RGB ones, measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Brightness & Contrast: negative Brightness / Contrast are the inverse curves of the positive ones (Brightness −40: 20/255 → 6/255 off After Effects 2026; still up to 6.2/255 off, not the same pixels), CPU and GPU |
|
||||
| 2026-10-19 | minor | Luma Key: Key Out Brighter / Darker cut hard at Threshold, measured against After Effects 2026 renders of a ramp (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Luma Key and Extract: Rec. 601 luminance (was Rec. 709), measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | 4-Color Gradient: each point weighs 1 / (d² + Blend²), measured against After Effects 2026 at Blend 10 / 50 / 100 / 300 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Fill without a mask: the layer becomes the fill colour at its alpha × Opacity (was a mix with the original), measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Curves: natural cubic spline through the points, clamped to 0..1 (was a monotone spline), measured against After Effects 2026 (CPU and GPU); Lumetri's curves and Glow's Arbitrary Map keep the monotone spline |
|
||||
| 2026-10-18 | minor | Tint uses Rec. 601 luminance weights, measured against After Effects 2026 renders of red, green, blue and grey solids (CPU and GPU) |
|
||||
| 2026-10-18 | minor | Exposure converts to linear light with a pure 2.2 power curve, as measured in After Effects (Offset 0.0005 / 0.002 / 0.02 / 0.1 on black: 8 / 15 / 43 / 90), not the piecewise sRGB curve; its sliders take After Effects' ranges (Exposure −100..100, slider −4..4; Offset −2..2, slider −0.5..0.5; Gamma Correction 0.1..10) |
|
||||
| 2026-10-12 | minor | Parameters 3,014 to 3,017 (effects.md); #303, #514, #526, #533–#536 marked closed instead of pending |
|
||||
| 2026-10-12 | minor | Effect colour labels (26.5) added in Effect Controls |
|
||||
| 2026-10-12 | minor | Tint's Swap Colors button and agent-drivable command (#963); undo/redo, instance targeting, animated layer-time edits and invalid-input tests |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ These checks use original compositions and public interaction behavior. No Adobe
|
||||
implementation code are included. Adobe's [selection documentation](https://helpx.adobe.com/after-effects/desktop/work-with-layers/select-and-arrange-layers/selecting-arranging-layers.html)
|
||||
describes Timeline range selection and Composition Shift-click/Shift-marquee selection.
|
||||
|
||||
`crates/ui-egui/tests/ui_workflow_selection.rs` exercises actual pointer and keyboard events:
|
||||
`crates/ui-egui/tests/it/ui_workflow_selection.rs` exercises actual pointer and keyboard events:
|
||||
|
||||
- Timeline Shift-click selects the visible range, skipping locked layers; Ctrl/Command-click toggles a layer.
|
||||
- A drag beginning in the empty Timeline outline selects the intersected layer rows without reordering them.
|
||||
|
||||
+21
-1
@@ -70,6 +70,16 @@ next to it.
|
||||
Agents: `file.importProject {"path": "/projects/titles.ecproj"}` returns the comps, the root comps,
|
||||
`primaryComp` (the only root comp, if there is one) and any missing media.
|
||||
|
||||
## Finding a layer's source in the Project panel
|
||||
|
||||
Right-click a layer in the Timeline or Composition panel and choose **Reveal Layer Source in
|
||||
Project** (also Layer ▸ Reveal ▸ Reveal Layer Source in Project). The Project panel opens, its
|
||||
search is cleared, the folders holding the source open, and the source item is selected.
|
||||
|
||||
Layers whose image is generated inside the composition — text, shape, null, camera, light and
|
||||
3D primitive layers — have no Project item, so the command is not offered for them. Adjustment
|
||||
layers do have one: the solid that backs them.
|
||||
|
||||
## Interpret Footage
|
||||
|
||||
Select a footage item in the Project panel and choose **File ▸ Interpret Footage ▸ Main…**
|
||||
@@ -110,6 +120,15 @@ vectors, positions and IDs are never the picture. EXtractoR reads every other la
|
||||
Importing a layered file as a composition refreshes the decoded sources used by its layers,
|
||||
including files overwritten at the same path and byte size.
|
||||
|
||||
- **Replace a layer's source** without rebuilding the layer: Alt-drag (Option-drag on macOS) a
|
||||
footage item, composition or solid from the Project panel onto a layer in the Timeline. The
|
||||
layer keeps its name, timing, transforms, keyframes, expressions, effects, masks, switches,
|
||||
parenting and track matte; only its source changes. A source with audio adds Audio properties
|
||||
when the layer has none. To replace several selected layers with the selected Project item, press
|
||||
Ctrl+Alt+/ (Cmd+Opt+/). Compositions, footage and solids are interchangeable; folders, data files
|
||||
and 3D models are not sources, and a composition can't be nested in itself. Alt-dragging onto a
|
||||
layer is one layer at a time, as in After Effects.
|
||||
Agents: `layer.replaceSource {"item": id, "layers": [ids]}`.
|
||||
- **File ▸ Replace Footage ▸ File…** (Ctrl+H) points the item at another file. Pick a numbered
|
||||
file to replace it with that file's whole sequence (`file.replaceFootage {"path": …,
|
||||
"sequence": false}` for one frame).
|
||||
@@ -129,7 +148,8 @@ including files overwritten at the same path and byte size.
|
||||
File ▸ Open tries each missing file at the same place under the project's new folder, without
|
||||
marking the project modified.
|
||||
- **File ▸ Reload Footage** (Ctrl+Alt+L) reads the files again. A sequence picks up frames
|
||||
added to its folder (a render still in progress) and keeps its interpretation.
|
||||
added to its folder (a render still in progress) and keeps its interpretation; a still is
|
||||
read as its one file, even when numbered files sit next to it.
|
||||
|
||||
**File ▸ Dependencies ▸ Relink Missing Footage…** searches a chosen folder and its subfolders
|
||||
for missing source files on the desktop. It relinks unique exact filenames whose decoded type,
|
||||
|
||||
+4
-4
@@ -72,11 +72,11 @@ are grouped by area; the issue tracker has the rest.
|
||||
| 13 | **3D model formats and materials** | After Effects 26 imports FBX, USD/USDZ, STL besides glTF and OBJ (`usd_plugins`), and Substance 3D materials with mesh displacement (26.0–26.2); we read glTF, OBJ, STL (binary and ASCII, with facet colours) and FBX 7.x (binary and ASCII: meshes, hierarchy, Lambert/Phong materials and textures; no FBX animation or skinning yet) | Product shots and 3D scenes come as FBX/USDZ | 25–45 h | — · [file-format-parity.md](file-format-parity.md) |
|
||||
| 14 | **Object Matte** (26.2, disk cache in 26.5) | First slice (#783): the Object Matte effect (Effect ▸ Matte) and `objectMatte.apply` turn a point (and optional box) on a reference frame into a matte and follow the object up to 600 frames each way, or segment every frame on its own; Roto Brush's engine, cache and model (MobileSAM from Settings ▸ Roto Brush, classical graph cut without it). Since then: the Object Matte tool in the viewer (click / drag a box, hover crosshair), the box as the model's box prompt, background tracking with progress and cancel, Freeze, and segmentations kept in the disk cache (26.5). Missing: a comparison with After Effects ([plan](fidelity/object-matte.md)) | The new one-click roto | 10–20 h | G7 · [target-app-parity.md](target-app-parity.md) |
|
||||
| 15 | **Hardware video decode and encode** | No VideoToolbox, NVENC, Quick Sync or VA-API; #498 asks for it (answered: CUDA / NVENC need Nvidia's proprietary SDK through FFI, which the no-`unsafe` rule forbids); open PR #819 adds hardware decoding via `filmcraft-platform`; FilmCraft has VideoToolbox / NVENC / VA-API paths to borrow | 4K playback and export speed | 25–45 h | G5/G6 · [hardware-parity.md](hardware-parity.md) |
|
||||
| 16 | **Localization depth** | Menus in 10 languages (ja, zh-hans, zh-hant, uk, es, pt-br, ru, fr, de, cs: 624 rows each); panels in 6 (ja, zh-hans, zh-hant, uk, pt-br, cs); effect names translated in Simplified Chinese (306, plus the generated Effect menu), their ~1,650 parameter names untranslated; Korean and Italian (After Effects ships them) absent; Hangul probe added (#599 closed) | After Effects is used heavily in Japan, China, Korea, Brazil, Europe | 60–110 h + native review | G9 · [localization-parity.md](localization-parity.md) |
|
||||
| 16 | **Localization depth** | Menus in 11 languages (ja, zh-hans, zh-hant, uk, es, pt-br, ru, fr, de, cs, ka: 628 rows each); panels in 7 (ja, zh-hans, zh-hant, uk, pt-br, cs, ka); effect names translated in Simplified Chinese and Georgian (307, plus the generated Effect menu), their ~1,650 parameter names untranslated; Korean and Italian (After Effects ships them) absent; Hangul probe added (#599 closed) | After Effects is used heavily in Japan, China, Korea, Brazil, Europe | 60–110 h + native review | G9 · [localization-parity.md](localization-parity.md) |
|
||||
| 17 | **Media Browser lists files import refuses** | `media_browser.rs` lists AVI; `media::is_importable` has none of them (Targa and Radiance HDR import since #745; DPX and Cineon since #756, log shown as stored: no Cineon Settings conversion yet; MXF OP1a / OP-Atom since #767; FilmCraft has no AVI demuxer); #431 and #324 closed | Confusing failures on common VFX formats | 15–30 h | G6 · [file-format-parity.md](file-format-parity.md) |
|
||||
| 18 | **Relative paths and relinking** | Projects store absolute footage paths; no relink when a project moves | Moving or sharing a project breaks it | 6–12 h | G4 · [file-format-parity.md](file-format-parity.md) |
|
||||
| 19 | **Plug-in ecosystem** | No After Effects SDK hosting (#600, #327 OpenFX, #334, #376 plug-in manager); our WebAssembly API has no third-party plug-ins | Many pro projects depend on Trapcode, Element, Saber… | 40–80 h for API growth; SDK hosting needs an owner decision | G8 · [scripting-parity.md](scripting-parity.md) |
|
||||
| 20 | **Multi-monitor and video output** | #486: any panel can move into a native window of its own (panel menu ▸ Move Panel to New Window, `window.panelWindow`) and so onto another screen, restored with the workspace; still missing: dragging tabs between those windows, app shortcuts while one has the focus, named workspaces remembering them, Mercury Transmit to video hardware (we have a second preview window only) | Two-screen setups are standard for professionals | 10–20 h | G2 · [hardware-parity.md](hardware-parity.md) |
|
||||
| 20 | **Multi-monitor and video output** | #486: on Windows, macOS and X11 every floating panel is an OS window of its own (Undock Panel, a drag out of the main window, panel menu ▸ Move Panel to New Window, `window.panelWindow`) and so can go onto another screen (on Wayland, the web and in embedded viewports floating panels stay in the app window, since Wayland reports no window positions); tabs and groups drag between windows, shortcuts work in them, and the current and named workspaces restore their places; still missing: Mercury Transmit to video hardware (we have a second preview window only) | Two-screen setups are standard for professionals | 10–20 h | G2 · [hardware-parity.md](hardware-parity.md) |
|
||||
| 21 | **Small 26.x features** | Proportional Scrubbing (26.2), pasting Illustrator's own clipboard data as shapes (26.3; SVG markup on the clipboard pastes as a shape layer, #738), variable-font filter (26.3), the rebuilt Effect Controls (26.5). Percentage guides (View ▸ Add Percentage Guide, Convert Guides to Percentage / Pixels) and effect colour labels (Effect Controls header bar and right-click Label menu, `effect.setLabel`) are in (#1095), built from the release notes' one-line descriptions and never compared with After Effects: the Timeline's effect rows wear the label as a colour bar on the left edge (#1509), but After Effects' exact UI there is unknown | Users of the current version expect them | 10–20 h left | — · [ui-parity.md](ui-parity.md) |
|
||||
| 22 | **Encoder efficiency** | HEVC / AV1 have no B-frames, multi-reference or SAO / CDEF; Opus no FEC / DTX; single-file intermediates are 8-bit uncompressed QuickTime only (#372 closed by #778: no 16-bpc, AVI or lossless-compressed codec); #325 render over existing file | Larger files than After Effects + Media Encoder | 20–40 h | G6 · [file-format-parity.md](file-format-parity.md) |
|
||||
| 23 | **Essential Graphics interchange** | Templates are `.ectemplate`, not `.mogrt`; #338 and #611 closed | Premiere editors can't use our templates | 15–30 h | — · [file-format-parity.md](file-format-parity.md) |
|
||||
@@ -313,8 +313,8 @@ Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the vie
|
||||
|
||||
- Localisation of the interface ([localization-parity.md](localization-parity.md)): since 6–12
|
||||
October the menus are translated into Japanese, Simplified and Traditional Chinese, Ukrainian,
|
||||
Spanish, Brazilian Portuguese, Russian, French, German and Czech, and the panel catalog covers
|
||||
Japanese, both Chinese, Ukrainian, Brazilian Portuguese and Czech. Next: effect and parameter
|
||||
Spanish, Brazilian Portuguese, Russian, French, German, Czech and Georgian, and the panel catalog covers
|
||||
Japanese, both Chinese, Ukrainian, Brazilian Portuguese, Czech and Georgian. Next: effect and parameter
|
||||
names, the remaining panel strings (Spanish, Russian, French, German), Korean and Italian, and
|
||||
native-speaker review of all of them.
|
||||
- Accessibility (keyboard navigation, screen readers, contrast), user documentation and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (catalogs recounted at `upstream/main` 470ea7a3: menus in 10 languages, panels in 6, Hangul probe (#599)) · **Target:** Adobe After Effects 2026
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-23 · **Change:** minor (Georgian: menus, panels and effect names; missing strings show in English; `missing_translations_report` lists them) · **Target:** Adobe After Effects 2026
|
||||
|
||||
How much of EffectCraft's interface is translated, language by language, compared with After
|
||||
Effects. [gaps.md](gaps.md) gap 16 and workstream G9 hold the work;
|
||||
@@ -17,16 +17,17 @@ interface; Arabic and Hebrew text *in layers* is supported through its Middle Ea
|
||||
## How EffectCraft translates
|
||||
|
||||
- **Menus:** `crates/ui-egui/src/i18n.rs`, one catalog per language keyed by
|
||||
`(command, English label)`. A test (`every_translation_key_exists_in_the_actual_menu_tree`)
|
||||
fails if any fixed menu entry is untranslated or a row is stale, so every catalog covers all
|
||||
fixed entries (624 catalog rows each at 470ea7a3, counted). The generated Effect menu is not part of that fixed tree: its entries
|
||||
`(command, English label)` (624 catalog rows each at 470ea7a3, counted). A test
|
||||
(`menu_catalogs_have_no_stale_or_duplicate_rows`) fails on a row whose entry left the menu tree
|
||||
or on a repeated row. The generated Effect menu is not part of that fixed tree: its entries
|
||||
and category submenus take their labels from the effect catalog below.
|
||||
- **Panels, dialogs, buttons, tooltips:** `crates/ui-egui/src/i18n/ui.rs`, keyed by the English
|
||||
source string, with English fallback. Catalogs exist for Japanese (410 rows), Simplified and
|
||||
Traditional Chinese (506 each), Ukrainian (505), Brazilian Portuguese (400) and Czech (501);
|
||||
counted at 470ea7a3.
|
||||
counted at 470ea7a3. Georgian has every row the Chinese catalogs have at this revision (695, as
|
||||
both Chinese catalogs now have), plus 191 for panels converted to the catalog.
|
||||
- **Effect names:** `crates/ui-egui/src/i18n/effects.rs`, keyed by **effect id**, in Simplified
|
||||
Chinese (306 rows, one per effect in the registry, plus its 21 categories). The registry's
|
||||
Chinese and Georgian (307 rows, one per effect in the registry, plus its 21 categories). The registry's
|
||||
English name stays the effect's identity: `effect.list`, `effect.apply {"effect": …}`, scripts
|
||||
and expressions match on it, and the property tree stores it as the effect group's name. Only
|
||||
the drawing — the Effect menu, Effect Controls, the Timeline and Effects & Presets — goes
|
||||
@@ -38,6 +39,12 @@ interface; Arabic and Hebrew text *in layers* is supported through its Middle Ea
|
||||
slot is for). Each language decides for itself which names stay English — Adobe's Japanese
|
||||
interface leaves the `CC`, `VR` and `OCIO` sets in English while its Chinese one translates
|
||||
them — so a row repeating the English name is a choice, not a missing row.
|
||||
- **Missing strings:** a string with no row in the interface language shows in English, and no
|
||||
test, build or release fails on a missing translation. The `missing_translations_report` test
|
||||
(`crates/ui-egui/src/i18n.rs`) prints, per language, the fixed menu entries, panel strings, effect
|
||||
names and effect categories that have no row. It always passes; `cargo xtask ci` runs it with
|
||||
`--nocapture`, so the list is in the gate log. Locally:
|
||||
`cargo test -p effectcraft-ui-egui --lib missing_translations_report -- --nocapture`.
|
||||
- **Not translated anywhere yet:** effect *parameter* names (≈ 1,650, `crates/effects`),
|
||||
engine-generated text (property names, errors, undo labels), the Home screen's unwrapped
|
||||
literals (#574), Learn tutorials and documentation.
|
||||
@@ -76,13 +83,14 @@ the true percentages are slightly lower.
|
||||
| Korean | `ko` | 0 | — | Hangul fallback probe added (#599, closed); not checked on macOS here | no | none | 9–13 h + review |
|
||||
| Vietnamese | `vi` | 0 | — | Latin with stacked diacritics; untested | no | none (After Effects: none) | 8–12 h + review |
|
||||
|
||||
Other languages EffectCraft ships (3):
|
||||
Other languages EffectCraft ships (4):
|
||||
|
||||
| Language | Code | UI strings translated | Status | To `full` |
|
||||
|---|---|---|---|---|
|
||||
| Traditional Chinese | `zh-hant` | 1,130 (≈ 29%): menus 624 rows, panels 506 | partial | 6–9 h + review |
|
||||
| Ukrainian | `uk` | 1,129 (≈ 29%): menus 624 rows, panels 505 | partial | 6–9 h + review |
|
||||
| Czech | `cs` | 1,125 (≈ 29%): menus 624 rows, panels 501 | Existing catalog entries covered; unwrapped UI, effects and engine text still in English; AI-assisted, not yet native-speaker proofread | Remaining strings + independent native review |
|
||||
| Georgian | `ka` | 1,514 (≈ 39%): menus 628 rows, panels 886 (the Chinese catalogs' 695 plus 191 Georgian-only rows: form dialogs, context menus, column headers), effect names 307 + 21 categories; native-speaker reviewed | partial | 5–8 h |
|
||||
|
||||
Russian (`ru`) has a menu catalog (624 rows, ≈ 16%); After Effects also ships Italian, which EffectCraft does not (8–12 h).
|
||||
|
||||
@@ -97,13 +105,14 @@ needs ≈ 2,900 more strings (≈ 5–9 h per language at the pace of #398 and #
|
||||
Effects' 10 non-English languages: **≈ 60–110 h**; the four extra languages of the standard list
|
||||
(Hindi, Arabic, Indonesian, Vietnamese) add ≈ 50–80 h, most of it right-to-left layout and
|
||||
complex shaping in the interface. Every language needs native-speaker review, which only a human
|
||||
can give; none has had it.
|
||||
can give; only Georgian, contributed by a native speaker (#1843), has had it.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | Recounted: Russian, French, German and Czech menus (#873, #987), Brazilian Portuguese and Czech panels (#830, #987), Hangul probe (#599); overall ≈ 12% to ≈ 18%; Korean and Italian still absent |
|
||||
| 2026-10-14 | minor | Georgian (`ka`): menus 628, panels 886 (191 Georgian-only, in `GEORGIAN_PANELS`), effect names and categories in `effects.rs` |
|
||||
| 2026-10-10 | major | First localization parity document: catalog rows counted for all six languages, the twelve-language table, After Effects' 11 shipped languages from its bundle |
|
||||
| 2026-10-11 | minor | Ukrainian panel catalog brought level with the Chinese ones (495 of 496 rows, counted at this commit); its share rises to ≈ 29%. The other languages' panel counts are the 10 October measurement |
|
||||
| 2026-10-13 | minor | Effect names and the generated Effect menu leave English: `i18n/effects.rs` adds 306 rows keyed by effect id (Simplified Chinese, from After Effects 2026's interface and Adobe's Chinese documentation for the `CC` series) and the 21 category names. Parameter names are still English; no other language has rows |
|
||||
|
||||
+3
-2
@@ -30,7 +30,7 @@ Commands (CLI, MCP, control channel):
|
||||
|
||||
### Settings that change behaviour
|
||||
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk` / `es` / `pt-br`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`, a Spanish one (`es`, `es-ES`, `es_MX.UTF-8`) selects `es`, a Portuguese one (`pt`, `pt-BR`, `pt_PT.UTF-8`) selects `pt-br`; the browser build stays in English with it. Choose **Español** or **Português (Brasil)** for Spanish or Brazilian Portuguese menus (panels stay in English for now); choose **Українська** for Ukrainian menus and cataloged panel, dialog, button and tooltip text; Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`, with rows for Japanese, Simplified and Traditional Chinese and Ukrainian), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the tests in that module fail on an unused row and on a hard-coded string in a converted panel.
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk` / `es` / `pt-br` / `ka`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`, a Spanish one (`es`, `es-ES`, `es_MX.UTF-8`) selects `es`, a Portuguese one (`pt`, `pt-BR`, `pt_PT.UTF-8`) selects `pt-br`, a Georgian one (`ka`, `ka-GE`, `ka_GE.UTF-8`) selects `ka`; the browser build follows `?lang=` in the address, then the browser's languages (`navigator.languages`). Choose **Español** or **Português (Brasil)** for Spanish or Brazilian Portuguese menus (panels stay in English for now); choose **Українська** for Ukrainian menus and cataloged panel, dialog, button and tooltip text; Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`, with rows for Japanese, Simplified and Traditional Chinese and Ukrainian), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the `missing_translations_report` test lists what each language lacks, and the tests in `ui.rs` fail on an unused row.
|
||||
- `general.undoLevels`: Levels of Undo
|
||||
- `general.pathPointSize`: Path Point and Handle Size
|
||||
- `general.recentItems`: Recent Projects Shown
|
||||
@@ -91,7 +91,8 @@ Commands (CLI, MCP, control channel):
|
||||
one and in the comps that nest it (through the precomp layers' timing)
|
||||
- `general.expressionPickWhipCompact`: off, the pick whip writes match names
|
||||
(`thisComp.layer("A")("transform")("opacity")`)
|
||||
- `general.createSplitLayersAbove`: where Edit ▸ Split Layer puts the new layer
|
||||
- `general.createSplitLayersAbove`: where Edit ▸ Split Layer puts the new layer (on by default,
|
||||
as in After Effects)
|
||||
- `startup.showHomeOnOpenProject`: the Home screen comes up after File ▸ Open / Open Recent
|
||||
- `composition.motionPath`, `composition.motionPathSeconds`, `composition.motionPathKeyframes`:
|
||||
how much of a position motion path the viewer draws (all, none, N seconds or N keyframes
|
||||
|
||||
@@ -688,8 +688,10 @@ Fixes from measured reports:
|
||||
step (undo and redo keep it) and keeps the comp's cached frames; projects saved before open
|
||||
with Auto. View ▸ Resolution and the panel's popup run `view.res.full|half|third|quarter|auto`
|
||||
and `view.res.custom {factor}`, which now work headless too; `comp.info` reports `resolution`
|
||||
and `resolutionFactor` (#417). Not yet: the magnification is not saved per comp, and the
|
||||
Composition Settings dialog has no Resolution popup.
|
||||
and `resolutionFactor` (#417). Composition Settings ▸ Basic has the same Resolution menu, with
|
||||
the frame size and memory per 8bpc frame at that resolution beside it; OK sets it as the popup
|
||||
does, outside the undo step of the other settings (#2167). Not yet: the magnification is not
|
||||
saved per comp.
|
||||
|
||||
### Update: plug-ins and extensions as in After Effects; ease presets become a ScriptUI panel
|
||||
|
||||
@@ -1068,7 +1070,7 @@ resolution, adjustment) and composited at 8 and 32 bpc:
|
||||
Bucket's flood-filled region, Eyedropper Fill's sample); segments and convex polygons are
|
||||
binned into 16 × 16 tiles and rasterised on the GPU with the CPU's coverage functions, glows
|
||||
and compositing included. The CPU effects were refactored to draw from the same plans with
|
||||
bit-identical output (`crates/effects/tests/gen_golden.rs`: 288 frame hashes pinned before
|
||||
bit-identical output (`crates/effects/tests/it/gen_golden.rs`: 288 frame hashes pinned before
|
||||
the refactor). Exception: CC Threads decides band membership and fibre shade per pixel, and at
|
||||
round parameter values (the defaults) pixel centres sit exactly on band edges, where f32 and
|
||||
the CPU's f64 can fall on either side: up to 5 % of pixels allowed (measured ≤ 3.6 % at half
|
||||
@@ -1082,7 +1084,7 @@ resolution, adjustment) and composited at 8 and 32 bpc:
|
||||
7 more effects run on the GPU (280 in all, with M13.28's 37), and five CPU fallbacks are gone. Each case is
|
||||
checked against the CPU oracle (≤ 1/255 at 8 bpc, ≤ 1e-3 at 32 bpc) directly on a buffer (full
|
||||
and half resolution) and composited at 8 and 32 bpc; the CPU refactors behind the shared plans
|
||||
are pinned bit for bit by golden hashes (`crates/effects/tests/particle_golden.rs`,
|
||||
are pinned bit for bit by golden hashes (`crates/effects/tests/it/particle_golden.rs`,
|
||||
`timewarp_golden.rs`, `sim_golden.rs`).
|
||||
|
||||
- **Particle render passes** (`gpu::fx_particles`): CC Particle World and CC Particle Systems II
|
||||
|
||||
+10
-1
@@ -5,6 +5,15 @@ cargo test --workspace # everything
|
||||
cargo xtask ci # fmt, clippy -D warnings, tests, layers, assets, wasm
|
||||
```
|
||||
|
||||
`cargo xtask ci` builds in the dev profile and runs the tests with `cargo nextest run` (plus
|
||||
`cargo test --doc`) when `cargo-nextest` is installed, and with `cargo test` otherwise. It then
|
||||
runs `missing_translations_report` with `--nocapture`, which prints the interface strings each
|
||||
language has no translation for. Those strings show in English; the report always passes.
|
||||
|
||||
Each crate with several integration-test files has one test binary, `tests/it/main.rs`, whose
|
||||
modules are the files in `tests/it/`. Run one file with `cargo test -p <crate> --test it <module>::`
|
||||
or `cargo nextest run -p <crate> -E 'binary(it) and test(/^<module>::/)'`.
|
||||
|
||||
## What the tests cover
|
||||
|
||||
- **Engine commands** (`crates/engine/src/tests*.rs`): every command family, including undo and
|
||||
@@ -16,7 +25,7 @@ cargo xtask ci # fmt, clippy -D warnings, tests, layers, assets, was
|
||||
- **Effects** (`crates/effects`): each effect has a determinism test and at least one behaviour
|
||||
test (identity at neutral settings, known pixel results, or simulations giving the same frame
|
||||
whether you seek straight to it or play up to it). A registry-wide test checks that effects
|
||||
which read the clock are declared time-dependent. `crates/effects/tests/sim_golden.rs` pins
|
||||
which read the clock are declared time-dependent. `crates/effects/tests/it/sim_golden.rs` pins
|
||||
exact pixel hashes of the simulation effects the GPU shares plans with (CC Rainfall … Card
|
||||
Wipe) at several settings, times, bit depths and resolutions, so any change to their CPU output
|
||||
fails (re-pin with `SIM_GOLDEN_PRINT=1` after an intended change; pinned for aarch64 macOS,
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (path-editing layer bounds, #1220; closed issue references re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-18 · **Change:** minor (shape group Contents row elided; path-editing layer bounds #1220; closed issue references re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026
|
||||
|
||||
How closely EffectCraft's interaction matches After Effects: tools, handles, snapping, nudging,
|
||||
modifier keys, shortcuts, drag behaviour, numeric entry, panels, context menus and feel. Evidence
|
||||
@@ -16,8 +16,8 @@ feel and platform differences, and the 26.x interface changes are not in yet.
|
||||
| Area | Status | % | Evidence and gaps |
|
||||
|---|---|---:|---|
|
||||
| Menu bar | After Effects' menus, order, submenus and labels; native on macOS; keyboard-operable in-window bar | 95% | 587 entries in `crates/engine/src/menus.rs`; Adobe-service entries omitted by design; `ui_menus` tests (#279) |
|
||||
| Panels and workspaces | Every After Effects panel; all standard workspaces; drag-to-dock, tabs, floating panels, maximise | 75% | `ui_tabs`, `ui_panels`; panels move into their own native windows on any screen (#486 open: no dragging tabs between those windows); #302, #301 and #320 (docking, stacked-panel headers, workspace persistence) are closed |
|
||||
| Timeline | Switches, modes, parent & link, twirl-downs, reveal shortcuts (U, UU, P, S, R, T, A, E, M, F, Alt+Shift…), pick whips, graph editor, work area, markers | 75% | `ui_keyframes`, `ui_timeline_*`; expand precomps in place (#601); #456, #457, #569 and #355 (Graph Editor jitter and buttons, negative rotation, bulk keyframing) are closed; Proportional Scrubbing (26.2: Ctrl+Alt / Cmd+Opt scrub of hot text spreads the change over the selected layers by selection order, #770) matched for layers selected as a whole, not yet for individually selected properties; every layer has the Video switch, nulls, cameras and lights included (a null with its eye off has no box or handles in the viewer and isn't picked there) |
|
||||
| Panels and workspaces | Every After Effects panel; all standard workspaces; drag-to-dock, tabs, floating panels, maximise | 75% | `ui_tabs`, `ui_panels`; on Windows, macOS and X11 floating panels are OS windows of their own on any screen (on Wayland, the web and in embedded viewports they stay in the app window), with tabs and groups dragging between windows and shortcuts working in them (#486); #302, #301 and #320 (docking, stacked-panel headers, workspace persistence) are closed |
|
||||
| Timeline | Switches, modes, parent & link, twirl-downs, reveal shortcuts (U, UU, P, S, R, T, A, E, M, F, Alt+Shift…), pick whips, graph editor, work area, markers | 75% | `ui_keyframes`, `ui_timeline_*`; expand precomps in place (#601); #456, #457, #569 and #355 (Graph Editor jitter and buttons, negative rotation, bulk keyframing) are closed; Proportional Scrubbing (26.2: Ctrl+Alt / Cmd+Opt scrub of hot text spreads the change over the selected layers by selection order, #770) matched for layers selected as a whole, not yet for individually selected properties; every layer has the Video switch, nulls, cameras and lights included (a hidden null, camera or light has no box or handles in the viewer and isn't picked there; other selected hidden layers keep their layer controls and paths, #1769); a row control (a switch, the label swatch, a Modes cell) clicked on one of several selected layers acts on the whole selection, with the layers the switch doesn't apply to left out (#1849); a shape group's inner Contents (After Effects' elided Vectors group) draws no row — the group's path, fill/stroke and Transform list directly under it |
|
||||
| Composition viewer | Selection, handles with direction-aware cursors, rulers, guides, grids, snapping, channels, exposure, region of interest, snapshots, 3D views, Spacebar hand | 70% | `ui_viewer`, `ui_viewers`; Cmd/Ctrl +/− doesn't zoom (#570); anchor point grab on Linux (#492); #352 (snapping) is closed; no 3D transform gizmos (#392, #347); percentage guides (26.5) exist as View ▸ Add Percentage Guide and Convert Guides to Percentage / Pixels (a guide stores a percent of the comp, so it follows comp-size changes; behaviour inferred from the release note, not compared with After Effects); Copy Frame to Clipboard (26.3) is in the Composition menu and the viewer's context menu |
|
||||
| Tools panel | All After Effects tools; long-press flyouts; Tool Creates Shape / Mask; Fill / Stroke options | 80% | #213, #227 fixed; #553 (pen and vertex tools on motion paths) is closed |
|
||||
| Nudging and modifiers | Arrow nudge (1 px, Shift 10 px), Alt/Option duplicates and slips, Shift constrains | 75% | #290 nudging fixed; #491 (Linux multi-layer edits) is closed |
|
||||
@@ -41,15 +41,15 @@ cover shape paths and masks, corner-vertex dragging and undo, and layers without
|
||||
|
||||
## Estimate
|
||||
|
||||
**≈ 60–120 h**: the open interaction issues above (25–45 h, about 0.2–0.5 h each with a scripted
|
||||
test), 3D gizmos 8–15 h, multi-monitor panels 10–20 h, the 26.x interface changes 10–20 h, an
|
||||
accessibility pass 15–30 h. Checking feel against After Effects needs the owner's Mac with After
|
||||
**≈ 60–110 h**: the open interaction issues above (25–45 h, about 0.2–0.5 h each with a scripted
|
||||
test), 3D gizmos 8–15 h, the 26.x interface changes 10–20 h, an accessibility pass 15–30 h. Checking feel against After Effects needs the owner's Mac with After
|
||||
Effects running; most of the rest parallelises.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-18 | minor | A shape group's inner Contents (After Effects' elided Vectors group) draws no row: the group's path, fill/stroke and Transform list directly under the group |
|
||||
| 2026-10-13 | patch | Layer bounds are suppressed during path-point editing (#1220); shape/mask interaction regressions |
|
||||
| 2026-10-12 | minor | Closed issues (#302, #320, #491, #511, #553 and others) marked as closed instead of open; panels in their own windows (#486) |
|
||||
| 2026-10-12 | minor | Percentage guides and effect colour labels (26.5) added; rows updated |
|
||||
|
||||
+3
-3
@@ -462,19 +462,19 @@ every request and reply, a render through an in-process offload; `tests_roto.rs`
|
||||
propagation through an offload streaming segmentations; `tests_mask_warp.rs`: Warp Stabilizer
|
||||
plans solved in the job, never by `warp.status` on the page), the frame-worker protocol
|
||||
(`crates/engine/src/remote.rs`: diff / patch, a replica following edits renders the same pixels;
|
||||
`crates/ui-egui/tests/ui_web_offload.rs`: the frame cache through a remote renderer, `wait: true`
|
||||
`crates/ui-egui/tests/it/ui_web_offload.rs`: the frame cache through a remote renderer, `wait: true`
|
||||
deferred until the offloaded job ends) and the Media Browser's virtual tree (`media_browser.rs`,
|
||||
`tests_panels.rs`). M13.24: deferred readbacks on a real device (`crates/gpu/src/tests_deferred.rs`:
|
||||
whole-frame GPU and CPU-composited frames in passes match the CPU, placeholders never reach the
|
||||
layer cache; `deferred.rs`: keys, late readbacks of an older frame dropped), the frame server's
|
||||
passes and Auto timing (`remote.rs`, a deferred stand-in accelerator), the browser's routing and
|
||||
disk cache (`crates/ui-egui/tests/ui_web_gpu_storage.rs`: a GPU frame worker renders effect frames,
|
||||
disk cache (`crates/ui-egui/tests/it/ui_web_gpu_storage.rs`: a GPU frame worker renders effect frames,
|
||||
the page's GPU keeps frames without effects, frames stored under their disk keys come back after a
|
||||
"reload" without rendering, the blue cache bar sees them; the storage manager's widgets and
|
||||
commands), the disk index (`disk_cache.rs`), the store's usage by kind and clearing (`store.rs`),
|
||||
`storage.*` (`storage.rs`), and every WGSL float literal fitting f32 (browsers reject the whole
|
||||
module otherwise; `tests.rs`). M13.30: job workers on a deferred device
|
||||
(`crates/ui-egui/tests/ui_web_job_gpu.rs`: a Render Queue job and a Warp Stabilizer analysis
|
||||
(`crates/ui-egui/tests/it/ui_web_job_gpu.rs`: a Render Queue job and a Warp Stabilizer analysis
|
||||
render their frames in passes on the device, with the CPU worker's pixels / result),
|
||||
particles, Advanced 3D and the job pass driver in passes (`crates/gpu/src/tests_deferred_jobs.rs`),
|
||||
layer buffers through the disk store and the prefetch plan (`remote.rs`
|
||||
|
||||
Reference in New Issue
Block a user