draw_bounds padded every stroke by width × miter limit / 2 on each side, joins or
not. A straight 1080 px wide wipe used as a track matte became a 4326×6366 layer
buffer (~440 MB as RGBA f32), made anew every frame while its trim animates, then
cached on the CPU and uploaded to the GPU. With 12 frames exported in parallel a
16 GB Mac ran out of memory and froze.
Now the miter limit applies only to miter-joined paths with a join (two or more
segments, or closed); square caps reach √2 half widths, other strokes one; Taper
and Wave keep the widest padding. Rendered pixels are unchanged.
A full-res frame of the affected ad: 1.7 GB → 0.7 GB peak.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A layer selected by another layer's effect (Displacement Map, Blend...) had
its structure, values, matrix and time hashed into the owner's key, but not
the comp camera/lights its own scene-reading effects (Caustics, CC Particle
World...) see. The owner's key included the scene only when the owner itself
ran such an effect, and a cached owner never runs the selected layer's
effects, so moving the camera or a light reused stale owner pixels.
Dependencies::layer now hashes comp_scene for every selected (or nested)
layer that reads it, with the same policy as owner keys.
Fixes#1252
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kwMcw2pYQ5fSjKa3ZPnh
Guide layers render only in their own comp's viewer: a nested renderer (precomp, collapsed
precomp in 2D, Classic 3D and Advanced 3D, comps read by effects, aux passes, the GPU walk's
resident precomps) now runs with guides off (Renderer::nested_opts), so a precomp's guide
layers no longer show in the parent comp's viewer or in a "Current Settings" render.
The Timeline layer and Composition viewer right-click menus (shared layer_menu) gain a
Guide Layer item (a plain toggle, no check mark) acting on the selection, and guide layers show a guide mark
before their name in the Timeline.
Co-Authored-By: Claude Opus 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>
Expansion was a fill plus a stroke 2·|expansion| wide, blended per pixel. Once the
expansion passed the shape's curvature radius the stroke's inner edge inverted: a
20 px circle expanded by 60 left a ring with the layer showing through the middle,
+600 cut only the original dot, and negative values left islands. The outline is
now offset with Offset Paths' geometry (round joins) and filled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Merge Paths item appended to a shape layer's Contents cleared every draw
collected so far, including fills and strokes inside groups above it, so
the layer's shapes vanished from the render. AE's rule is per group: only
paint items in the merge's own group above it are dropped. Draws now
record which contents level created them; the merge drops only its own
group's paint, and the group-bubbled draws keep rendering.
Adds a regression test (Merge Shapes and Add modes) that renders nothing
without the fix.
Stretched layers copied the nearest source sample for each output sample, which adds error: at
48 kHz the worst error on a 440 Hz tone was 0.025, and 0.15 at 8 kHz. They now blend the two
source samples either side. Layers at 100% and 50% speed keep their exact samples.
The render tests cover a plain precomp, which mixes the same audio as its layers, and the stretch
error against the exact tone.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Echo, Posterize Time and the other Time effects read the layer at other times with none of
its effects. After Effects renders them with the effects before them: SceneYard "Task
Preparation UI" applies Echo after CC Particle Systems II on black solids, and AE shows the
echoed fireworks over a white page where we echoed the black solids and blacked it out
(Posterize Time after Turbulent Displace, the usual stop-motion setup, depends on the same).
The host already bounds the count to the effects before the one rendering.
Mean abs error vs AE: Task Preparation UI 0.1855 -> 0.0071, Lightbulb Idea 0.0126 -> 0.0102;
the 15 other corpus projects with a Time effect after another effect are pixel-identical.
(cherry picked from commit 46870452db2656c72406f63245e8b1609ceb04d4)
(cherry picked from commit 334d102582e5a64db32e2f3e55836d55ac96bdaa)
(cherry picked from commit 734da3e35dc57c01b73823458bdd87611ac1bf99)
(cherry picked from commit 87a733e076)
- Renderer: no cover step for adjustment layers (#686); their buffer is the comp below.
Restores a single CPU step in split adjustment stacks.
- Effect Controls rename (#953): focus the name field the frame it exists. Focusing an id
not yet drawn left AccessKit with a focus outside its tree (a panic in the consumer).
The tests double-click at 60 fps, within egui's double-click time.
- i18n: Russian, French and German entries for Copy Frame to Clipboard and the extrusion
Front/Bevel/Side/Back Color submenus added by today's other PRs.
- Tests: "fr" is now a valid language; the marker dialog's comment is focused on open
(#922), so Enter-is-OK is tested with the focus elsewhere; the toolbar gradient tests
give the selected layer its paint kind, since the toolbar mirrors it (#732).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Footage sources: the cancellable wrapper (#784) forwards frame_with (#752 float curves)
and problem (#887); problem's default lives on the trait.
- Media: a called-off decode (#784) is not a footage problem (#887).
- Effect Controls rename (#953): Escape cancels (egui drops focus on Escape the same
frame), the Rename item is translated; regression test.
- i18n (#873): drop the /out-i18n/ ignore rule and the note about an uncommitted script.
- Tests: text range selector caps (#926) re-borrow the layer; the RAM preview test (#793)
sets the comp's own Resolution (#462).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shape layers are always continuously rasterised, as in After Effects and
as text layers already are here: the contents are drawn at the layer's
effective scale (own transform and parents) whether or not the
Continuously Rasterize switch is on, so scaled-up shapes stay sharp.
Fixes#751
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: the extra panes of View > Switch View Layout were fixed to Top, Front and Right, and the 3D View menu only changed the main view.
Fix: every extra pane has a view selector (Active Camera, Default, Front, Left, Top, Back, Right, Bottom, Custom View 1-3). The choice is kept per comp in Views3D::panes (serde default, so older editor states load with Top/Front/Right) and set by the new command view.setPaneView {pane 0-3, view}. The Active Camera pane renders through the comp camera.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Problem: a movie whose profile the software decoders lack (H.264 High 4:2:2 / 10-bit)
probes fine, then every frame read fails; MediaPool logged the error per frame and returned
None, so the layer rendered black with no sign why in the viewer, Project panel or for agents.
Fix: MediaPool records the reason once per file (codec from the probe plus the decoder's text
without error wrappers; I/O errors stay with missing-footage handling), logs it once, and
exposes it through the new FootageSource::problem. The Composition viewer shows it on the
affected layer, the Project panel marks the item with a warning whose tooltip is the reason,
and project.summary / footage.info carry a "problem" field. Cleared when the file is
forgotten; only set when decoding really failed, so a hardware decoder that handles the file
shows nothing.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Picking Alpha / Alpha Inverted / Luma / Luma Inverted in a layer's Timeline
Track Matte menu only dispatched a command when the layer already had a
matte layer, so with "No Track Matte" the click closed the menu and did
nothing. The mode items now run layer.trackMatte (Layer > Track Matte),
which keeps an existing matte and otherwise takes the layer above, or
reports that the top layer has none. The menu ticks the current mode and
the cell's tooltip names the matte layer and the mode (the 104 px cell
has no room for both).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: computed Object Matte segmentations lived only in the in-memory
cache, so every restart or memory eviction segmented the shot again (After
Effects 26.5 keeps Object Matte results in its disk cache).
Fix: the persistent disk cache gets a third entry kind, Matte (folder
`mattes`, LZ4-compressed FrameSeg bytes, same checksum, atomic writes, LRU
eviction under the one size limit, purged with the rest). Entries are keyed by
the segmentation's chain key and scale; chain keys already hash the Input Key
(the layer's source, timing, masks and effects above), the prompt, the mode and
the model, so any change simply misses. The effects crate gets a SegStore
trait behind its memory cache: Chain::ensure reads from it on a memory miss
and writes what it computes. The engine installs the session's DiskCache as
the store (held weakly, so switching the disk cache off drops it). Roto Brush
segmentations go through the same chain and are persisted too. Frozen mattes
already persist in the project (the Frozen parameter).
Tests: engine tests_roto::object_matte_mattes_persist_in_the_disk_cache (a
second session on the same folder and project gets all tracked frames back
without being able to read a single input frame, renders identically, and a
moved prompt finds nothing); render disk_cache
segmentations_round_trip_and_reopen (round trip, reopen, damaged file is a
miss, size limit evicts).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: scrubbing a long movie showed the frame under the pointer
seconds after the pointer stopped. Every viewer frame a free frame-pool
thread had started kept running: each seeked the movie's one decoder and
decoded up to a GOP, and the newest frame waited behind all of them on
the decoder's lock (45 min 1080p H.264/HEVC, GOP 250: 1.8-3.6 s for the
last frame of a drag; a single jump is 0.1-0.4 s). FilmCraft's decoder
already polls a cancellation flag, but nothing set one.
Fix: FootageSource::cancellable / called_off and CancellableFootage (a
per-render view whose frame reads run under the render's own flag, so
reads on other threads answer to the right render). MediaPool runs them
under filmcraft_media::cancel::with_cancel. The frame workers render
through it, and a newer viewer frame calls off the comp's running viewer
frames of other times. A called-off render is not cached (frames, disk,
layer cache). Drag latency is now 0.1-0.3 s.
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>