Commit Graph

1352 Commits

Author SHA1 Message Date
Brandon Thomas 25dcdcd2d8 Merge pull request #740 from vangel76/fix-303-mesh-warp-grid
#303: Mesh Warp's grid can be dragged in the Composition viewer
2026-10-12 03:03:16 -04:00
Brandon Thomas 8176c86043 Merge pull request #739 from vangel76
# Conflicts:
#	docs/gaps.md
2026-10-12 03:03:10 -04:00
Brandon Thomas 27e095e60e Merge pull request #702 from vbasky/fix/681-brightness-contrast
Match After Effects non-legacy Brightness & Contrast
2026-10-12 03:03:02 -04:00
Brandon Thomas e98788690c Merge pull request #737 from vangel76/gap-copy-frame-clipboard
#730: Composition ▸ Copy Frame to Clipboard
2026-10-12 03:02:59 -04:00
Brandon Thomas e5ff011296 Merge pull request #735 from vangel76/gap-stl-import
#729: import STL 3D models (binary and ASCII)
2026-10-12 03:02:58 -04:00
Brandon Thomas 4aa2c6ba8b Merge pull request #734 from vangel76/fix-678-psd-drop-dialog
#678: dropped Photoshop files show the Import Photoshop File dialog
2026-10-12 03:02:56 -04:00
Brandon Thomas 64b7088867 Merge pull request #733 from vangel76/fix-473-crypto-quoted-names
#473: Cryptomatte Selection keeps quoted names with commas whole
2026-10-12 03:02:55 -04:00
Brandon Thomas a4a117c1f7 Merge pull request #731 from vangel76/fix-710-ruler-frames
#710: Timeline ruler labels frames when Time Display is Frames
2026-10-12 03:02:54 -04:00
Brandon Thomas adaa8d65bb Merge pull request #728 from vangel76/contributors-vangel76
Contributors: add vangel76 (Valentin Klein)
2026-10-12 03:02:52 -04:00
Brandon Thomas 15d50597dd Merge pull request #716 from storytold/ci-speedup-nightly-platform-checks
CI: FreeBSD and Windows ARM64 nightly and on release, not per PR
2026-10-12 03:02:51 -04:00
Brandon Thomas 0ae711ba3a Merge pull request #713 from mjohnson7cd/fix/2081-rendering-hang
fix(gpu): cap GPU readback staging buffers to prevent memory exhaustion
2026-10-12 03:02:50 -04:00
Brandon Thomas 686280cc5f Merge pull request #711 from Ni-zav/fix/704-cylinder-axis-rotation-s20261011T035800Z-cf39
fix(effects): add XYZ rotation to CC Cylinder on CPU and GPU
2026-10-12 03:02:48 -04:00
Brandon Thomas 196dd80104 Merge pull request #709 from Ni-zav/fix/684-empty-viewer-project-drop-s20261011T035800Z-cf39
fix(viewer): create a composition when dropping footage onto an empty viewer
2026-10-12 03:02:47 -04:00
Brandon Thomas d7fe6d2e56 Merge pull request #705 from tylerjenningsw/enhance/custom-ui-scale
Settings: allow custom UI scale percentages (#682)
2026-10-12 03:02:46 -04:00
Brandon Thomas 4482ad9634 Merge pull request #703 from didpublishing/fix/windows-path-tests
Tests: six engine tests no longer assume the working directory's drive on Windows
2026-10-12 03:02:44 -04:00
Brandon Thomas 162a2e2e01 Merge pull request #699 from didpublishing/fix/596-gpu-information
#596: GPU Information names the adapter, or why the GPU compositor is off
2026-10-12 03:02:43 -04:00
Brandon Thomas b84b5f47ed Merge pull request #695 from vbasky/fix/535-pixel-motion-vectors
Pixel Motion Blur: synthesize from motion vectors, honour Vector Detail (#535)
2026-10-12 03:02:42 -04:00
Brandon Thomas 62dd3ae2ed Merge pull request #694 from tylerjenningsw/enhance/gradient-editor
Shapes: edit gradient color and opacity stops (#612)
2026-10-12 03:02:40 -04:00
Brandon Thomas edfa8ee51c Merge pull request #677 from vbasky/test/651-gif-preview
Tests: an imported animated GIF plays its frames (#651)
2026-10-12 03:02:39 -04:00
Brandon Thomas dff86891d0 Merge pull request #399 from FeiLiuEM/test/variable-font-axis
Text: the variable-font test picks an axis that moves the advance
2026-10-12 03:02:38 -04:00
Dylan M. Taylor 67434e9038
ci: refactor workflows to use storytold/craft-common-actions (#801)
* ci: refactor workflows to use reusable artcraft-common-actions

* ci: point reusable workflows to storytold/craft-common-actions

* ci: pin storytold/craft-common-actions to commit 27a978f and pass secrets explicitly

* ci: use @main (unpinned) for craft-common-actions with explanation comment
2026-10-11 11:51:43 -04:00
tylerjenningsw f4c5015b35 Settings: keep action buttons visible at high UI scales
At 200 % in a 1600x1000 window, Previous / Next / Cancel / OK were cut
off. Settings sized its pages from the window height less a fixed 170,
and the shared modal put everything, buttons included, in one more
scroll area, so a footer that didn't fit scrolled out of the dialog.

The modal helper gets a modal_fit variant without that scroll area: it
hands its body the height left under the title. Settings lays out its
buttons first, from the bottom up, and gives the page list and the page
what they leave; both scroll on their own and the buttons stay put. The
pages are a clipped child, so a wide page can't widen the dialog under
the buttons. Other dialogs keep modal as it was.

Tests: settings_buttons_stay_in_view_at_200_percent opens Settings at
200 % in 1600x1000 and checks that each button is inside the window and
painted unclipped (fails before this change: the scroll area clipped
them at the bottom). It scrolls the page to UI Scale without moving
them, then types 150 and clicks Cancel (200 % again, nothing saved) and
OK (150 % saved). The UI Scale test no longer needs a larger window and
clicks Cancel at 200 %.
2026-10-11 11:48:07 -04:00
tylerjenningsw 3df7e8f77c Shapes: a closed colour picker leaves the next Escape to the dialog (#612)
The picker marked every pass it drew, including the one it closed in, so
for one pass after closing the dialog Escape handler still stepped aside
and a quick second Escape did nothing. The mark is now set only while the
picker stays open and cleared in the pass it closes (OK, Cancel, Escape or
a click outside); the picker also consumes the Escape it handles.

Test: the_escape_after_the_picker_closes_cancels_the_editor presses Escape
to close the picker and again in the very next pass, with no settling
frames: the editor cancels. It fails on the previous marking.
2026-10-11 08:29:54 -04:00
vangel76 c733277263 #303: Mesh Warp's grid can be dragged in the Composition viewer
Problem: Mesh Warp keeps its Distortion Mesh in a hidden text parameter,
and the viewer only draws handles for point parameters (Bezier Warp's),
so with Mesh Warp selected there was no grid and nothing to drag: the
effect could not warp anything from the UI.

Fix: with a Mesh Warp effect (or one of its parameters) selected and
layer controls shown, the viewer draws the (rows+1)x(columns+1) grid
over the layer in effect space, with a handle on every vertex
(automation id viewer.meshWarp.<fx>.<row>.<col>). Dragging a vertex
writes its offset to the `mesh` parameter through prop.set, one undo
step per drag; a mesh that does not match Rows/Columns starts from the
undistorted grid, as it renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:43:38 +02:00
vangel76 d58253cc5a #738: Edit ▸ Paste pastes SVG markup as a shape layer
Problem: After Effects 26.3 pastes copied vector artwork as shape layers. Edit ▸ Paste only
knew the app's own clipboard, so SVG markup on the system clipboard was ignored; turning SVG
into shapes needed an imported footage file and Create Shapes from Vector Layer.

Fix: a new `edit.pasteSvg {svg, name?, comp?}` command builds a shape layer from SVG text with
the same conversion as Create Shapes from Vector Layer (paths, fills, strokes, gradients, clip
masks), above the selected layer with the SVG canvas centred in the comp, as one undo step;
oversized, non-SVG or shapeless text is rejected. In the UI, Ctrl/Cmd+V (and the native Edit ▸
Paste) run it when the system clipboard holds SVG. Copying in the app already replaces the
system clipboard with a note, so SVG there is newer than the app's clipboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:43:18 +02:00
vangel76 ddcca91d50 #730: Composition ▸ Copy Frame to Clipboard
Problem: After Effects 26.3 copies the current composition frame to the
system clipboard; EffectCraft could only write it to a file (Save Frame As).

Fix: new command comp.copyFrame {comp?, time?, scale?} renders the frame as
Save Frame As ▸ File does and emits Event::CopyImage (pixels not
serialized); the egui frontend puts it on the clipboard after checking the
buffer size. In the Composition menu (translated) and the viewer's context
menu; drivable by id over the control channel / MCP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:33:45 +02:00
vangel76 5661d5111c #729: import STL 3D models (binary and ASCII)
Problem: After Effects 26 imports STL models besides glTF and OBJ; we
refused .stl files, and the Media Browser and import dialog did not list
them as models.

Fix: a pure-Rust STL reader in effectcraft-model (binary and ASCII,
detected from the contents; several ASCII solids and polygon loops;
VisCAM/SolidView and Materialise Magics facet colours as plain
materials; Z-up turned to the model's Y-up; faceted normals from the
winding). Truncated, empty or non-finite input is an error, never a
crash. .stl is now a model extension for import, probing, the Media
Browser, the import dialog and the system report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:32:11 +02:00
vangel76 f9db3afd34 #678: dropped Photoshop files show the Import Photoshop File dialog
Problem: dropping a PSD/PSB file on the window imported it straight away.
The drop handler always passed `importAs` (Settings > Import > Default Drag
Import As) to `file.import`, and the Import Photoshop File form only opens
when `importAs` is missing, so the dialog that File > Import > File...
shows never appeared for drops.

Fix: a drop that includes a Photoshop file leaves `importAs` out, so the
form asks (other drops keep passing the preference). For dropped files the
form's Import Kind starts at Default Drag Import As; File > Import keeps
starting at Composition. The engine already falls back to the preference
for `drag` imports without `importAs`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:28:18 +02:00
vangel76 3b7f1ace28 #473: Cryptomatte Selection keeps quoted names with commas whole
Problem: selection_hashes split the Selection on every comma before
stripping quotes, so a name containing a comma (Blender allows a material
called `Green, Glossy`) was cut into two names that match nothing, quoted
or not; only its `<hex>` hash selected it.

Fix: a small tokenizer splits at commas outside quotes. A name in double
or single quotes stays whole, a backslash takes the next character
literally (`Green\, Glossy`), and unquoted lists, wildcards and `<hex>`
hashes parse as before. The CPU effect and the GPU path share the function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:25:24 +02:00
vangel76 00383eed99 #710: Timeline ruler labels frames when Time Display is Frames
Problem: with the project's Time Display Style set to Frames, the
current-time display showed frame numbers but the Timeline ruler kept
labelling its ticks in seconds:frames (`00:15f`). The label format was
hard-coded and never read the project settings.

Fix: a `ruler_label` helper prints the frame number in Frames mode the
same way the current-time display does (`{:05}`, counted from Start
Numbering Frames At), and keeps seconds:frames for the other styles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:21:30 +02:00
vangel76 b47f02eaba Contributors: add vangel76 (Valentin Klein)
Credit me by real name and my public GitHub profile name in About > Contributors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:11:25 +02:00
Brandon Thomas a482981c57 CI: run FreeBSD and Windows ARM64 checks nightly and on release, not per PR
They ran on every push to main and on any PR touching Cargo.toml/Cargo.lock,
which is nearly every PR. See craftrules release/playbook.md §2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:03:00 -04:00
Mara Johnson c88d2b3a7f fix(render): prevent render hang and system lag (#2081) 2026-10-11 00:36:38 -04:00
Nigel Ihza Firdausy f179b2f2d2 docs(effects): update CC Cylinder parameter count 2026-10-11 11:08:10 +07:00
Nigel Ihza Firdausy 4c81cf04f2 fix(effects): use portable WGSL discriminant guard 2026-10-11 11:07:36 +07:00
Nigel Ihza Firdausy f29436580e fix(effects): implement XYZ axis rotation for CC Cylinder on CPU and GPU 2026-10-11 11:06:51 +07:00
Nigel Ihza Firdausy 31370e6144 fix(viewer): create a composition when footage is dropped on empty viewer 2026-10-11 11:02:26 +07:00
tylerjenningsw 42d8120147 Settings: allow custom UI scale percentages (#682)
Settings > Appearance > UI Scale offered only 75, 100, 125, 150, 175 and
200 %, so a size between 75 and 100 wasn't possible. It is now the shared
numeric field (Kind::Int(75, 200, "%")): type or drag any whole
percentage. The engine already stored the scale as a clamped integer and
the UI applies it with set_zoom_factor, so the range, the default, live
preview, Cancel and saved preferences are unchanged; the preset list
goes.

Numeric settings now apply a typed number once it is entered (Enter or
leaving the field), not at every keystroke: typing 85 used to apply the
partial 8, clamped to 75, and resize the whole interface before the 5.
Dragging still applies as it moves.

Tests: the UI Scale test types 85 a key at a time (nothing applies and
the field keeps the keyboard until Enter), Cancel, 50 and 300 clamp to
75 and 200, 90 with OK, and a new app with the saved preferences starts
at 90 %. The GPU Information transaction test types 125.
2026-10-10 23:47:27 -04:00
tylerjenningsw 5c1484b6e7 Shapes: the Gradient Editor uses the shared colour picker; Escape closes only the picker (#612)
The stop colour now uses widgets::srgb_color_button, the After Effects-style
picker every colour input opens (#638): hex field, HSB/RGB fields, OK and
Cancel, sRGB as the project stores colours. The stop keeps its alpha.

The dialog Escape handler only stepped aside for egui popups, and this
picker isn't one, so in a modal dialog Escape with the picker open closed
the dialog too. The picker now marks the pass it was open in
(color_picker::open_recently), and the handler leaves Escape to it: Escape
restores the colour the picker opened with and closes only the picker.

Tests: ui_gradient_editor clicks inside the picker's colour field over the
modal editor, types a hex code, closes the picker by clicking outside it
and saves the stop; a new test checks that Escape and the picker's Cancel
restore the stop's colour and keep the editor and its other edits. That
test fails without the Escape change. ui_color_hex still passes.
2026-10-10 23:43:40 -04:00
Vikram Bhaskaran a81c98d33c
Match After Effects non-legacy Brightness & Contrast.
The modern curve used g = 2^(2c), so Contrast 100 crushed 0.25 to about 8/255 while AE is about 40/255. Brightness is now a pin-black-and-white power toward white or black, then a milder S-curve g = 1 + 0.53 c, fitted to kasper9n's AE grey ramps. Legacy linear mode is unchanged. The GPU kernel matches the CPU.
2026-10-11 14:31:10 +11:00
Dennis M. Francis fe5c2b29b0 Tests: six engine tests no longer assume the working directory's drive on Windows
Six effectcraft-engine tests failed on a Windows machine whose working directory
and home folder are on different drives:

- rq_tests::add_configure_render and tests_stubs::{add_output_module_encodes_twice,
  pre_render_imports_and_replaces, post_render_actions_import_and_set_proxy,
  log_notify_and_overflow_settings} used "/tmp/..." output paths. On Windows that
  has no drive letter, so the render queue resolves it against the home folder's
  drive (C:) while std::path::absolute, which the tests compared with, uses the
  working directory's (D:). They only passed where both are on one drive, as on CI.
  They now build their outputs with tests_stubs::tmp_out, a path under the OS temp
  directory that is absolute on every platform and unchanged on Unix.
- footage_check::relative_footage_is_found_next_to_the_project compared strings:
  Windows joins the stored "media/clip.png" onto the project's folder and leaves
  mixed separators. It now compares the paths by components.

Test-only: the render queue stores the path it writes to, so the product is
consistent. effectcraft-engine --lib: 497 passed (6 failed before).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 20:26:20 -07:00
Dennis M. Francis a6945de659 #596: GPU Information names the adapter, or why the GPU compositor is off
Settings > Previews > GPU Information was fixed text ("Compositing: CPU, N
threads... Display: egui on wgpu."): it never named a GPU, even while the GPU
compositor was running, so a card looked undetected. The System Report's GPU
line only said whether the compositor started.

sysinfo::gpu_summary builds the graphics lines from the session: the compositor's
adapter, or the reason compositing is on the CPU, and the adapter the window
itself got. The dialog uses it.

This does not explain why a particular card isn't used for compositing; that
needs the reporter's System Report (Help > System Report).

Tests: engine gpu_summary_names_the_adapter_or_says_why_there_is_none and UI
gpu_information_names_the_adapter_and_why_the_gpu_is_off (fails with the old
text). The six engine tests that fail on this Windows machine (relative footage
and render-queue paths) fail on main too.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 19:33:06 -07:00
Vikram Bhaskaran 9ce7d12721
Pixel Motion Blur: synthesize the blur from motion vectors and honour Vector Detail (#535).
Time > Pixel Motion Blur fetched the layer at its shutter times and averaged the decoded
frames as they were, so fast motion rendered as doubled copies and the accepted Vector
Detail parameter changed nothing. Build a TimewarpPlan instead: every shutter instant is
decoded between the two source frames it falls between along a measured motion field — the
same block-matching and in-between synthesis Time > Timewarp uses, with a search radius
wide enough for full-frame displacements and the vector grid tightened from Vector Detail
0 (one vector per 32 px) to 100 (one per 2 px) — and the instants average into the blur.

CPU and GPU share the plan (fx_time renders it with its Timewarp step loop, declining to
the CPU when there is no host), so the existing GPU parity cases for the effect compare
the new path. Falling back to the old average is kept for layers without a time host.

A render integration test moves a textured block 12 px between source frames, renders
the straddling shutter, and asserts the output is neither the still nor the plain frame
overlay, lays trail coverage along the block's path, and changes with Vector Detail;
without the synthesis it fails at the overlay assertion.
2026-10-11 13:32:13 +11:00
tylerjenningsw ec1bc8cc99 Shapes: edit gradient color and opacity stops (#612)
The Timeline's "Edit Gradient…" on a gradient property (Gradient Fill /
Gradient Stroke ▸ Colors, Gradient Overlay) only painted its label. It now
opens a Gradient Editor (prop.editGradient): opacity stops above a preview
bar and colour stops below. Click to add a stop (it takes that track's
own value there), drag or type a Location to move it, set its opacity or
colour (egui's colour picker), Delete removes it (never the last of a
track, never while typing). The tracks stay in order while editing and the
selection follows its stop.

The editor works on a draft. OK sets the property once with prop.set at
the composition time it opened, snapped to the frame, with
timeBase: "comp" (offset and stretched layers get the key where it was
opened): one undo step, and nothing at all when the draft is unchanged, so
OK adds no keyframe. Cancel and Escape change nothing; Escape with the
picker open closes only the picker. On an animated gradient only the
keyframe at that time changes; when its stop counts no longer match a
neighbouring keyframe's (gradients only blend with equal counts) the
editor says so. The draft is saved in the typed form, so one colour stop
and each stop's RGBA survive.

prop.editGradient checks its target is a gradient property before the UI
opens anything, so agents get an error for a wrong one. New Gradient
color_at / opacity_at sample one track each (sample() multiplies opacity
in, which would apply it twice to a new stop).

Tests: ui_gradient_value (fails on main: the value had no automation id),
ui_gradient_editor (11), gradient_editor and keyframe unit tests.
2026-10-10 22:22:13 -04:00
skylar taylor-barrick 9a7d3e7699
Merge pull request #689 from skylartaylor/codex/ui-suite-integration-20261010
Share docking transactions and preserve viewer lifecycle
2026-10-10 20:10:35 -06:00
skylar taylor-barrick 83ae6218ef UI.9: retain shared docking across upstream workspace changes 2026-10-10 19:25:49 -06:00
Brandon Thomas f953cd2855
Release prep: 0.8.0 and a Linux riscv64 release build (#669)
* Release prep: bump to 0.8.0 and add a Linux riscv64 release build

- release.yml: new `linux-riscv64` job, ported from GridCraft. It cross-compiles on
  ubuntu-24.04 against riscv64 libraries from ports.ubuntu.com (the Linux job's X11/Wayland/GL
  -dev packages plus ALSA), ships effectcraft-<v>-linux-riscv64.tar.gz, and smoke-tests
  effectcraft-cli under qemu-riscv64. The draft-release job now needs it, so its tarball is
  uploaded and covered by SHA256SUMS.txt.
- packaging/linux/package.sh: cross-compiles when CROSS_ARCH/CROSS_TARGET/CROSS_COMPILE are set
  (cargo --target, the cross strip, the EMULATOR smoke test), as GridCraft's does.
- Version 0.7.0 -> 0.8.0 (cargo xtask version set), Cargo.lock refreshed.
- README and docs/releasing.md list the riscv64 tarball.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix FreeBSD CI: pointer_wrap test harness, wasm32 target, clippy as_chunks

- pointer_wrap tests: the harnesses dropped run_ui's FullOutput with the font atlas upload still
  in textures_delta, and epaint 0.36's drop check panics on that in debug builds. Every
  pointer_wrap test failed on every platform; FreeBSD is the only CI that runs them. Clear the
  delta, as the other UI tests do.
- freebsd.yml: install wasm32-unknown-unknown in the VM. Without it the host plug-in test's build
  of examples/plugins/posterize-bands failed (E0463, no std) and the test skipped silently; now
  it builds and runs.
- media/convert.rs: chunks_exact(2) -> as_chunks::<2>() (clippy chunks_exact_to_as_chunks,
  new in Rust 1.99); same pairs, same dropped remainder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:08:18 -04:00
Vikram Bhaskaran 983a2f0ebf
Tests: an imported animated GIF plays its frames in a wired session (#651).
Importing an animated GIF as a movie (bfbdadd) made it preview and export
again, but nothing pinned it: the item must probe as a GIF movie and its
rendered frames must follow the file's own per-frame delays, not hold one
still. Add a host integration test that imports a red/green/blue GIF and
asserts each third of its duration renders the matching colour, so a
regression to still-frame import is caught.
2026-10-11 11:26:26 +11:00
BFlat 4b58cd2eb7
Merge pull request #676 from storytold/claude/community-pr-review-lwtrlr
Issue fixes: drag render pile-up, CC Smear/Tiler past text bounds, relative footage paths, GIF playback test
2026-10-11 07:16:00 +07:00
Claude ceb116d6d2
Issue fixes: #647 nested frame renders while dragging, #659 CC Smear/Tiler past text bounds, #652 relative footage next to the project, #651 GIF playback test 2026-10-10 23:55:23 +00:00