The old check zipped the padded output with the unpadded source, so pixels never lined up and
it always passed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add Layer Styles submenu to the Timeline and Composition viewer layer context menu, matching After Effects parity and the application Layer ▸ Layer Styles menu.
- Adds Layer Styles submenu between Blending Mode and Effects
- Exposes Layer Style Options..., Convert to Editable Styles, Show All, Remove All, and the 9 individual styles
- Translates Layer Styles across all 7 UI catalogs
- Adds regression test verifying submenu presence, position, and action invocation
project_dir rebuilt the folder with `/` and dropped a UNC share's leading
`\`, so a project opened from \server\share\Job looked for its moved
footage under the relative path server/share/Job and never found it. It now
keeps the path's own separator and its leading `/` or `\`.
The relink tests built mixed-separator paths (`…\Job/p.ecproj`) and so failed
on Windows; they now use native paths and check the separator is kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Drop the byte-count tooltip, the "—" marker and their strings and UI test,
and the bench wrapper script: they are outside this PR's scope and After
Effects leaves the Size cell blank when the size is unknown. The size sort
and its tests are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review on #1627: the X arrow tip can sit on a bounding-box handle (a 120 px
wide layer at 100%). The arrow is painted on top, but the press and hover
tests checked the handles first, so dragging it scaled the layer. Check the
arrows first in both, and cover the overlap with a pointer-input test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 3D gizmo's X/Y/Z arrows were drawn but not interactive, so pushing a
layer in depth meant typing Z Position by hand. With the Selection tool,
dragging an arrow now moves the layer along that axis only (no snapping),
as one undo step, reusing the viewer's Move gesture.
An axis pointing at the camera (Z in the default front view) projected to
a dot; it is now drawn as a short diagonal stub so it can be grabbed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The After Effects stagger idiom
id = thisProperty.propertyGroup(2).propertyIndex;
failed with "not a callable function": the expression object model had
no propertyGroup() and property groups had no propertyIndex.
- Property.propertyGroup(countUp = 1) and PropertyGroup.propertyGroup()
return the group countUp levels up; from a top-level group they reach
the layer. A count below 1, not a number, or above the layer is an
error naming propertyGroup, never a crash.
- PropertyGroup.propertyIndex: the group's 1-based index among its
siblings (Child requests now carry it).
- One new host request (Ancestor) walks up from the node's full uid
path, so it also works for the property being evaluated.
- Tests for the stagger expression, effect parameters, reaching the
layer, other layers' properties and groups and out-of-range counts;
the fuzzer seeds and tokens include the new members.
Load PDF document objects through the pinned craft-pdf parser and convert them into EffectCraft's existing drawing model with bounded traversal, recovery and retained data. Preserve generation numbers, stream limits, encrypted-document refusal and reported repairs.
Retain app-owned drawing, EPS, fonts and content/image decoding. Assert early excessive-nesting rejection without partial art, and cover tolerant arrays on native and WASM.
Validated with the22-stage local matrix and exact-head FreeBSD, Windows ARM64 and packaging checks.
Co-authored-by: Codex <[email protected]>
The Effect Controls header menu and the Timeline property menu build their
items with the same local closure the layer menu used, so they still drew
English. Both now go through i18n::context_item; Copy, Paste, Edit Value…
and Remove Expression get panel catalog rows.
The face's own style (Figtree's Light) is its default instance, so it
resolves without axis values and lays out as before, on the static path.
docs/agents.md says how named instances are selected.
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).
Clicking keyframes on Position, Rotation and Scale highlights those
properties, and Copy then added every key of a highlighted property that
had none selected, so deselected keys came back on paste. The keyframe
copy branch also left a stale value clipboard that Paste preferred over
the new keys.
Copy now takes whole animations only when no key is selected or the
Transform group is selected, and every copy branch clears the clipboards.
Also collapses a nested `if` in the STL importer into a match guard,
which Clippy 1.95 flags under -D warnings (no behaviour change).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The font scan read only whether a face has an fvar table, so a variable
family offered one style (its default instance), fontStyle = "Black"
picked that face with synthetic bold, and an instance's PostScript name
(Figtree-Black, what After Effects scripts use) found no face at all.
The scan now reads the named instances (style, axis values, PostScript
name, or the one Adobe Technical Note #5902 derives). They are styles of
the family in text.fonts and the font menus, a style, PostScript name or
full name selects the instance, and layout uses its axis values as the
base variations; axes set on the text still win.