* fix(engine): refuse to convert an expression on a layer of more than 2^18 frames
* address review: refuse a non-positive frame duration; count a partial last frame; check every selected layer before converting
* Convert Expression to Keyframes: cap at 2^20 frames (a 3-hour 60 fps comp is 648,000)
* Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)
* Import: a model's left-out parts reach the user as an import warning
On a precomp layer the key covered only the nested comp's size, rate and
duration, so an edit inside the precomp kept a stale analysis. The key now
writes the precomp layer's Essential Properties and what its comp draws, field
by field and recursively. Each comp is written once per key and nesting is
capped, so shared, deep or cyclic precomps are keyed in bounded time. Docs say
what clears an analysis.
Their analyses are made from the layer's input to the effect (masks, then the
effects above it), but the key covered only the layer's frames, so a mask or
effect edit kept a stale analysis. frames_key::analysis_key adds the masks,
the effects above the instance and the Effects switch. Keys saved before
(frames only) stay valid while each edit keeps the input.
Expressions find effects by name and read names (thisProperty.name,
propertyGroup(1).name), and seed random/wiggle with the property uid, so
both can change the frames. The Debug-based key had them.
Whether a footage file is there is an observation (footage.check sets
Footage::missing without an edit), not part of the footage. The
signature leaves it out, and keys saved by 0.22-0.24, which hashed it,
are recognised with the file either online or offline. Warp Stabilizer,
3D Camera Tracker and Roto Brush analyses are kept through the next edit
while the footage is offline and after it comes back.
analysis::frames_key writes what a layer's frames are made of field by
field: every struct and enum it writes is destructured or matched in full
and each variant is a fixed word, so a new field doesn't compile until it
is decided and a rename doesn't change keys. Names, labels, UI hints and
markers are left out. Roto Brush's Input Key writes masks and the effects
above it the same way, instead of their Debug text.
Keys saved by 0.22-0.24 are recognised by the old code, moved unchanged
to frames_key::legacy, and kept rather than rewritten (frozen mattes hash
them).
A fixture keyed by the current release: Warp Stabilizer, 3D Camera Tracker
and Roto Brush keys keep matching through edits that keep the frames, undo
and save/reopen, and real changes still clear them.
The viewers replace their picture only when the frame key changes, and a
frame rendered again after a LUT change had the same key, so the old look
stayed on screen. The viewer frame keys carry the LUT generation (in memory
only), which also leaves frames still rendering with the old LUT unasked
for, so the refusal of late frames is no longer needed.
A precomp instance whose Essential Property names another LUT file renders
with that file, but a layer reading the instance through a layer parameter
was keyed on the nested comp's own file, so editing the overriding file left
that layer cached.
Renders now follow a LUT file, but the viewer and RAM preview kept the
frames already rendered with the old one. A small worker stamps the LUT
files in use every two seconds and wakes the interface when one changed;
the interface then drops its frames, and a frame whose render started
before the change is not kept when it finishes.
Analysing a tracker reused its saved tracks only when their key was
today's, so solving a 0.22 project again (after a Solve Method change)
tracked the footage again, and failed when it was offline.
Their keys were compared only with today's signature, so the first edit
of a project saved by 0.22 (even adding it to the render queue, as
effectcraft-cli render does) cleared both analyses and queued them again,
and lost them when the footage was offline.
One check, warp::key_matches, now accepts a key made from today's
signature or from the one 0.22 wrote for the same frames, and keeps it.
Warp Stabilizer, the 3D Camera Tracker and Roto Brush all use it.
The regression test opens a project saved by the released 0.22.0 CLI.
A layer graded with Apply Color LUT or Lumetri's Input LUT and Look was
cached by the parameter text alone, so the same frame kept showing the old
LUT from memory and from the disk cache after the file changed. A layer's
cache key (and the key of a layer reading it, through a precomp too) now
holds each LUT file's size and modification time, and a render lists the
LUT files it read, so a frame kept on disk made with an older file is not
used.
The Warp Stabilizer, 3D Camera Tracker and Roto Brush signatures hashed
the footage item's derived Debug text, so every new Footage field changed
every signature and dropped every saved analysis (placed_page did in 0.23).
Write the same text from a named list of fields instead: a new field no
longer compiles until it is decided there. Pin the text in a test.
* fix(scriptui): bound the edges and bands a fillPath is tessellated with
* address review: cap the triangles a ScriptUI fill produces
* address review: raise fillPath limits (65,536 edges, 262,144 bands, 4M triangles, work cap); fill what fits and log a warning instead of nothing
* address review: bound the fillPath crossing scan: edges swept by top, pairs and boundaries counted against the work and band budgets
* address review: bands with crossings that were not cut are left unfilled when the crossing scan is over budget
* address review: bands left out of a fill still count against the work budget
* fix(engine): index sequence runs and folder listings on import; probe relink candidates once
* address review: cap relink candidates before cloning paths; reuse a picked folder's listing for its run lookups
* Scripting: export commands a script runs directly respect Allow Scripts to Write Files
CompItem.saveFrameToPng and exportAsMotionGraphicsTemplate checked the
file-write preference only in their JavaScript wrappers. A script could call
comp.saveFrameAs or essential.exportTemplate itself (app.run, __exec) and the
native handlers wrote the file with the preference off.
Session::refuse_export and write_output_with now refuse when the running
command was issued by a script and the preference is off, so every command
that writes a path it is given is gated the same way. The wrappers check the
gate with a new side-effect-free __file("checkWrite") instead of writing an
empty file at the destination.
* address review: clear stale export test dir before use
* test: allow script writes in script_imports_footage_and_saves
In a comp whose Start Timecode is negative, the current-time display shows
the first frames as `-0:00:00:12`, but typing that back (the Go to Time
dialog opens with it since #3419, and the Timeline's time field takes it)
read the leading `-` as "move back", so OK on an unedited dialog jumped to
another frame. There, a `-` before a full timecode is now the time's sign,
as it already is before Feet + Frames; a bare `-n` still steps back, and
comps that start at 0 or later are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Synchronize Time of All Related Items clamped each related comp's time
with its own duration-minus-one-frame rule, so a nested comp whose
duration is off the frame grid could not reach its final partial frame.
Use comp_frame_at, as direct moves and restored times do.
A frame-rate change can leave the duration off the frame grid. The
current-time clamp used duration minus one frame, which put the last
reachable frame before a final partial frame, so a layer fitted to a
work area inside it never got the playhead. Clamp to the last frame
that starts before the end instead; on-grid durations are unchanged.
The work area need not sit on frames (a typed start, a frame-rate
change), so rounding its start to the nearest frame could land before
the new layer's in-point. show_span now rounds up to the first frame in
the span and moves nothing when the span holds no frame.
show_span now checks and moves the playhead of the comp the layer was
made in, through a new Session::set_time_of that set_time uses for the
active comp, so time sync and the text editor boundary stay the same.
With Fit New Layers to Work Area on and the playhead outside the work
area, the Type tool, shape tools and Pen now move the playhead to the
work area start so the new layer and its controls show. Layer timing is
unchanged; Layer ▸ New commands leave the playhead.
Settings ▸ General ▸ Fit New Layers to Work Area (off by default) makes
new text, solid, shape, null and adjustment layers start at the work
area start and end at its end. It applies to Layer ▸ New, the shape
tools and the Pen tool, and wins over Create Layers at Composition
Start Time. Footage and compositions keep their own length.
It never steps back up during a play, so a looping preview kept playing at a quarter of its resolution after its first loop. Without it the paced first loop caches the viewer's resolution and the next loops play in real time, as in After Effects. The setting stays for whoever prefers a lower first pass.
A GPU frame that failed or failed its health check, and a remote frame replaced before it finished, stayed counted as running on its side, so every later frame there was timed as one of several side by side and that side looked cheaper. AutoPick::abandoned releases them.