clippy's allow-unwrap-in-tests only recognises #[cfg(test)], not cfg(all(test, ...)),
so cargo xtask ci failed on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
normalize() canonicalizes whatever part of a path exists, which on Windows adds
the verbatim prefix to some paths and not others, so a file under a folder
that isn't on disk failed the project-folder check against an existing
project folder. The check failed closed (access refused); it now drops the
prefix from canonical drive paths so both sides compare.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the browser editor a .jsx script couldn't open any file, even one the
page holds next to the project: File.open("r") returned false with "File
not found" and File.exists was false. The footage check after File > Open
also marked imported media as missing.
Both asked whether a file exists on the local disk (std::fs), which has no
files on wasm32:
- scripts read files through Session.services but checked existence with
std::fs (File.open's access check, File.exists, Folder.exists); they now
ask services.exists / services.is_dir, after the folder check;
- WebServices kept the default exists (std::fs); it now looks in the file
table.
Desktop and CLI are unchanged (FsServices keeps the std::fs defaults).
Tests: script tests_services (a host with in-memory files), web smoke
step hostFiles (a script reads a stored file, footage check finds stored
media).
`exec_on_keys` put the named keys in the key selection, then `exec` made
the keys' comp active. Since comps keep their own selections (#1037),
activating the comp stored the injected keys with the comp that had been
active and brought back the target comp's own key selection, usually
empty, so `keys.interpolation` and `keys.velocity` had nothing to act on:
setInterpolationTypeAtKey and setTemporalEaseAtKey were silently ignored
in any comp other than the active one. The keys' comp is now made active
before the keys are selected, and the comp that was active comes back
afterwards, as `exec` does for its own target (now `leave_comp`).
With Warn User When Executing Files on, file.executeFile asks the
frontend first, and the frontend's answer re-runs it with
`confirmed: true`. That flag was taken from the parameters whoever
called, so a script could pass it itself (app.run('file.executeFile',
{path, confirmed: true}), also inside engine.batch) and the file opened
without the question, as long as the user had allowed file and network
access.
`confirmed` now counts only when the command isn't running for a script
(Session::running_for_script, which #590 uses for the same reason and
which stays set through nested batches). A script's request always gets
the question; the user's answer from the frontend's form still opens
the file; with the warning off nothing changes. The permission gate
itself is unchanged.
Tests: engine a_script_cannot_confirm_executing_a_file_for_itself
(execute_for_script directly and through engine.batch; the frontend's
confirmation; warning off) and the same through real JavaScript in the
script crate (app.run, app.run('engine.batch'), File.execute()). Both
fail on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ReferenceError's first use was searched across the whole file, so a mention
earlier in a comment, a string or another function could report the wrong line.
The search now starts at the innermost script frame's line, and covers the whole
file only when there is no frame. Follow-up to storytold/effectcraft#1161.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
- Cryptomatte (#1183): planes shorter than the image read as empty instead of panicking.
- layer.addMarker (#1180): a comp marker no longer replaces one at the same time.
- Desktop (#1005): ignore macOS's -psn_ argument instead of refusing to launch.
- Doc comments put back on their own items (#1153, #1037).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A single selected_layers list kept ids from every comp, so the active comp looked selected whenever another comp was. Opening a comp and running a script against a different comp now park and restore that selection.
sanitize_state dropped every selected layer not in the active comp at the
end of each script run, so a layer selected by script in a comp that was not
open was gone by the next script call. Every comp keeps its own selection as
in After Effects; only ids gone from every comp are dropped now. UI and
engine lookups resolve selections per comp and tolerate foreign ids.
FootageItem had no replaceWithSequence, setProxy, setProxyWithSequence or
setProxyToNone, useProxy and proxySource were fixed stubs, and mainSource
was a new object on every read with no conformFrameRate. They now run the
existing commands (file.replaceFootage, file.setProxy, file.useProxy,
file.setProxyNone, file.interpretFootage); the item info reports the proxy
and the conform/native frame rates, and mainSource is one live
FootageSource per FootageItem. replaceSource is not covered: it needs an
engine command that changes a layer's source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects scripts save a comp frame with saveFrameToPng(time, file);
EffectCraft only had the comp.saveFrameAs command, so the method was
undefined. It now runs comp.saveFrameAs at the given comp time, after the
same file-gate check as other script writes (Allow Scripts to Write Files).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dashes reported its children as `dash`, `gap`, `offset`, so
`property("ADBE Vector Stroke Dash 1")` was null and addProperty threw.
They now report ADBE Vector Stroke Dash 1-3 / Gap 1-3 / Offset.
addProperty with one of those names returns the existing property
(EffectCraft's Dashes always holds Dash 1, Gap 1 and Offset) or adds the
Dash/Gap 2 or 3 pair through shape.dashes.add. Lookups by the old ids
still work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A script asks a light's options for "ADBE Casts Shadows", which returned null. The light's option
is named "Casts Shadows" here, so the lookup never matched. The name is now an alias for the
castsShadows property, and a regression test checks the lookup returns the property.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Inside a function wrapper such as (function () { ... })(), boa places a ReferenceError at the start of
the function, so a script's error was reported at the wrapper's first line rather than the line that
failed. The first use of the undefined name in the code is now the position, and boa's frames remain
the fallback. Thrown errors and object-model errors already report their own lines. A regression
test covers a thrown error, an undefined name, and an object-model error inside a wrapper.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Pro Levels2 keeps its Alpha controls one group down, under Alpha, and a script's
effect.property("Alpha Input White") only searched the effect's direct children, so it returned null.
The lookup now searches the groups nested below the direct children, up to three levels, after the
direct match fails. Direct children keep their priority. A regression test checks the name on the
effect and the same control through the Alpha group.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Every key call (removeKey, keyTime, keyValue, setInterpolationTypeAtKey…)
looked its key up through `__keys()`, which serialised the whole key list,
eases and tangents included, on each call. Removing keys one by one was
therefore quadratic: about 100 ms per removeKey on a 2,000-key property.
The `keys` query now takes an optional `index` and returns just that key
with the count; `__key` uses it. removeKey on 2,000 keys drops from
103.7 ms to 0.4 ms (release, Windows 11 aarch64).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows $.fileName is the full path with backslashes, so split('/') returned the whole path
and includes_are_inlined_relative_to_the_script failed. The test now takes the last part after a
backslash and then after a slash, with plain string splits: a regex split did not split the path
in the script engine.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
CameraLayer gets the transform and camera-options shortcuts (position, xRotation, zoom...) that AVLayer already had, and Convert Expression to Keyframes run without a property converts the selected ones, as app.executeCommand does it.
Both returned their input unchanged. They now go through the layer's transform and its parents'
at comp time, and the inverse.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
setInterpolationTypeAtKey and setTemporalEaseAtKey act on effect parameters
(a Slider Control's slider and a 3D Point Control), not just transform
properties. Verified on current main; lock it in with a script regression
test driving the per-key methods through the property object.
A script reading an expression that calls sampleImage on a footage layer got
[0,0,0,0]: the script evaluator built its EvalCtx with EvalCtx::new, which
has no footage source, so the renderer substituted transparent frames (solids
and shapes stayed correct because they are procedural). Pass the session's
footage source, as the app's renderer and diagnostics already do.
Opening movies in place (#796) skipped the whole-file bytes #827 read the
track matrix from, so the pool and the probe lost the turn for movies on
disk. stream::movie_header reads just the ftyp and moov boxes (bounded,
positional reads) and both in-place paths derive the orientation from it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>