Commit Graph

1005 Commits

Author SHA1 Message Date
tylerjenningsw 4aea350f2a Packaging: the installer's Repair puts back a damaged effectcraft-cli.exe (#466)
Cause: Repair used REINSTALLMODE=ecmus. effectcraft-cli.exe has no version
resource, so Windows Installer treats it as unversioned: a changed copy
with a later write time counts as a user edit and is kept, and "c" only
checks files that carry an MSI checksum, which these don't.

Fix: Repair uses "amus", which puts back every file whatever its version
or date.

Tests: test-msi.ps1's stand-in CLI is unversioned like the real one; the
test damages it in place and repairs with the REINSTALLMODE read from
installer-ui.wxs (no longer msiexec /fa after deleting it), then compares
contents. It fails with ecmus (the damaged file stays) and passes now.
2026-10-09 14:49:19 -04:00
tylerjenningsw 462913a483 Packaging: upgrading a custom-folder install from 0.6.0 or earlier keeps its folder (#466)
Cause: the folder search read only the App Paths "Path" value, which this
branch introduced. 0.6.0 and earlier could already be installed elsewhere
with INSTALLFOLDER=..., but have no Path value, so an upgrade went to
Program Files while the wizard said it would stay in the same folder.

Fix: when there is no Path value, a second search reads the App Paths
default value (the full path of effectcraft.exe, written by every
release). A "file" registry search with no FileSearch inside yields the
folder of that file.

Tests: test-msi.ps1 installs a legacy package (the same .wxs without the
Path value) into a custom folder, then upgrades it without naming a
folder; it fails on the previous .wxs (the upgrade lands in Program
Files) and passes now. A further upgrade checks the Path value route.
2026-10-09 14:45:47 -04:00
tylerjenningsw 184df34875 CI: the MSI test reads Apps entries one value at a time (#466)
Get-ItemProperty throws on a malformed Uninstall entry, which aborted the
upgrade check on a real Windows 10 machine. Failures now print their message.
2026-10-09 14:11:53 -04:00
tylerjenningsw 2412173b7e Contributors: tylerjenningsw's display name is tylerjenningsw 2026-10-09 14:05:49 -04:00
tylerjenningsw 6c58eea643 Contributors: credit tylerjenningsw as Tyler Jennings 2026-10-09 14:05:18 -04:00
tylerjenningsw 5d58fd4029 Docs: releasing.md describes the MSI install folder and its test (#466) 2026-10-09 14:00:46 -04:00
tylerjenningsw 7dc2b7a381 README Downloads: the Windows installer lets you pick the folder (#466) 2026-10-09 14:00:29 -04:00
tylerjenningsw f2d335b138 CI: the MSI is install-tested with a custom folder, upgrade, repair and uninstall (#466)
test-msi.ps1 builds the MSI around stub binaries, validates it (ICE) and,
silently: installs into a folder with spaces and a non-ASCII name, upgrades
without naming a folder (it must stay there, one entry in Apps), repairs a
deleted file, uninstalls (files and App Paths gone), and installs to the
Program Files default. Run by a windows-latest job in packaging-lint.yml.
2026-10-09 14:00:09 -04:00
tylerjenningsw 9ceda6a8f5 CI: packaging lint checks every Windows .wxs file (#466) 2026-10-09 13:59:46 -04:00
tylerjenningsw b32965f209 Packaging: the Windows installer asks where to install EffectCraft (#466)
A fresh install opens on a destination page (Program Files by default;
Change... browses, with Up and New folder), then a Ready page that shows
the folder before anything is written. An upgrade goes straight to the
Ready page with the installed copy's folder and can't move it; opening the
MSI again offers Repair or Remove. Cancel, finish, files-in-use and error
dialogs are authored too. Silent installs (/qn) show none of it.

The dialogs (installer-ui.wxs) are original, built from Windows Installer's
own controls and events, not the WixUI library, whose MS-RL licence is not
on the allowed list. package.ps1 builds both .wxs files.
2026-10-09 13:58:35 -04:00
tylerjenningsw 8b8a57780f Packaging: Windows upgrades and repairs install into the existing folder (#466)
The MSI records its folder as the App Paths "Path" value of effectcraft.exe
and reads it back with a registry search, so an upgrade, a repair or a
silent reinstall goes where the installed copy already is. A folder passed
on the command line (INSTALLFOLDER=...) still wins, and INSTALLFOLDER is
Secure so a folder chosen in the UI reaches the elevated install. v0.6.0 and
earlier have no Path value and upgrade in Program Files, where they are.
2026-10-09 13:51:44 -04:00
BFlat 4b7bd135ec
Merge pull request #449 from storytold/fix/410-411-412-exr-ocio
EXR beauty layer, Interpret Footage Preserve RGB, OCIO v2 transforms for Blender configs (#412, #411, #410)
2026-10-10 00:27:39 +07:00
bflatastic 492fa6de0a Effects: OCIO custom configs read Blender's v2 transforms and warn (#410)
Cause: the config reader didn't know ColorSpaceTransform or
ExponentWithLinearTransform and passed colours through them silently.
Blender's Linear Rec.709 -> sRGB therefore stayed in its E-white XYZ
reference space (the blue cast) without the sRGB curve. Display
colorspaces, view transforms, v2 views, looks, AllocationTransform,
BuiltinTransform and .spi1d / .spimtx files were missing too, and
nothing told the user.

Fix (from the OCIO v2 documentation and the CLF "moncurve", not OCIO's
source): ExponentWithLinear, ColorSpace, Look, DisplayView, Allocation
and Builtin transforms (identity, ACES AP0/AP1 matrices, ACEScct /
ACEScc, the sRGB / P3 / Rec.1886 / gamma / PQ displays). RangeTransform
now clamps and takes one-sided ranges. Display colorspaces meet the
scene reference through the default view transform. Added v2 views,
view looks, the looks section and .spi1d / .spi3d / .spimtx files. A
conversion is built once as a flat list of steps (Config::path /
display_path) for both the CPU effect and the GPU colour program (new
moncurve and clamp ops and the ACEScc curve; a program over the
kernel's 255 ops renders on the CPU). Named-transform resolution is
limited to 16 levels and 4096 steps, and YAML nesting to 64 levels, so
hostile or looping configs stop. Transforms that are still unsupported
(Grading Primary / Tone, ACES output, HLG…), unreadable configs and
unknown spaces are now reported: a warning under the effect in Effect
Controls, and the new effect.warning query.

Tests: ocio_config::blender_like_config_converts_linear_rec709_to_srgb
(CST, display colorspaces, v1 / v2 views, allocation, looks, builtin,
range, loops), moncurve_matches_srgb_and_inverts, builtin_styles,
yaml_nesting_is_bounded, ocio::spi_files_parse,
tests_effects::ocio_effect_warns_about_unsupported_config_transforms,
gpu tests_fx_lut::ocio_custom_config_v2 (GPU = CPU).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:57:12 +07:00
bflatastic d6de68b20d Import: Interpret Footage ▸ Preserve RGB keeps the file's values (#411)
Cause: float footage (OpenEXR, float TIFF) was always sRGB-encoded on
decode, and Interpret Footage could not turn that off, so an OCIO effect
on the layer converted values that were already encoded.

Fix: After Effects' Interpret Footage ▸ Color ▸ Preserve RGB
(Footage::preserve_rgb; file.interpretFootage {"preserveRgb": true}; a
checkbox in the Interpret Footage form; Remember / Apply Interpretation
carry it). The decoder keeps float values linear, and the working-space
conversion from the colour profile and Interpret As Linear Light don't
apply, so the file's values enter the composition unconverted. The media
cache key includes the flag, so both interpretations decode separately.

Tests: pool::preserve_rgb_reads_float_exr_values_unconverted,
tests_color::preserve_rgb_footage_is_not_converted (managed, linearized
and not), tests_proxy::guess_alpha_invert_and_linear_light (command,
render, Apply Interpretation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:57:12 +07:00
bflatastic 698ade94ac Import: layered EXRs show the beauty pass, never Cryptomatte (#412)
Cause: a multi-layer OpenEXR without an unnamed RGB layer showed its
first colour layer unless one was named combined / beauty / rgba. A
Blender compositor File Output names the beauty pass "Image", and its
Cryptomatte layers (lowercase r, g, b, a) sort first, so the picture was
CryptoMaterial00.

Fix: the picture is the unnamed RGB layer, else the beauty pass by the
last part of its name (Combined, Beauty, Image, RGBA, RGB, Color), else
the first colour layer that isn't a data pass (Cryptomatte, depth, mist,
normals, vectors, positions, IDs). A layer without R, G and B shows its
own first channel as grey. After Effects shows a file's main RGBA and
leaves the other layers to EXtractoR, which is unchanged.

Tests: exr_channels::layered_exr_shows_the_beauty_pass_not_cryptomatte
(the reported Blender layout, ViewLayer.Combined, no beauty name,
data-only files).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:57:12 +07:00
BFlat 176cddcdad
Merge pull request #444 from storytold/fix/415-416-417-text-fidelity
Text and viewer fidelity: glyph seams, overlay fringes, text raster scale, viewer minification (#415, #416, #390, #417)
2026-10-09 23:56:18 +07:00
bflatastic 9d85d4f116 Viewer: Auto resolution follows the magnification; minified frames are averaged (#417)
Cause: Auto dropped to Half below 75 % magnification, so text between
50 % and 75 % was rendered with fewer pixels than the screen shows and
looked soft. Below 100 % the frame texture was minified with a plain
bilinear sampler and no mip levels (Full at 46 % skipped most frame
pixels), so edges stair-stepped.

Fix:
- Auto renders the pixels the magnification needs, as in After Effects:
  the coarsest whole factor that still gives every screen pixel a
  rendered one (Full above 50 %, Half down to 33.3 %, Third down to
  25 %, Quarter below).
- GPU frames: the display texture carries a full mip chain, each level
  the 2 x 2 mean of the one above (display.wgsl `mip`, same bind group
  layout as `display`), sampled trilinearly with Viewer Zoom Quality
  More Accurate (Faster keeps the nearest pixel of the full-size frame).
- CPU frames (egui-wgpu textures have no mips): uploaded averaged down
  by the whole factor that leaves about one texel per screen pixel, the
  same box average as the texture-limit fit (#201), re-uploaded when the
  magnification calls for another factor; drawn with texture coordinates
  that keep each texel over its pixels when the size isn't a multiple.
  Show Channel / exposure textures follow.

Tests: gpu tests::display_frames_are_mipmapped (level count and level 1
= 2 x 2 means of level 0), viewer_tools
auto_resolution_follows_the_magnification (60 % was Half) and
minified_frames_keep_their_texels_in_place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:17:33 +07:00
bflatastic 94e4afe8de Text: text layers are always rasterised at their on-screen scale (#390)
Text is vector in After Effects and stays sharp at any Scale. Here a
text layer was drawn at its font size and the bitmap resampled by Scale
unless Continuously Rasterize was on, so text grown with Scale went soft.
This reworks #200 (reverted in 29c4d3a for kinetic_typography).

Why kinetic_typography failed: its time remap key at 1.9 s sits on
frame 23 (1.917 s), so main-comp 0.3 s shows the precomp at 1.6026 s,
but the test compared it with the precomp rendered at 1.6 s. The moving,
motion-blurred title already differed by 0.89 of the 1.0 mean-difference
budget on main (0.987 in all); with continuous rasterisation the "Sub"
line, whose expression scale is 84.00 % at 1.6 s and 84.105 % at
1.6026 s, falls on either side of the 2^-1/4 quarter-octave step and was
drawn at 0.84 at one time and at 1.0 at the other, pushing the total to
1.075.

Fix: raster_scale treats text layers as always continuously rasterised
(shape layers and vector footage still follow the switch), and rounds
the quarter-octave step up past rounding noise only: an unscaled layer
at a fractional position measured its scale as 1 + 1e-15 and was
rasterised at 2^1/4, softening unscaled text. The QA test now compares
the remapped precomp with the precomp at the time the remap really
shows (read back from the property): difference 0.63, all of it the
title's motion blur, the Sub line identical.

Tests: tests_collapse::text_scaled_up_stays_sharp_without_the_switch
(from #200: 10 px text at 800 % matches 80 px text),
tests_collapse::unscaled_text_rasterises_at_the_output_scale (fails
without the noise tolerance), automation qa kinetic_typography.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:17:32 +07:00
bflatastic 36ef45077a Styles: overlays keep anti-aliased edges clean (#416)
Cause: the interior styles were blended over the layer body with the
content's coverage folded into their opacity. On an edge pixel at 50 %
coverage a Gradient or Color Overlay went over the half-transparent body
as a half-transparent layer, so the result kept a quarter of the layer's
own fill and its alpha rose to 0.75: a light rim (the white fill
showing through) on styled text.

Fix: blend_clipped composites the interior styles (Gradient Overlay,
Color Overlay, Satin, Inner Glow, Inner Shadow and the inside of
Stroke) onto the layer within its shape: the body is divided by the
content coverage, blended with the style at its own opacity, and
multiplied back, so the alpha is kept and an edge pixel mixes the style
with the layer's colour inside the shape only, as a clipped layer does.
Fill Opacity and Blend Interior Styles as Group behave as before.

Tests: tests_styles::overlays_leave_antialiased_edges_clean (a masked
square with half-covered edge columns; Color Overlay, Gradient Overlay
and an inside Stroke now match a plain layer of the overlay colour;
Color Overlay's edge was 0.41 red against 0.23 before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:17:32 +07:00
bflatastic 88e2e6470a Text: touching glyphs join without a seam (#415)
Cause: each glyph's fill was rasterised and composited on its own, so
where two glyphs share an edge (the crossbars of "ff") two half-covered
pixels composited to 0.75 coverage instead of 1: a thin light seam,
where After Effects draws the line as one outline.

Fix: consecutive fill passes of the same opaque colour, without Blur,
under Normal Inter-Character Blending are rasterised as one coverage
mask (the glyphs' outlines in layer space, wound the same way so
overlaps add up under NonZero). Characters of other colours, translucent
or blurred ones, strokes and the other blending modes are drawn one by
one as before.

Tests: text::tests::touching_glyphs_join_without_a_seam (two glyphs
sharing an edge mid-pixel: 0.75 before, 1.0 now; opposite windings and
overlaps stay filled; other colours and Multiply still composite one by
one).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:17:32 +07:00
BFlat 2a460986ee
Merge pull request #445 from storytold/fix/398-tr-lookup
i18n: interface translations are looked up by hash (#398 follow-up)
2026-10-09 23:16:12 +07:00
bflatastic ea5dfae95f i18n: interface translations are looked up by hash, not by scanning the catalog (#398)
tr() scanned the whole catalog (about 380 rows) for every string a panel
draws, every frame, in Chinese. Each catalog is now indexed once, on
first use, in a HashMap keyed by the English source; English and
languages without rows still skip the lookup. The catalogs already have
no duplicate sources (catalogs_are_unique_and_keep_the_english_shape),
so the index finds the same rows.

Test: the i18n tests (pass-through, tr_args, catalog shape).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:15:54 +07:00
BFlat 4fad3e7af4
Merge pull request #436 from storytold/fix/393-370-timeline-ux
Timeline and viewer: resize cursors, time display toggle, pick whip, expression field, Tab, property menu (#393, #370, #362, #363, #371, #361, #407)
2026-10-09 23:10:56 +07:00
bflatastic 79bf5a6b59 Timeline: property names have the property menu (#407)
Right-clicking a property's name in the Timeline did nothing, so
Position's dimensions could be separated only from the Graph Editor.

As in After Effects, it now opens the property's menu: Reset, Edit
Value…, Separate Dimensions (Position and, once separated, X / Y / Z
Position, checked), Add / Remove Expression and Add Property to
Essential Graphics. Only Position separates, as in After Effects
(Scale stays one property).

`prop.reset` without `default` used to leave the value as it was: it
now gives the property its own default (an effect parameter's, or a
Transform property's, shared with Layer ▸ Transform ▸ Reset), which
also makes the Properties panel's Reset reset the values. `prop.set
{layer, prop}` without `value` opens the value dialog, like
`keys.set`.

Tests: timeline_property_names_have_the_property_menu
(ui_blank_context_menus), reset_gives_a_property_its_default (engine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
bflatastic 9c9e954ee2 Timeline: Tab moves to the next value field (#361)
Tab in a value being typed (hot text) dropped the keyboard: egui moved
the focus to the next widget, and the inline editors asked for the
focus again every frame, which also reset what keys a field keeps for
itself.

As in After Effects, Tab now commits the value and types in the next
value field of the same panel (the next dimension, then the next
property's), Shift+Tab the previous one. The hot-text fields of each
panel register in drawing order; the dock tags each panel's Ui so the
order stays within it. `keep_focus` asks for the focus only when the
field doesn't have it.

Tests: tab_commits_a_value_and_moves_to_the_next (ui_inline_edit:
Timeline and Effect Controls).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
bflatastic 0171eb1e2e Timeline: the expression field grows while typing and drags taller (#371)
The inline expression field took its height from the saved
expression, so lines typed into it ran over the rows below until the
expression was committed.

Its height now comes from the text being typed (up to 8 lines, as
before), and its bottom edge drags it to any height in whole lines, as
in After Effects; the dragged height is kept per property in the
Timeline state (`expr_lines`).

Tests: the_expression_field_grows_while_editing_and_drags_taller
(ui_inline_edit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
bflatastic 6fadae2818 Timeline: the pick whip picks the dimension it is dropped on (#363)
Dropping the expression pick whip on one of a property's values (the X
of Position) referenced the whole property. As in After Effects, a
drop on one value now picks that dimension: `transform.position[0]`;
a drop on the name still picks the whole property.

`prop.pickWhip` takes `target.dimension`.

The rest of #363 is unchanged: inserting at the cursor landed with
#284, and After Effects' property pick whip in the Parent & Link
column is left for later.

Tests: pick_whip_picks_one_dimension_of_the_target (engine),
the_expression_pick_whip_picks_the_dimension_it_is_dropped_on
(ui_inline_edit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
bflatastic 639342a5e7 Timeline: the pick whip scrolls the layers at their edges (#362)
A pick whip dragged to the top or bottom of the Timeline's layers left
them where they were, so a property out of view couldn't be picked.

While a pick whip (expression or parent) is held within a row of the
top or bottom edge of the layers, or past it, the layers scroll that
way, faster the deeper, as in After Effects.

Tests: the_pick_whip_scrolls_the_timeline_at_its_edges
(ui_inline_edit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
bflatastic 0b2ef8e584 Timeline: Ctrl/Cmd-click on the time display toggles Timecode / Frames (#370)
The Time Display Style could only be changed in Project Settings. As in
After Effects, Ctrl-click (Cmd-click on macOS) on the current-time
display of the Timeline or the Composition panel now toggles it between
Timecode and Frames (Feet + Frames goes to Timecode). It runs
`file.projectSettings {timeDisplay}`, so it is one undo step.

Under the Timeline's display the other style shows now (frames under
timecode, timecode under frames), as in After Effects; it always showed
frames.

Tests: ctrl_click_on_the_time_display_toggles_timecode_and_frames
(ui_inline_edit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
bflatastic fcf3bb5dca Viewer: resize cursors follow each handle and the layer's rotation (#393)
Every bounding-box handle in the Composition viewer showed the
north-west / south-east resize cursor, so two corners and all four
edges showed the wrong direction.

Each handle now takes the cursor along the direction it scales, worked
out from the on-screen box: a corner points between its two edges (the
box's diagonal whatever its proportions), an edge handle across its
edge. The cursors turn with the layer's rotation, as in After Effects.

Tests: handle_cursors_follow_each_handle_and_the_layer_rotation
(ui_viewer: a wide box at 0°, 90° and 45°).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
BFlat 609ef9cacb
Merge pull request #429 from storytold/fix/413-414-export-cache
Export: Render Queue jobs keep a layer cache and render on one compositor (#413, #414)
2026-10-09 22:51:10 +07:00
Brandon Thomas e3873ae92f CI: pushes to main no longer cancel the FreeBSD / Windows ARM64 run in progress
Merges land faster than the hour-long FreeBSD job, so every main run was
cancelled and main went unverified. Only PR pushes cancel now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:37:20 -04:00
bflatastic 8c4a3cbfe0 Export: a Render Queue job renders every frame on the project's renderer (#414)
Cause: pipeline::Cx rendered with Backend::Auto, which picks the CPU or the
GPU compositor for each top-level frame from measured frame times
(render::AutoPick: warm-up on both sides, a re-probe every 48 frames). The
two compositors differ slightly, so frames came out differently depending
on which side the timings favoured in that run, and two GPU exports of the
same frames differed.

Fix: the job renders every frame on the project's renderer, as After
Effects renders a job with one renderer: Backend::Gpu with Mercury GPU
Acceleration (frames the GPU declines still render on the CPU, as they do
in every GPU render), Backend::Cpu with Mercury Software Only. The viewer
and previews keep Auto. Docs (architecture, parity, web) say so.

Tests: render_settings::a_job_renders_on_one_compositor exports ten frames
with a stand-in GPU compositor that keeps Auto history and draws solid
green: with Mercury GPU Acceleration every frame is green and two runs are
identical (with Auto the warm-up sent frames to the CPU: it fails), with
Software Only the stand-in is never asked. The web worker GPU job test's
comment no longer describes Auto.

Diagnosis by @pat-the-wizard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:26:17 +07:00
bflatastic 1ccf7cb910 Export: Render Queue jobs keep a layer cache, so static layers render once (#413)
Cause: pipeline::Cx::render_at built the job's Renderer without a layer
cache, so every layer of every frame rendered from scratch (source, masks,
effects, styles), and the GPU compositor, whose uploads are keyed by the
cached buffers, uploaded every layer again on every frame.

Fix: each job (Cx) owns a LayerCache, the cache type the viewer uses, with
its default budget (1 GiB, the default Settings > Memory & CPU layer cache),
shared by the job's parallel frames and dropped with the job. With deferred
GPU readbacks it holds the accelerator's miss gate, as the job workers'
caches do. Cx::renderer builds the job's renderer (options, accelerator,
cache) for render_at and the test.

Tests: pipeline::tests::layers_render_once_per_job exports ten frames of a
solid animated only in Transform: the cache misses of the first frame are
the only ones (it fails without the cache: no layer is cached), and every
frame equals the frame rendered without the cache.

Diagnosis by @pat-the-wizard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:26:16 +07:00
BFlat f08d090cd8
Merge pull request #433 from storytold/fix/402-no-row-separators
UI: no parameter row separators, as in After Effects (#402 follow-up)
2026-10-09 22:14:05 +07:00
bflatastic 82f0cd9668 UI: Effect Controls and the Timeline have no parameter row separators, as in After Effects (#402)
#402 made Effect Controls' rows compact and aligned, as After Effects
draws them, and also added thin separator lines between parameter rows
in Effect Controls and the Timeline, with a Settings > Appearance > Show
Parameter Row Separators preference. After Effects has neither, so the
lines, the preference and the Tokens field go; the compact rows, the
alignment and the automation ids stay.

Saved settings with the old key load as before (unknown keys are
ignored).

Test: ui_parameter_spacing keeps the row geometry, hit target and
nesting tests; the separator tests go with the feature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:13:31 +07:00
Brandon Thomas 9144eef269 Merge remote-tracking branch 'origin/main' into integrate5
# Conflicts:
#	docs/preferences.md
2026-10-09 10:33:41 -04:00
Brandon Thomas f1a31fb9eb i18n: drop the Ukrainian row for the removed Ease Presets panel (#401 follow-up)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:33:17 -04:00
BFlat be676f90ec
Merge pull request #401 from dmatviichuk/i18n/ukrainian
i18n: add Ukrainian (uk) menu translation
2026-10-09 21:32:04 +07:00
Brandon Thomas 4047ea94ea Merge pull request #398 from FeiLiuEM/ui-i18n
i18n: translate the interface below the menu bar (panels, dialogs, tooltips)

# Conflicts:
#	crates/ui-egui/src/lib.rs
#	docs/preferences.md
2026-10-09 10:22:32 -04:00
bflatastic 07e163db64 i18n: Ukrainian drops the Ease Presets panel entry, which became a ScriptUI panel (#395, #404)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:22:15 +07:00
Brandon Thomas a0c6a6e96e Merge pull request #401 from dmatviichuk/i18n/ukrainian
i18n: add Ukrainian (uk) menu translation

# Conflicts:
#	crates/text/src/fonts.rs
#	docs/preferences.md
2026-10-09 10:22:14 -04:00
Dmytro Matviichuk a787d077d4 i18n: add Ukrainian menu translation (#395) 2026-10-09 21:22:07 +07:00
Brandon Thomas ea785355cd GPU: note why Posterize's division correction is an fma (#405 follow-up)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:22:01 -04:00
Brandon Thomas 9b852b62af Merge pull request #405 from sanyamsingh001/fix/gpu-threshold-parity
Fix GPU Posterize threshold parity

# Conflicts:
#	contributors/people.toml
2026-10-09 10:21:53 -04:00
Brandon Thomas 9b62a4a833 Merge pull request #402 from cdinhoo/codex/parameter-row-spacing
UI: Compact and align effect parameter rows
2026-10-09 10:21:43 -04:00
BFlat 7885b67aa9
Merge pull request #404 from storytold/feat/plugins-ae-parity
Plug-ins and extensions as in After Effects: ScriptUI panels reopen, Ease Presets becomes a bundled panel, AE's Snapping options
2026-10-09 21:21:25 +07:00
bflatastic da165724e9 Plug-ins: Ease Presets moves out of core into a bundled ScriptUI panel
The Ease Presets panel (#254) is modelled on a third-party panel, so,
as with third-party tools in After Effects, it isn't core any more: it
is `extensions/scriptui-panels/Ease Presets.jsx`, a ScriptUI panel
bundled as an optional extension (Window ▸ Ease Presets.jsx), written
only against the public scripting API. Clicking a preset eases every
pair of neighbouring selected keyframes in one undo step (per
dimension, along the motion path for spatial properties, the keys'
other sides as they play); the fields edit the working curve, which the
panel draws; From Keys, Save, Rename and Delete as before. The twelve
built-in curves are unchanged.

Scripting API fixes it needed (After Effects parity):
- `app.settings` lasts between runs (`script_settings.json` in the
  settings folder; `script.settings.get` / `script.settings.save`).
- Per-key methods (`setTemporalEaseAtKey`, `setInterpolationTypeAtKey`,
  `removeKey`, the continuous / auto-Bezier / roving setters) no longer
  change the user's key selection (they selected the key they edited).
- `keyInTemporalEase` / `keyOutTemporalEase` report linear and
  auto-Bezier sides as they play, `keyIn/OutSpatialTangent` the
  tangents the motion path plays.
- `selectedProperties` includes the properties of selected keyframes.
- `isInterpolationTypeValid` is false for Linear / Bezier on values that
  only hold.

Removed from core: Window ▸ Ease Presets, the `keys.easePreset.*`
commands, `engine::ease_presets` and the `ease_presets.json` store,
`keyframe::EaseCurve` (nothing else used it) and the egui panel. User
presets saved by the v0.6.0 panel move into the panel's app.settings
section the first time settings load (the old file is left as is).
Docs: plugins.md (bundled extensions), parity.md, gaps.md G8, agents.md.

Test: app_settings_last_between_runs,
per_key_methods_keep_the_key_selection_and_report_eases_as_they_play,
spatial_tangents_are_reported_as_the_path_plays (each fails before),
the_ease_presets_panel_eases_every_selected_pair_in_one_undo_step,
a_typed_curve_scales_to_each_dimension_and_follows_motion_paths,
user_presets_save_rename_delete_and_persist,
presets_saved_by_the_old_core_panel_carry_over (script, through the
panel's controls), ease_presets_panel_eases_the_selected_keys_from_its_list
(UI: the panel docks and a click on a preset eases the keys).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:03:12 +07:00
bflatastic 9da33c157e Plug-ins: ScriptUI Panels folder scripts dock as panels and reopen, as in After Effects
EffectCraft's extension points compared with After Effects' (public
user guide and Scripting Guide) are now in docs/plugins.md, which
becomes "Plug-ins and extensions": scripts, ScriptUI panels and effect
plug-ins, with a short guide and an example panel for writing your own.

ScriptUI panels already came from `Scripts/ScriptUI Panels/` in the
settings folder, were listed at the bottom of the Window menu and
docked as tabs. What was missing: After Effects reopens the panels
that were open when it quit. Now the open panels are remembered in
`scriptui_panels.json` in the settings folder (a panel is added when
it opens and dropped when its tab or its script closes it; quitting
leaves the list), and `window.restoreScriptPanels` opens them again;
the desktop and web apps run it at start-up. A panel whose script is
gone or fails is reported and forgotten, and the others still open.
Where a panel was docked isn't kept, as workspace layouts aren't saved
between launches.

Gaps recorded in plugins.md and gaps.md G8: the Startup and Shutdown
script folders, keyboard shortcuts for scripts, ExtendScript's
`#include` / `#target` directives and `.jsxbin`.

Test: open_panels_open_again_at_the_next_launch (fails before: no
restore command), a_panel_dropped_in_the_folder_is_listed_and_opens_as_a_panel
(script), panels_open_at_quit_dock_again_at_launch (UI: the restored
panel docks with its controls; closing the tab forgets it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:03:12 +07:00
bflatastic 6ce58040fd Viewer: the Snapping menu has After Effects' two options
The Tools bar's Snapping options menu (#265) offered per-feature
toggles (Edges, Corners, Centers, Anchor Points, Mask and Shape Path
Points) that After Effects' Snapping menu doesn't have. It now has the
two After Effects has:

- Snap Edges Extended (as before).
- Snap to Features in Collapsed Compositions and Text Layers: the
  visible layers inside a precomp layer with Collapse Transformations
  on are snap targets too, mapped into the outer comp (points always,
  edge lines while the precomp layer is unrotated); a snap highlights
  the precomp layer. Nested collapsed precomps are followed eight deep.

Every layer feature snaps again (corners, edge midpoints, centre,
anchor point, mask and shape path points); the snapping engine itself
is unchanged. `view.snappingOptions` takes `edgesExtended` and
`collapsedFeatures` (and `toggle` of either); the old keys are refused.

Test: collapsed_precomp_features_snap (fails before: no such option),
snapping_options_command_sets_and_toggles (the per-feature keys are
refused), snapping_options_pick_the_targets (AE behaviour kept: edges
extended or not, hidden layers, guides only), and the Snapping menu UI
test (only the two options are listed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:03:12 +07:00