`path.convertToBezier` only cleared `roto_bezier` on mask groups, so on a shape
layer it did nothing at all: a Rectangle, Ellipse or Polystar path stayed
parametric and its vertices could not be edited or animated. In After Effects
the command replaces such a path with an editable Path 1 of the same geometry.
The geometry is not re-derived. `parametric_path` calls the same
`effectcraft_path::{rect, ellipse, polystar}` functions `render::shapes::collect`
calls, with the same defaults for a property a group doesn't carry, so the path
that comes out is the shape that was on screen; `from_kurbo` and
`build::shape_path` then make an ordinary Path 1.
Masks are unchanged: shape items take precedence when selected, and with none
selected the command falls through to the old mask behaviour. Path Direction is
carried over rather than reset, since reversing a path is a separate command,
and the new path is left selected so its vertices can be edited straight away.
The parametric properties are read at the current time and the result is a
static path: a Size or Roundness animation cannot survive becoming a vertex
list, in After Effects either. Undo restores the parametric shape.
Problem: effect colour labels (26.5) were drawn only on the Effect Controls header; the Timeline's effect rows looked unlabelled.
Fix: an effect group row in the Timeline paints the label colour as a 4 px bar on its left edge (same look as the Effect Controls header) and registers `timeline.group.<uid>.label` with the label's name for agents. Docs: gaps.md row 21 and ui-parity.md updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In After Effects solo is not time-dependent: while any AV layer is soloed,
non-soloed layers are excluded at every time. The renderer, nested/collapsed
3D paths, engine viewer helpers and viewer hit tests only counted solo layers
active at the current time, so outside the soloed layer's in-out range the
whole comp reappeared. Drop the time condition (audio already ignored it).
Update the #66 viewer regression test to the AE behaviour and add a render
test for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The interface below the menu bar is localized (618 fixed menu entries, ~490 panel rows) but an
effect's name still rendered in English everywhere it appeared: the generated Effect menu, Effect
Controls, the Timeline and Effects & Presets.
The registry's English name is the effect's identity: `effect.list`, `effect.apply {"effect": …}`,
scripts, expressions and the property tree's group name all match on it. Only the drawing changes.
`crates/ui-egui/src/i18n/effects.rs` keys the names by effect **id** rather than by the English
string — a repeat is stored as `Gaussian Blur 2` (an English key would miss it) and upstream
renames a display name now and then, while the id is stable. It carries 306 rows for Simplified
Chinese, one per effect in the registry, taken from the installed After Effects 2026 (26.5.0.89)
Simplified Chinese interface, with the `CC` series following Adobe's Chinese documentation (which
names 68 of the 69 that its interface leaves in English). `Cryptomatte`, `EXtractoR`, `IDentifier`,
`Key Light` and `Unmult` stay in English as brand names; `Face Measurements` and `Face Track
Points` are EffectCraft's own effects. The 21 category folder names are in the same file, worded
after the effect names (channel → 通道, noise → 杂色, matte → 遮罩 …).
What draws the catalog now:
- **Effect menu:** the entries and the category submenus. The command and its `{"effect": <id>}`
params are untouched, so the menu still applies by id.
- **Effect Controls:** the header, its Rename field, the accessible name and the About popup.
- **Timeline:** effect group rows, and the search (a Chinese interface is searchable in Chinese).
- **Effects & Presets:** the rows, the drag ghost, the folder rows and the search.
Kept as they are: the effect id, the property tree's `PropGroup.name`, every automation id and
label, `effect.list`'s `name`/`category`, and the undo label (`Apply Gaussian Blur`). A name the
user typed in Effect Controls stays verbatim, and a repeated instance keeps the index the engine
appends (`Gaussian Blur 2` → `高斯模糊 2`). No other language has rows yet, Traditional Chinese
included, so those draw the registry's English name.
Tests: nine unit tests under `cargo test -p effectcraft-ui-egui --lib i18n` cover the data (every
effect has a row, the row still carries the registry's English name, ids are unique, only the five
brand names repeat their English name, every category has a row, a row names an effect the registry
knows) and the behaviour (the instance index, user renames, English and untranslated languages,
search) plus a scan pinning the drawing sites. `crates/ui-egui/tests/ui_effect_names.rs` checks the
Effect Controls header, the generated Effect menu and that the ids, automation labels and
`effect.list` stay English.
`docs/localization-parity.md` and `docs/gaps.md` no longer record effect names as deliberately
English; effect *parameter* names are still untranslated and stay the next piece.
Refs #821
Problem: the built-in animation preset library had no Backgrounds category
(gap 7: 31 presets against After Effects' 621).
Fix: 23 original Backgrounds presets in crates/engine/presets/backgrounds.json,
built from our own generator, noise, colour and stylize effects: Soft Gradient
Drift, Aurora Veil, Slow Fog, Cloud Bank, Dark Smoke, Ember Haze, Magma Flow,
Ice Flow, Pastel Plasma, Cell Drift, Crystal Facets, Hex Mosaic, Mirrored Silk,
Water Caustics, Blueprint Grid, Checker Scroll, Scrolling Stripes, Pulse Rings,
Bokeh Drift, Spotlight Floor, Dawn Sky, Sweeping Light, Grainy Wash.
The preset format gains `expr` (an expression per property, so backgrounds keep
moving for the whole layer) and point parameters written as fractions of the
layer size. Effects & Presets lists them under * Animation Presets > Backgrounds.
Tests: catalogue uniqueness and categories, apply/undo/redo of every preset
(engine), render of every preset with the expression engine: finite,
non-flat, deterministic, moving (host), panel listing and apply (ui-egui).
Sidecar, ATTRIBUTION.md, gaps.md row 7 and effects-parity.md updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Guides can store a percent of the comp so they follow comp-size changes
(View > Add Percentage Guide, Convert Guides to Percentage / Pixels); an
effect instance has an optional colour label (effect.setLabel, Effect Controls
header bar and Label menu). Both are serde-defaulted, so old projects load.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 40b232d02069925e9c72677f52ee6f356495a736)
(cherry picked from commit a2d89bda11b7217c83c98ab681b8f9b4a334786d)
(cherry picked from commit 63951595222207b6bfd0ecde72f991628c6f12d9)
(cherry picked from commit 20c2bb0dcb)
Problem: Object Matte had no After Effects comparison, and none can be made
on this Linux machine (no After Effects). The gap notes still listed the
viewer tool, background job and disk cache as missing.
Fix: docs/fidelity/object-matte.md states plainly that nothing is measured
yet and pins down the comparison for when an After Effects 26.2+ reference
is available: four original or openly licensed clips (the synthetic moving
disk from the engine tests, the disk with a jump, two objects with a box
prompt, one CC0/CC BY real clip), the prompt and settings, where reference
mattes stay (gitignored), and the metrics: per-frame IoU against After
Effects (headline), IoU against ground truth, boundary distance, soft-edge
error, temporal stability, drift and time per frame, plus a proposed pass
bar left to the maintainers. Linked from docs/fidelity/README.md.
docs/gaps.md (#14) and docs/target-app-parity.md now list what landed and
leave the comparison as the one remaining item.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: After Effects 26.2's Object Matte (pick an object with a click,
optionally a box, get a matte that follows it) had no counterpart; only
stroke-driven Roto Brush existed.
Fix: an Object Matte effect (Effect > Matte, ec.matte.objectmatte) turns
the Object Point (and optional box) on a Reference Frame into Roto Brush
strokes and segments them with Roto Brush's engine, content-keyed cache
and the model chosen in Settings > Roto Brush (MobileSAM, classical graph
cut without it). Track Object carries the matte frame by frame up to 600
frames each way; otherwise every frame is segmented on its own. Feather,
Contrast, Shift Edge, Invert and a Matte view. The engine keeps its Input
Key current so upstream edits recompute it. objectMatte.apply creates one
from agents / the CLI. Unusable prompts pass the layer through.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: floating panels lived inside the main window only, so on a
two-screen setup no panel could go onto the second monitor (#486).
Fix: panel menu > Move Panel to New Window (and the agent/control command
`window.panelWindow {panel, window?}`) undocks the panel if needed and shows
its floating group in a native egui viewport the user can drag to any screen;
Move Panel to Main Window or closing the window brings it back. The window's
place and size are kept in the workspace (sanitised against hostile values)
and restored on the next start. Browser and test builds, which embed
viewports, show it as an in-app window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: nothing fuzzed project files. The hand-written .ecprojx XML reader
recursed once per nested <arr>/<obj> with no limit, so a deeply nested file
overflowed the stack and aborted the app instead of failing to open.
Fix: a dependency-free, deterministic, seeded mutation fuzzer for File > Open
(JSON and XML seeds from the demo, byte and JSON-structure mutations, the real
file.open path, first queries and a thumbnail render; each input under
catch_unwind with a deadline; bounded for cargo test, longer runs via env
vars). The XML reader now stops at the JSON reader's 128-level limit, with a
regression test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: with the Project panel focused the arrow keys did nothing (After
Effects selects the previous / next item, extends with Shift and opens /
closes folders with Right / Left). The custom-drawn rows, sort headers,
twirls, label swatches, proxy switches and footer buttons reached
AccessKit with no name or role, the empty list area and each row's name
cell were unnamed extra Tab stops, a Tab-focused row showed no focus
ring, and the arrows then moved egui's spatial focus instead.
Fix: Up / Down select the previous / next row (Shift extends from the
anchor), Right opens and Left closes the selected folder or selects the
parent; the moved-to row scrolls into view. A row with keyboard focus
keeps the arrows, focus follows the selection and the panel takes focus.
Rows are AccessKit tree items named "<name>, <type>" with selected,
level and expanded state; headers and row / footer buttons are named
buttons; one Tab stop per row and a focus ring.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the Render Queue had no single-file uncompressed output, so
handing a render to an external encoder meant a lossy codec, ProRes,
or an image sequence plus a separate WAV.
Fix: add the "QuickTime (Uncompressed)" Output Module format. Frames
are stored as QuickTime 'raw ' samples: 8-bit RGB (depth 24, rows
padded to even bytes) or ARGB with RGB + Alpha (depth 32), written
through FilmCraft's existing QuickTime muxer with PCM audio
(16/24-bit or float) like ProRes. Format name `uncompressed` on the
control channel; disk-space estimate and Sample Format menu included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the Media Browser lists .mxf files, but File > Import's dialog
filtered them out and media::is_importable refused them, although the
pinned FilmCraft codecs behind crates/media already demux MXF (AVC,
MPEG-2 / XDCAM / D-10, VC-3 / DNxHD, ProRes, PCM audio) by content.
Fix: add "mxf" to VIDEO_EXTENSIONS and to the File > Import and Set
Proxy dialog filters. Tests check MPEG-2 4:2:2 long-GOP + 24-bit PCM and
DNxHD 36 MXF against ffmpeg frames (>35 dB, measured 64-67 dB), audio
level, and that the Import dialog offers MXF. AVI stays open: FilmCraft
has no AVI demuxer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the Media Browser listed .dpx files and After Effects imports
DPX and Cineon scans (stills and numbered sequences), but import
refused them: nothing could decode either format, and .cin was not even
an image to the browser, so its runs never grouped into sequences.
Fix: a small pure-Rust reader written from the public SMPTE 268M and
Kodak Cineon specifications (crates/media/src/dpx.rs): luma, RGB, RGBA
and ABGR at 8/10/12/16 bits, 10-bit filled method A/B, 12-bit in 16-bit
words, both byte orders, lines padded to 32-bit words, unrotated
orientations and the pixel aspect ratio; the codec names the transfer
(log, linear, ...) and code values are shown as stored. RLE, signed,
float, Y'CbCr, packed 10/12-bit and transposed files are refused with a
clear error; every header field is bounds-checked and the data length
verified before allocating. Probe and frame decode use it first;
is_importable, the Media Browser kinds and the import dialog list
.dpx/.cin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: EffectCraft shipped only 8 original text-animator presets against
After Effects' 621; there was no Transitions category (ranked gap #7).
Fix: an original preset library, crates/engine/presets/transitions.json
(23 presets on our own Transition effects and layer opacity: fades,
directional and diagonal wipes, clock and fan reveals, blinds and slats,
pixel/mosaic dissolves, card flips, CC grid, radial scale, line sweep and
jaws wipes; In and Out). New commands anim.applyBuiltinPreset (adds the
effects with values and keyframes from the current time, one undo step,
reports any path that doesn't fit) and anim.builtinPresets. Effects &
Presets lists them under * Animation Presets > Transitions; double-click
applies. Sidecar and ATTRIBUTION.md entry; gaps/effects-parity updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: In Advanced 3D, an extruded shape or text layer was one mesh in
its paint colour. After Effects colours the front, bevel, side and back
of an extrusion separately (shape Contents ▸ Add ▸ Front/Bevel/Side/Back,
text Animate ▸ Front/Bevel/Side/Back), and EffectCraft had none of it.
Fix: The extruder can split its mesh by surface (front cap, bevel rings,
walls, back cap). Shape layers get Front / Bevel / Side / Back contents
items (layer.addShapeItem kinds frontColor…backColor, one item per
group, colouring every paint of the group and its nested groups; the
innermost wins). Text animators get Front / Bevel / Side / Back Color,
blended over the character's fill or stroke by the selection. Paints
with a surface colour render as one mesh per surface. Menus, the
Contents Add: pop-up and the menu translations list them. The other
surface material properties (Ambient, Diffuse, Specular, Metal,
Reflection, ...) remain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: with no working space and Working Gamma 2.4, a linear EXR still
converted with the sRGB curve (0.18 -> 0.4627, After Effects 0.4902):
float footage was sRGB-encoded at decode, before the render knew the
project's curve. Interpret As Linear Light on float footage then encoded
a second time (0.18 -> 0.7255).
Fix: FootageSource::frame_with asks for float footage with a FloatCurve
(sRGB, a pure 2.4 power curve, or as stored). The renderer asks for the
Working Gamma's curve when unmanaged, sRGB when managed, and the stored
values when Interpret As Linear Light encodes them afterwards.
MediaPool decodes float values that way (Preserve RGB still reads them
as stored) and caches each curve apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>