Split out of 7782ab01 (shapes: Reverse Path Direction …; work area keys) for upstream, where it
belongs with the commands it binds.
(cherry picked from commit 7782ab015a01613c1685cb6dda48d48c3b1db67b)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01F2dXDtF1ivCLP4tQwFveQ4
(cherry picked from commit b58cfb7434e147535e66582718b8856bd3ce1230)
(cherry picked from commit ff4876054f3cbf9388954602e8fe7288d136c255)
(cherry picked from commit 79d083a8c2a0bfa4663445257149a0d0ce491987)
Shortcut presets accept Physical+<key> (egui US-position names) so layouts that report the
same logical key for two physical keys (AZERTY's - and )) can bind them separately; physical
bindings win for that press, and a focused panel's binding wins over an application command on
the same chord. ui.key accepts the same notation. Adds time.workAreaStart / time.workAreaEnd.
(cherry picked from commit 209543e9e0ba32f809f9ff333abb8b8ec4125cea)
(cherry picked from commit f8301a9e3e0b60e200e8a233455398741658481e)
(cherry picked from commit 4d002fabf992934e1bb73a0c32bc9f3fd3ae5447)
(cherry picked from commit 5d466c5955ee934d5347c194ec7d15fb5f58bba4)
File > Import > EffectCraft Project (and .ecproj/.ecprojx given to File > Import)
merges the project into a folder named after the file, with every id shifted past
the open project's, in one undo step. Relative footage resolves next to the file;
the result lists comps, root comps, primaryComp and missing media.
offset_ids now shifts tracker Motion Targets and saturates instead of overflowing;
fix_next_id saturates too, so a damaged file can't overflow on open or import.
Menu entry translated in all ten menu catalogues.
Rebased onto current upstream main for the split of #858 (test module list in
engine lib.rs; es/pt-br/ru/fr/de/cs menu translations).
(cherry picked from commit 19c653fccc95133fb35e978a7f7f20a18c0e8c89)
(cherry picked from commit 4e751ad2)
(cherry picked from commit ceed706cc3b5f3755769738a1ddb3d965a94ab6b)
3eb3b99 makes the particles replace their layer as in AE, so their 112 pinned
CPU hashes change intentionally; no other effect changed.
(cherry picked from commit 9900eae773e1cad43a6d1bdcacbadc45a84b063a)
(cherry picked from commit 7aa27c0dd7c24b9f24591bb144a95951dc0ec510)
(cherry picked from commit 73377e8eca5c639715e5f3f623ed53ff329638a0)
(cherry picked from commit f07497dfee)
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)
The particles were composited with the original layer by the Transfer Mode, so the solid an
artist applies them to stayed visible. After Effects 2026 frames show no trace of it:
SceneYard "Burnout matchstick" (a red rectangle around the flame), "Documentary Man Falling"
(a black block over the light), "Task Preparation UI" (black solids under fireworks). The
Transfer Mode now only sets how the particles meet each other.
Mean abs error vs AE: Burnout matchstick 0.0191 -> 0.0130, Documentary Man Falling
0.1177 -> 0.0937 (Task Preparation also needs the next commit).
(cherry picked from commit 3eb3b9958f9cd929dcf060865e604d42cc0f9337)
(cherry picked from commit 729638710e63f4da10711fc5e6a1f7ddc42c37aa)
(cherry picked from commit 083332116c936f1b4863f0bb390d0647f2b567a1)
(cherry picked from commit 338421de36)
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 <[email protected]>
layer.source was undefined in expressions, and a layer from a Layer Control had no transform.
Both now work.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
Shift+Spacebar set Skip to 1, so it played every other frame out of the box. It now plays every
frame, like Spacebar. Skip still works when it is set.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
The menus have been complete in Japanese since the menu catalog landed, but the panel
catalog below the menu bar stopped at 410 of the 506 source strings the Chinese catalogs
define. Settings, the Home screen, the viewer tools and the Fast Previews and channel
popups therefore still drew in English for a Japanese interface.
This adds the 96 missing rows. Existing rows are untouched and keep their order; the new
ones follow the canonical (Simplified Chinese) order, which the catalog tests require, and
no row changes the number of `{}` values its English source carries.
Terms follow the Japanese already in the catalog and, wherever a control exists in both,
the wording After Effects 2026 (26.5) itself shows in Japanese: 平面 (Solid), 関心領域
(Region of Interest), フル画質 / 1/2 画質 / 1/3 画質 / 1/4 画質 (viewer resolution),
ドラフト 3D (Fast Previews), 透明グリッド (Transparency Grid), マスクとシェイプのパスを表示
(Toggle Mask and Shape Path Visibility), チャンネルとカラーマネジメント設定を表示 (Show
Channel and Color Management Settings).
The crate's `i18n` unit tests pass (10/10) and rustfmt is clean.
First in the menu, as in After Effects, and for the right-clicked comp rather than the one in the viewer: the dialog keeps the comp it edits and OK passes it to comp.settings. comp.settings with only {"comp": id} opens the dialog on that comp.
Zoom to Fit Comp remembers the span it left and goes back to it when pressed
again from the whole comp.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Alt+1 after Alt+Shift+1 shows the red channel in grayscale instead of going back
to RGB (and the other way round). The same key again is still RGB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
layer.sampling takes `toggle`: layers that all have the sampling already get the
other one. The Bilinear and Bicubic menu entries, which carry the keys, pass it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
F2 Deselect All, Cmd+Shift+T Effect Controls, Cmd+Alt+Left / Right Go to Start /
End, Cmd+Shift+/ Add to Render Queue.
Effect Controls is a menu entry with bound parameters, which could hold one key:
a menu line can now give further default keys after a comma
(F3, Cmd+Shift+T); the menus show the first. MenuEntry gains alt_shortcuts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
arc added 2π to its sweep in a loop until the sweep was no longer
negative, and arcn subtracted 2π the same way. For an angle of 10^18
degrees the loop took about 3e15 passes, and from 2^56 radians (about
4.1e18 degrees) up, adding 2π left the sweep unchanged and the loop
never ended. A sweep that needed no loop, as in 0 10^18 arc, was passed
unchanged to kurbo, which divides it into more than 1e16 curves (about
four per turn at a radius of 10), and arc appended each curve to the
path. Importing an EPS file probes it with effectcraft_pdf::parse,
which has no time limit, so importing such a file did not finish.
As in storytold/vectorcraft#1062, every arc now starts at the first
angle's remainder after whole turns, and when the second angle is below
the first (arc) or above it (arcn), the sweep is worked out from the
two angles' remainders. A sweep of up to 1,024 turns is drawn whole.
For a longer one, the import records a skip note
("arc (too many turns)") and leaves out whole pairs of turns, so that
between 1,022 and 1,024 turns remain. The arc still ends at the second
angle, and an even-odd fill covers the same area as the full sweep.
Arcs of up to 1,024 turns whose angles are within about 10^6 degrees
are unchanged apart from rounding, except some whose angles are a whole
number of turns apart or within rounding of that. For larger angles,
the old conversion to radians lost precision: at 10^15 degrees, a
quarter-turn arc swept 1.5723 radians instead of 1.5708.
When raising (arc) or lowering (arcn) the second angle by whole turns
makes it equal to the first, as in 7 -353 arc or 2 362 arcn, rounding
sometimes made the old loop add a turn and draw a full circle. Those
arcs are now empty, as the PostScript Language Reference defines them.
When the raised or lowered angle falls just short of the first
instead, the reference defines a full circle, and each version leaves
some of these arcs empty: the old code drew nothing for
125.00000000000001 125 arc, which the new code draws as a full circle,
and the new code draws nothing for 1.0000000000000002 -719 arc, which
the old code drew as a full circle.
arcs_with_huge_angles_end_at_their_second_angle parses each program on
a thread of its own and fails when no result arrives within 20 seconds.
Both were a single command taking the mode as a parameter, with no menu entry,
so nothing could carry a default key. Each mode is now its own command:
view.fastPreviews.off / adaptive / draft / fastDraft / wireframe (Cmd+Alt+1-5)
and view.channel.red / green / blue / alpha (Alt+1-4), redColorized /
greenColorized / blueColorized (Alt+Shift+1-3) and rgbStraight (Alt+Shift+4).
A channel's key again returns to RGB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cmd-click (or Alt-click) a twirl arrow now takes the whole tree with it in
three more places: Audio's Waveform row in the Timeline, an effect or effect
group in Effect Controls, and a folder in the Project panel.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The cap applied whenever an accelerator was attached, so Software Only
projects (which render on the CPU) rendered one frame at a time. It
now needs the project's GPU acceleration too, as #898 gates it.
- Only the render is capped: each window of max(cap, threads) frames
renders in chunks of the cap, then encodes in parallel, holding at
most one window of images.
- An invalid EFFECTCRAFT_GPU_INFLIGHT (0, negative, not a number, too
large) logs a warning before using the default.
- The default stays one frame: the GPU backend reports no free or total
device memory portably, so it is not sized from VRAM.
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.
The engine's layer.create has shapesFromText and masksFromText, but the
timeline and Composition context menus only offered Create Shapes from Vector
Layer, so text could not be converted to editable outlines from the UI. Gate
the two text conversions on a text layer (LayerMenuCaps replaces the lone
vector flag), beside the vector one.
Cmd-click (or Alt-click) the arrow of Effects, Contents, Masks or any other
group in the Timeline twirls every group under it open, or closed, with it,
as the layer's own arrow already did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
Drop the 6 px added to expression editor rows (and the matching offset in
the drag-to-resize maths), so the row is a whole number of row heights.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Three frames at once still filled an 8 GB card on the #879 project at
1080p (7.9 GB, the same as no cap); one frame peaked at 6.3 GB and two at
7.0 to 7.8 GB. One matches how the export scheduler in #898 renders image
sequences (one GPU worker, synchronous readback). EFFECTCRAFT_GPU_INFLIGHT
still raises it.
Rows stripe by their index among the non-expression rows, so adding or
removing an expression leaves every other row's stripe unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
setParent re-expressed Position for 3D layers but skipped cameras and lights
entirely, so parenting a camera to a (default) null moved its eye by the
null's world transform and the framing jumped. Cameras and lights now get the
same parent-space Position fix, plus Point of Interest for two-node rigs
(values, keys, spatial tangents and ease speeds). They have no Scale, so
Orientation stays as authored.
Shift-click adds a vertex to the selection and removes it again, a plain
click collapses to one, and a marquee from empty viewer area selects the
enclosed vertices (additive with Shift) without moving the layer. These
behaviors regressed or were never covered; the tests drive them through the
Composition viewer like a user.
With a GPU accelerator every frame of a batch rendered in parallel, each
holding its own working textures, so a 30-frame 1080p batch could take
the whole card (20+ GB on an RTX 3090) and run shared-memory GPUs out of
memory. Cx::frames now renders GPU frames in consecutive chunks of 3
(EFFECTCRAFT_GPU_INFLIGHT overrides it), the same cap as the export
scheduler in #898; output order and the CPU path are unchanged.
Fixes#879
WenQuanYi Micro Hei is a Simplified-oriented face, so keeping it in the Traditional list - even as a
last resort - could still draw Simplified forms, which is the one thing the split is for. Heiti TC
and Hiragino Sans CNS are the macOS Traditional faces the family's own lists use (photocraft's
`CjkScript::TraditionalChinese`). Dropping the entry does not lose the face: the generic scan behind
the script lists still finds it when nothing Traditional is installed.
The test now walks the `FallbackScript::Han` arm as well, so a list wired to the wrong script fails
it, and it refuses a WenQuanYi entry in the Traditional list.
anim.reveal keyframes/animation only matched keyframed top-level properties,
so Time Remap vanished from U once its keys were deleted even though enabling
remapping is itself the modification (and freshly enabled remaps, which get
two keys, kept showing). Match the bare timeRemap prop for both kinds, as
modified already did.
The Properties panel kept its own ephemeral per-layer link toggle, so
unlinking Scale there left the Timeline linked and vice versa, and its edit
math differed from the Timeline's. Use the shared per-property unlinked set
and widgets::constrained, with the stable match id instead of the display
name, plus Alt-edits-one-axis like the Timeline.
The viewer.nudge shortcuts bailed out unless Composition, Viewer or Timeline
had focus, so with any other panel focused the arrows silently did nothing.
After Effects nudges from almost anywhere; only the Project panel owns the
arrows for its own keyboard navigation. Typing, dialogs, menus and selected
mask vertices still keep the keys.
Tests: arrow nudge from Effect Controls, no nudge from Project, Shift+arrow
10x, time untouched, click-row path.
Right-clicking selected layers and choosing Pre-compose grouped only the
right-clicked layer in 0.8.0 while Layer > Pre-compose grouped all of them.
The menu plumbing was fixed since; add a regression test driving the dialog
from both the outline row and the layer bar and asserting both selected
layers end up nested in the new composition.
The message box's only button is OK, the default; Enter now dismisses it
(back to Settings when opened from there), as Escape already does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Han fallback list carried a Simplified and a Traditional face for every platform (Microsoft YaHei
next to Microsoft JhengHei, PingFang SC next to PingFang TC), Simplified first. The fallback search
keeps the first family whose face covers the character, and the two scripts share their code points -
only the glyph forms differ - so a zh-hant interface was drawn with Simplified forms, and a
Traditional face could not win on a machine that also had a Simplified one.
The list is now two, picked by the script the locale in force asks for: Hant, or a Taiwan, Hong Kong
or Macao region. A bare zh stays Simplified, as the interface's own locale mapping resolves it.
`asks_traditional_chinese` normalises case and separators, and `chinese_families_for` is pure, so the
choice is testable without installed fonts. The Han order and the Japanese and Korean lists are
untouched.
Verified: cargo fmt --all --check clean, cargo clippy -p effectcraft-text --all-targets -- -D warnings
clean, cargo test -p effectcraft-text 54 tests pass.
Numpad * always added a composition marker. As in After Effects, it now
adds a layer marker to the selected layers at the current time, and a
composition marker only when no layer is selected.
Right-clicking a layer offered no way to mark it. The layer context menu
now has After Effects' Markers submenu with Add Marker (layer.addMarker,
as Layer ▸ Markers ▸ Add Marker), which marks the right-clicked layer, or
the selection it belongs to, at the current time. The submenu title reuses
the menu catalogs' "Markers" translation in the panel catalogs.
The viewer's Rotation tool wrote start rotation + (atan2 angle now - atan2 angle at
press), so the value jumped a full revolution back whenever the pointer crossed
atan2's ±180° seam (left of the anchor) and could never pass 360°. Sum the
per-frame angle change, wrapped to (-180°, 180°], instead: the value keeps
growing across revolutions in both directions (1x +0.0°, -1x -90.0°…), Shift
snaps the accumulated value to 45°, other selected layers follow, and one drag
is still one undo step. Non-finite angles turn by nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AuxChannels::crypto_rank_planes resolves a layer's (ID, coverage) planes once; crypto_ranks keeps its signature on top of it. The CPU effect and the GPU effect's aux image (effectcraft_effects::crypto_aux_image, rayon) index the planes per pixel. Output is bit-identical.