Commit Graph

1312 Commits

Author SHA1 Message Date
vangel76 a8de199aca #783: Object Matte fidelity plan; gaps and parity notes updated
Problem: Object Matte had no After Effects comparison, and none can be made
on this Linux machine (no After Effects). The gap notes still listed the
viewer tool, background job and disk cache as missing.

Fix: docs/fidelity/object-matte.md states plainly that nothing is measured
yet and pins down the comparison for when an After Effects 26.2+ reference
is available: four original or openly licensed clips (the synthetic moving
disk from the engine tests, the disk with a jump, two objects with a box
prompt, one CC0/CC BY real clip), the prompt and settings, where reference
mattes stay (gitignored), and the metrics: per-frame IoU against After
Effects (headline), IoU against ground truth, boundary distance, soft-edge
error, temporal stability, drift and time per frame, plus a proposed pass
bar left to the maintainers. Linked from docs/fidelity/README.md.
docs/gaps.md (#14) and docs/target-app-parity.md now list what landed and
leave the comparison as the one remaining item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:07:22 +02:00
vangel76 3466538d0e #783: Object Matte mattes persist in the disk cache
Problem: computed Object Matte segmentations lived only in the in-memory
cache, so every restart or memory eviction segmented the shot again (After
Effects 26.5 keeps Object Matte results in its disk cache).

Fix: the persistent disk cache gets a third entry kind, Matte (folder
`mattes`, LZ4-compressed FrameSeg bytes, same checksum, atomic writes, LRU
eviction under the one size limit, purged with the rest). Entries are keyed by
the segmentation's chain key and scale; chain keys already hash the Input Key
(the layer's source, timing, masks and effects above), the prompt, the mode and
the model, so any change simply misses. The effects crate gets a SegStore
trait behind its memory cache: Chain::ensure reads from it on a memory miss
and writes what it computes. The engine installs the session's DiskCache as
the store (held weakly, so switching the disk cache off drops it). Roto Brush
segmentations go through the same chain and are persisted too. Frozen mattes
already persist in the project (the Frozen parameter).

Tests: engine tests_roto::object_matte_mattes_persist_in_the_disk_cache (a
second session on the same folder and project gets all tracked frames back
without being able to read a single input frame, renders identically, and a
moved prompt finds nothing); render disk_cache
segmentations_round_trip_and_reopen (round trip, reopen, damaged file is a
miss, size limit evicts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:06:33 +02:00
vangel76 560767dfe7 #783: Object Matte tracks in the background and freezes
Problem: Object Matte computed its frames only on demand while rendering, so
playing a shot stalled on every untracked frame, there was no progress or way
to stop, and nothing kept the mattes once computed.

Fix: Object Matte runs through Roto Brush's job machinery (one job slot,
cancellable with roto.cancel, a worker thread on the desktop, a Web Worker in
the browser, never the UI thread). objectMatte.propagate computes the frames
around the reference frame (clipped to the layer) outward, filling the same
content-keyed segmentation cache the renderer reads; the engine's sync now
queues an instance for background tracking when its frames or prompt change.
objectMatte.freeze stores the final mattes in a new hidden Frozen parameter
(Roto Brush's FrozenCache format) under a key of the input, prompt, span,
model and matte settings; renders use them without segmenting while the key
matches; a frozen prompt is refused until objectMatte.unfreeze. Freeze is one
undo step and is dropped if the prompt changed meanwhile.
objectMatte.status reports progress, frozen state and computed frames. Effect
Controls shows Freeze / Unfreeze and Track / Stop with a status line (progress
banner). The frontend now polls Roto Brush / Object Matte jobs on their own:
before, a finished job (and so a Freeze result) was only picked up while a
Warp Stabilizer job was active. Job progress is set before the worker starts.

Tests: engine tests_roto::object_matte_background_job_cancel_and_freeze
(queued by the edit, returns at once with progress, cancel keeps a partial
result, completion computes all 25 frames, frozen renders recompute nothing,
unfreeze recomputes, undo restores); effects object_matte job_frames and
frozen-matte tests (stored pattern used, key mismatch and damaged data
ignored); ui-egui ui_object_matte Effect Controls Freeze end to end.
docs/agents.md: Object Matte section; docs/effects.md regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:02:50 +02:00
vangel76 0c695cff77 #783: Object Matte tool in the Composition viewer
Problem: Object Matte could only be applied through the objectMatte.apply
command; After Effects 26.2 picks the object directly in the viewer (click it,
or drag a box around it).

Fix: an Object Matte tool (with Roto Brush & Refine Edge in the Alt+W slot,
icon drawn in code). In the Composition viewer a click sets a point prompt and
a drag a box prompt (its centre the point) on the footage, solid or comp layer
under the pointer, converted to layer pixels through the layer's transform.
The layer's Object Matte (the selected one, else its last) is re-prompted;
Shift adds another. Everything goes through objectMatte.apply, which gained
`effect` to re-prompt an instance (point, box or no box, reference frame; the
reach kept unless given), so each prompt is one undo step; the effect is then
selected, so its point controls show in the viewer. Hover draws a crosshair
and the outline of the layer that would get the matte (a live matte preview
would need a segmentation per pointer move). Automation ids:
viewer.objectMatte.crosshair / .target / .box, tools.ObjectMatte.

Tests: ui-egui ui_object_matte (hover, click, drag box re-prompts the same
effect, Shift-click adds one, undo steps); engine
tests_roto::object_matte_reprompt_updates_the_instance. Checked with the
headless snapshot example (hover crosshair, box drag isolating a disk).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:50:01 +02:00
vangel76 4fc7b52351 #783: Object Matte passes its box to the model as a box prompt
Problem: with a trained model installed, Object Matte's optional box reached
MobileSAM only as background points along the box's edges (an approximation),
although the model's prompt encoder takes a box prompt directly.

Fix: a new Roto Brush stroke kind, Box (two opposite corners), that
stroke_prompt turns into the model prompt's bbox (scaled to the matte grid,
corners in any order, degenerate or non-finite boxes ignored). The graph cut
never rasterizes it. Object Matte emits it instead of the edge strokes when a
model segments; the classical fallback keeps the edge strokes. A prior matte's
box no longer replaces a box the user drew on that frame.

Tests: effects object_matte::the_box_reaches_the_model_as_a_box_prompt (a
stand-in model sees the box, at full and half resolution, and no edge points);
track roto::tests::box_stroke_is_the_model_box_prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:38:10 +02:00
vangel76 c21a93e45e #783: app.capabilities counts Object Matte (307 registered effects)
Problem: the hand-maintained effect count in app.capabilities still said 306 after Object Matte was registered.
Fix: bump it to 307.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:33:17 +02:00
vangel76 953fbbc730 #783: Object Matte effect from a point / box prompt, tracked
Problem: After Effects 26.2's Object Matte (pick an object with a click,
optionally a box, get a matte that follows it) had no counterpart; only
stroke-driven Roto Brush existed.

Fix: an Object Matte effect (Effect > Matte, ec.matte.objectmatte) turns
the Object Point (and optional box) on a Reference Frame into Roto Brush
strokes and segments them with Roto Brush's engine, content-keyed cache
and the model chosen in Settings > Roto Brush (MobileSAM, classical graph
cut without it). Track Object carries the matte frame by frame up to 600
frames each way; otherwise every frame is segmented on its own. Feather,
Contrast, Shift Edge, Invert and a Matte view. The engine keeps its Input
Key current so upstream edits recompute it. objectMatte.apply creates one
from agents / the CLI. Unusable prompts pass the layer through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:32:10 +02: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
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
BFlat 2ec87e921f
Merge pull request #675 from storytold/claude/community-pr-review-lwtrlr
Community batch: pointer-wrap test texture deltas, device-lost dialog
2026-10-11 06:55:07 +07:00
Claude f3fe25fd67
Import: test that an animated GIF imported from disk plays (#651)
Animated GIFs came in as one still frame in 0.7.0; bfbdadd3 (after that
release) probes them as movies. This end-to-end test imports a GIF file
through the wired session into a comp and checks the layer shows each of
its frames at its time, the path the viewer and exports share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 23:50:08 +00:00
Claude 8e14c00eaf
Project: footage saved with a relative path is found next to the project file (#652)
file.import stores a path as given, so footage imported by the CLI or an
agent as `clip.mp4` was saved relative, and opening the project looked it
up from the working directory. The CLI, started in the project's folder,
found it; the desktop app, started from the Finder, Explorer or a launcher
in another folder, showed and rendered neither its picture nor its sound,
while the Project panel still listed the saved duration.

On open, a relative footage path that doesn't exist from the working
directory but does next to the project file is resolved there (After
Effects also looks for footage relative to the project). Paths that exist
as they are, and absolute ones, are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 23:41:43 +00:00
Claude cb37c2511c
App: a lost graphics device tells the user why the window closes and where the recovery copy is (#591)
Ported from #673: a native error dialog names the auto-saved recovery copy before the window
closes; agent runs through the control channel skip it. The UI calls Hooks::device_lost once.

Co-Authored-By: Dennis M. Francis <44986763+didpublishing@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 23:36:52 +00:00
Claude 2aefe3e644
Effects: CC Smear and CC Tiler on text and shape layers reach past the content's box (#659)
A text or shape layer's effect bounds are comp-sized (as in After Effects),
but its buffer surrounds only its content, and each effect grows the buffer
itself when it needs room. CC Smear and CC Tiler never did, so a smear was
cut off where the glyphs end and tiles filled only the text's box.

CC Tiler now covers the whole layer rectangle; CC Smear grows by how far it
moves pixels (Reach x From->To), at most to the layer bounds, so footage
layers, whose buffer is their bounds already, are unchanged. CPU and GPU
alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 23:35:47 +00:00
Claude 7fd1d09088
Merge pull request #671 from vbasky/fix/pointer-wrap-textures-delta 2026-10-10 23:31:17 +00:00
Claude d247b98b95
Viewer: dragging a layer no longer piles up frame renders until memory runs out (#647)
Every viewer request spawns a job on the frame pool. A pool thread blocked
inside a render (waiting on a parallel loop's join) runs other pool work
meanwhile, rayon's injected jobs included: the spawn of the next request
started a whole frame render on top of the waiting one. Dragging makes a
new revision, and a new viewer frame, every UI frame, so renders nested
without bound on every thread, each holding its buffers (a large image's
full-resolution copies), until all memory was used. Holding the layer still
makes no new revisions, which is why only moving it did this.

A spawn now drains the queue, and one that lands inside a render on its
thread returns at once; the render below takes the queued jobs when it is
done. Each pool thread renders one frame at a time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 23:29:44 +00:00
BFlat 21681e9590
Merge pull request #674 from storytold/claude/community-pr-review-lwtrlr
Issue fixes: GPU device loss, motion-path vertex tools, rename focus, macOS sheet dialogs, MCP file confinement, desktop shortcut
2026-10-11 06:25:56 +07:00
Vikram Bhaskaran da53c828dd
Credit Vikram Bhaskaran by real name and display name. 2026-10-11 10:20:10 +11:00
Vikram Bhaskaran e7735f0875
Tests: pointer-wrap frames drain egui's texture deltas instead of dropping them.
The Scrub helper and the real-properties test discarded FullOutput, so the
first pass' font-atlas texture delta panicked on drop in debug builds —
which is how the FreeBSD CI gate failed. Consume the delta the way the
rest of the crate's egui tests do.
2026-10-11 10:20:10 +11:00
skylar taylor-barrick 3dba472943 UI.9: share docking transactions and preserve viewer lifecycle
Preserve legacy selection repair, modifier routing, viewer lifecycle and workspace persistence through the shared docking integration. Qualify the complete workspace gate by refreshing the 18 stale Audio Spectrum hashes for the existing linear renderer, with an independent synthetic-tone regression.
2026-10-10 17:14:12 -06:00
Claude 1ac09c7fa3
Issue fixes: #591 GPU allocation and device loss, #553 motion-path vertex tools, #488 rename focus, #511 macOS sheet dialogs, #500 MCP file confinement, #343 desktop shortcut 2026-10-10 23:06:04 +00:00
BFlat ca89a975c7
Merge pull request #668 from storytold/claude/community-pr-review-lwtrlr
Community batch: font folders, empty-Timeline drop, AE auto-Bezier tangents, clippy 1.95
2026-10-11 06:05:39 +07:00
Claude 8fb4281632
MCP: --automation-read-root / --automation-write-root confine the agent's file access (#500)
`effectcraft-cli mcp` had no way to keep an agent's file access to a folder;
the flags were unknown options. Both now take a folder (repeatable, as in
PhotoCraft): with either given, files are read only under a read or write root
and written only under a write root (none: no writes). Defaults are unchanged.

automation::confine::Confinement checks every engine command the backend runs
(execute_command, command_run, open/save_project, each engine.batch step), in
both headless and bridge mode, before it runs: the file parameters of the
commands that read or write files must be absolute paths inside the roots
(symbolic links resolved through the longest existing ancestor, `..` refused),
as must any other absolute path in a command's parameters. Commands that write
where they aren't told are checked against the target they would write: Save /
Increment and Save the open project's file, renderQueue.render every queued
output (renderQueue.list). Scripts, Execute File, Reveal in Finder, Open Recent
by index, `$N` file-path references in a batch, render_frame/screenshot paths
outside a write root and the bridge's UI-driving tools are refused while
confined. docs/mcp.md describes it.

Test: a confined headless server opens and saves inside its roots and is refused
outside them, for a relative path, `..`, a batch step, a `$N` path, Save to a
read-only root's project, the Render Queue's default output there, a symbolic
link out of the write root, and scripts; the unconfined server still writes
anywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:54:57 +00:00
Claude aaf23aa5f8
macOS: File menu dialogs open as sheets, so a drag over the window can't abort the app (#511)
The file dialogs were blocking rfd::FileDialog calls made inside eframe's frame,
that is inside winit's event handler. On macOS the dialog runs a nested modal
event loop there; a file dragged over the window while it was open reached
winit's handler again, which refuses re-entry with a panic across the
Objective-C boundary, aborting the app (Import with Cmd+I, then a drag).

Hooks gains file_dialog_async: a host that sets it shows a command's file dialog
(Import, Open, Save As, Save Frame As, presets, guides, templates, Collect Files,
Relink...) without blocking, and the command runs with the answer on a later
frame (menus::dialog_answered, drained at the start of each frame); a cancelled
dialog runs nothing. The macOS app implements it with rfd::AsyncFileDialog, a
sheet on the window answered when it closes, outside the frame. Other platforms
keep the blocking dialogs (winit queues their re-entrant events), as do the few
panel buttons that pick a file in place (Settings paths, shortcut import/export,
effect file pickers).

Test: with a non-blocking hook, Save As and Save Frame As return at once without
the blocking dialog, the save happens when the answer arrives from another
thread, and a cancelled dialog writes nothing. The macOS sheet itself needs a
manual check (Cmd+I, drag a file over the window, pick or cancel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:54:56 +00:00
Claude d6894249fa
Inline editors: the whole name is selected only as a rename opens; arrows then move the caret (#488)
widgets::keep_focus (Project panel rename and comment, Render Queue, Essential
Graphics and Timeline fields) selected the whole text whenever it saw its field
without the keyboard focus, not just on the frame the editor opened. Anything
that took the focus for a frame (an input method, another handler) then
re-selected the name, so the arrow keys seemed to do nothing and Backspace erased
the whole name, as reported on Linux. The editor now remembers the pass it was
last shown: it selects all only when it opens and otherwise just takes the
keyboard back with the caret where it was.

The harness does not reproduce the report's Linux behaviour itself (arrows reach
the field there); the tests cover the path that did: a unit test drops the focus
for a frame and checks the caret survives (and that reopening selects all), and a
kittest renames a Project item and a Timeline layer with Left / Right / Up /
Down / End and Backspace, editing part of each name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:54:56 +00:00
Claude 6a3d5c6703
Viewer: the Pen, Add, Delete and Convert Vertex tools edit motion paths, with pen pointers (#553)
Motion-path hits were only handled for the Selection tool, so the Pen slot's
tools did nothing on a layer's motion path, and every Pen slot tool showed the
same crosshair. As in After Effects, on a motion path:
- Add Vertex (and the Pen) add a Position keyframe at the clicked frame dot,
  with the path's value there (prop.addKey), so the path keeps its shape;
- Delete Vertex removes the clicked keyframe (keys.select + keys.delete);
- Convert Vertex switches it between Linear and Auto Bezier spatial
  interpolation (keys.interpolation {spatial}).
motion_path now reports its frame dots for the hit test. Over the viewer the Pen
slot tools draw their own pen glyph (Pen, Add, Delete, Convert, Mask Feather) at
the pointer in place of the system cursor, and the Pen shows Add Vertex's pen over
a mask, shape or motion path it would add a point to.

Test: ui_viewer clicks a two-key motion path with each tool and checks the added
key, its spatial interpolation both ways, its removal, and the pointer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:54:56 +00:00
Claude 3c063b5075
GPU: a failed allocation reported as an internal error, or a lost device, no longer crashes the app (#591)
On Adreno (Windows ARM64) a failed vkAllocateMemory surfaces as an internal
ERROR_INITIALIZATION_FAILED, not out-of-memory. Gpu::within_memory only scoped
OutOfMemory, and wgpu's DeviceExt::create_buffer_init maps the new buffer at
creation and panics ("Failed to get mapped range") when the allocation failed,
taking down a frame thread. Buffers with initial contents are now created unmapped
and filled through queue.write_buffer (context::buffer_init), so a failed
allocation is only an invalid buffer and an error for the caller's scope, and
within_memory scopes Internal errors too, discarding the frame for the CPU path
as it does for out-of-memory.

The device was then lost, and egui-wgpu panicked on the main thread staging the
next frame's index buffer on the dead device. Once the host reports the
presentation device lost, the app draws nothing more (no mesh to stage), auto-saves
unsaved changes, keeps the crash-recovery marker so the next launch offers them,
and closes its window.

Tests: a device with a small buffer limit gets an over-limit and an unaligned
buffer_init without a panic and sees the error in its scope; the UI harness
reports a device loss and checks the next frame has no shapes, closes the window
and wrote the auto-save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:54:56 +00:00
Claude d6efb795e4
Keyframes: a straight auto-Bezier segment interpolates its chord exactly (#650 follow-up)
Auto-Bezier end keys now carry handles along the path, so a straight two-key move went through
the arc-length table and landed a rounding error off its keys' midpoint (80.00000000000001).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:52:36 +00:00
Claude 1c7fcccef2
Packaging: the Windows installer offers a desktop shortcut, on by default (#343)
The MSI only installed a Start Menu shortcut. The wizard's folder page now has a
"Create a desktop shortcut" box (DESKTOPSHORTCUT, 1 by default; DESKTOPSHORTCUT=0
for silent installs) that adds an EffectCraft shortcut to the Public desktop. The
choice is recorded under HKLM\Software\EffectCraft so an upgrade keeps it, and an
upgrade from a release before this option adds none. test-msi.ps1 checks the
shortcut is created by default, kept by upgrades, removed on uninstall and absent
with DESKTOPSHORTCUT=0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:30:58 +00:00
Claude 9d689b1a61
Community follow-ups: dedup font files from overlapping folders (#665); empty-Timeline drop also accepts compositions, rustfmt (#662)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:29:04 +00:00
Claude 0b91b465df
Merge pull request #650 from KattKnight/auto-bezier-spatial-tangents
# Conflicts:
#	crates/expr/src/tests.rs
2026-10-10 22:28:40 +00:00
Claude 44e3fe5f6e
Merge pull request #662 from Ni-zav/fix/649-empty-timeline-project-drop-s20261010T2145Z-e7a2 2026-10-10 22:28:36 +00:00
Claude 808513155e
Merge pull request #665 from johnstockertutorial-afk/autocut-font-dirs 2026-10-10 22:28:35 +00:00
Claude c9695a49e6
Merge pull request #646 from tylerjenningsw/fix/clippy-1.95-lints 2026-10-10 22:28:35 +00:00
BFlat d024a16cf0
Merge pull request #667 from storytold/claude/community-pr-review-lwtrlr
Fix community-reported issues: Light theme contrast, macOS Sync with System, Timeline eyedropper, DX12 kernel fallback
2026-10-11 05:21:54 +07:00
Johnstocker b2d8abdd1c fonts: extra font folders from EFFECTCRAFT_FONT_DIRS
A tool can hand EffectCraft its own font library (a path list) without
installing the fonts system-wide; the folders are scanned like the
system ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:10:45 +07:00
Claude e615b06256
Merge branch 'fix/f6': community issue fixes
# Conflicts:
#	crates/gpu/src/tests.rs
2026-10-10 21:59:26 +00:00
Nigel Ihza Firdausy 2925d9605a fix(timeline): ignore non-media data items dropped onto empty timeline 2026-10-11 04:57:32 +07:00
Nigel Ihza Firdausy a9f4d92d5a test(timeline): avoid overlapping harness borrows in multi-drop regression 2026-10-11 04:55:57 +07:00
Nigel Ihza Firdausy bd906d3ee9 fix(timeline): create composition when footage is dropped into an empty timeline (#649) 2026-10-11 04:52:24 +07:00
BFlat 62ec101a41
Merge pull request #655 from storytold/claude/community-pr-review-lwtrlr
Fix community-reported issues: multi-layer edits, stacked-panel tabs, 32 bpc blend modes, and more
2026-10-11 04:49:01 +07:00
Claude 9495810ff5
Tests: the near-plane check tests for the flip, and loopOut compares within float tolerance (#572, #456 follow-up)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 21:48:52 +00:00
Claude ef96419bec
Appearance: the Light theme repaints Effect Controls headers, Timeline wells and the expression field (#644)
Effect Controls painted effect headers with fixed dark fills (#2a2a2a,
#2f3a52 when active) under the theme's text colour, so on Light the effect
names were dark on dark (1.1:1). The Timeline kept the selected layer's
time-graph row, the A/V and switch wells, the time navigator, the work area
bar and the expression field on fixed dark fills too.

Those surfaces are now theme tokens (fx_header, fx_header_active,
tl_graph_selected, switch_well, tl_nav_bar, tl_work_area_bar, expr_bg,
expr_text) with the dark values unchanged and light ones for Light. The
expression editor's syntax colours come from the theme as well: on the
light field a darker set keeps every category, enabled/disabled
expressions, matched brackets and line numbers at 4.5:1 or better
(contrast test, with a negative control showing the dark set fails there).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 21:29:46 +00:00
Claude 82788b7a68
Multi-layer edits: only a real drag offsets the other layers; the Timeline stopwatch stays per property (#355, #491 follow-up)
A typed value committed by clicking elsewhere could be applied as an
offset, because dragged_id() is set on the press that commits it; a scrub
now needs a decided drag. In the Timeline, clicking a stopwatch or the
keyframe button acts on that layer's property only, as in After Effects
(the Properties panel and keyboard shortcuts act on every selected layer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 21:28:25 +00:00
Claude 3f03e025ac
Merge branch 'fix/f7': community issue fixes 2026-10-10 21:27:44 +00:00
Claude 657f34d40b
Layers: with several layers selected, value edits, stopwatches and viewer Scale / Rotate change them all (#355, #491)
With several layers selected, After Effects applies a property edit to the same property of
every selected layer; we changed only the layer whose row was edited (the Properties panel
showed only the first selected layer), so a stopwatch switched on one layer and later moves
of the others had nothing to keyframe (#355), and scaling or rotating a selection changed
one layer (#491).

- Engine: `prop.set`, `prop.toggleAnimation` and `prop.toggleKey` take `selected: true`:
  the change also goes to the same property (by match path, `transform/scale`,
  `effects/<effect>#n/<param>`) of every other selected, unlocked layer whose value has the
  same kind, in one undo step (a scrub keeps folding into it through its `merge` key). With
  `offset: true` (a scrub) each layer changes by the same amount from its own value; a typed
  value sets them all. Position with Separate Dimensions is handled per layer.
- The Timeline, Properties and Effect Controls panels pass `selected` (and `offset` while a
  value is dragged). The Properties panel heads "(N) Selected Objects" (zh-CN / zh-TW / ja /
  uk rows).
- Viewer: a bounding-box handle drag scales every selected layer by the same ratio about its
  own anchor point; the Rotation tool turns them all by the same angle (one undo step).

Tests: engine test (stopwatch, scrub offset, typed value, keyframe button, one undo step,
unselected layers untouched); kittests: the Properties panel with three layers selected
(header, stopwatch on all three undone in one step, a scrub moving all three by the same
amount) and a handle drag scaling two selected layers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 21:23:36 +00:00
Claude 03598229ba
Viewer: 3D layer outlines and the axis gizmo clip at the camera instead of drawing long lines (#572)
The viewer projected a 3D layer's box, its axis gizmo and its Extended Viewer outline
through the camera with a plain perspective divide. A point at or behind the camera
(w ≤ 0) flips through infinity, so a layer turned or moved so that part of it reaches past
the camera drew long lines across the viewer, and its handles and hit area flipped too.

- `layer_quad` (handles, hit tests, marquee) is None for a layer reaching past the near
  plane, so nothing is placed or picked on a flipped quad.
- `layer_outline` clips the box to the part in front of the near plane (Sutherland-Hodgman
  in homogeneous coordinates); the selection outline and the Extended Viewer use it.
- The 3D axis gizmo's arrows are clipped the same way (`project_segment`).

Tests: a box crossing the near plane keeps only its front part, one wholly behind draws
nothing; gizmo segments are cut at the plane and never flip.

Note: the reporter's exact setup (ellipse, X/Y Rotation, Orientation) wasn't reproduced
in a headless probe with the default camera, where the layer stays in front of it; this
fixes the projection fault that draws such lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 21:23:23 +00:00