Commit Graph
1050 Commits
Author SHA1 Message Date
Kristian Myram 9d33c200d5 #1743: protect render sources and refresh proxies safely 2026-10-24 23:00:46 +00:00
dotCooCoo d8e756dec5 Proxies: a composition proxy draws with Separate Fields, Invert Alpha, linear light and Frame Blending
For a precomp layer whose composition had a proxy, the renderer
decoded the proxy file with frame_with and drew it as decoded.
Interpret Footage > Proxy's Invert Alpha, Interpret As Linear Light
and Separate Fields had no effect on the render, and neither did the
layer's Frame Blending. A footage item's proxy already goes through
footage_frame and gets all of them.

The composition proxy now goes through footage_frame too, so it is
decoded the way a footage proxy is.
2026-10-24 15:20:57 -07:00
vangel76 3f41f5c026 Reuse the validated Warp Stabilizer plan on first render
Prepare the default plan through the effects cache after analysis instead of discarding it. Plan from the serialized analysis so the first rendered frame retains identical floating-point inputs and output. Test bitwise render equality and cache reuse.

Part of #2322
2026-10-25 00:14:57 +02:00
dotCooCoo 0cb05a601b Animation presets: effects saved by an earlier version are upgraded on apply, as in a project
Opening a project runs migrate::upgrade_instance on every effect, so an
instance saved before a parameter was renamed, moved into a twirl-down
group, added or had its popup reordered takes the current shape.
Apply Animation Preset added a preset's effects as saved, without that
upgrade. A Change to Color saved with its single Tolerance applied
with that old property and without the Tolerance group, so the effect
did not read the saved tolerance.

apply_preset now upgrades each preset effect before adding it, sized by
the target layer's effect bounds as upgrade_effects does. A lone effect
property saved at a renamed or moved parameter id
(effects/ec.color.changetocolor/tolerance) is rewritten to the current
id through migrate::current_param_id, so it is applied instead of being
listed in skipped.
2026-10-24 13:46:12 -07:00
dotCooCoo 7efbe7d85c Interpret Footage: the Footage panel, thumbnails and tracking show Cineon Settings
The Footage panel, the Project panel thumbnail and the native-size source
frames that tracking, mask tracking, scene detection and auto-trace read took
DPX and Cineon frames straight from the file, so they showed the log code
values while a composition showed the converted ones. Each now applies the
footage's Cineon Settings, as the composition renderer does.
2026-10-24 11:48:41 -07:00
vangel76andClaude Opus 5.5 c8dc0fca8c Merge upstream/main into gap-behaviors-presets
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:11:09 +02:00
vangel76andClaude Opus 5.5 5289b2e853 Merge upstream/main into fix-1083-beat-marker-audio-pause
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:02:37 +02:00
dotCooCoo 557c13b9cd Interpret Footage: the params docs list every key the commands read
execute_checked refuses any key a command's params doc does not list, so
MCP, the CLI and the control channel could not pass preserveRgb,
overrideStart, startTimecode or guessAlpha to file.interpretProxy. The
command reads all four, and the shared Interpret Footage form sends
preserveRgb for every proxy, and overrideStart and startTimecode for a
movie or sequence proxy. file.interpretProxy's doc now lists them. The
docs of file.interpretProxy and file.interpretFootage list premulColor
beside matteColor, which both commands read as the matte color. The
docs of file.interpretFootage, file.applyInterpretation and
file.reloadFootage list `item` beside `items`, which the three commands
already read.
2026-10-24 10:01:40 -07:00
dotCooCoo 9de2c5ee8a Interpret Footage: Cineon Settings convert DPX and Cineon log scans
DPX and Cineon footage showed its log code values as stored, with no
way to convert them. Interpret Footage now has the Cineon Settings that
After Effects' help page describes: Logarithmic Conversion, the
Converted Black and White Points, the 10 Bit Black and White Points,
Current Gamma, Highlight Rolloff and Units. With Logarithmic
Conversion on, the code values of each decoded frame are converted
from printing density to the Current Gamma between the converted
points, before Separate Fields, Frame Blending, Interpret As Linear
Light and the working space use them. With it off (the default), the
values stay as stored.

file.interpretFootage and file.interpretProxy take the settings as
parameters and report them. They return an error for out-of-range
values, and for Cineon parameters when none of the items is a DPX or
Cineon file. Apply Interpretation copies the settings between DPX and
Cineon files.
2026-10-24 08:37:15 -07:00
onlykshitijandClaude Opus 5.5 6e7cc34c9e Scripts: window.scriptPanel opens only ScriptUI panels
It fell back to any script, so a script outside the ScriptUI Panels folder opened as a panel and
was remembered, then reported missing and forgotten at the next launch (restore looks only for
panels). Review feedback from CodeRabbit on #2289.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:45:29 +05:30
dotCooCoo c2011fb11d Scripts: Recent Script Files keeps a script's full path, and same-named entries show their paths
A script run from a relative path is stored with its absolute path, so
choosing it again runs the same file after the working directory changes.
The web build keeps its file keys as they are (Services::full_path).

Two recent scripts, projects, footage files or presets with the same file
name are listed with their paths instead of the name alone.
2026-10-24 08:03:16 -07:00
onlykshitijandClaude Opus 5.5 de88e084e6 Scripts: doc comments for the functions this change touches
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:32:47 +05:30
onlykshitijandClaude Opus 5.5 c7dda713d9 Scripts: uninstall never deletes through a subfolder
A subfolder of a Scripts folder can be a link to another folder (an extension's own checkout).
`file.uninstallScript {"name": "Motion Kit/Animate.jsx"}` then deleted Animate.jsx in that
folder, outside the settings folder. Uninstall now refuses `Folder/File.jsx` names and removes
only scripts at the top of the Scripts folders, where Install puts them; listing and running
through a linked folder still work.

Regression test: uninstall_never_deletes_through_a_linked_subfolder (failed before the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:46:37 +05:30
dotCooCoo eb2a4820e8 Scripts: Recent Script Files lists the last ten scripts and Cmd+Alt+Shift+D reruns the newest
File ▸ Scripts gets a Recent Script Files submenu with the ten scripts run
last, newest first: script files run with Run Script File… or a command, and
the installed and bundled scripts of File ▸ Scripts. ScriptUI panels are not
listed. The list is kept in the settings. Each entry runs its script again
through `file.runScript {"recent": i}`, and Cmd+Alt+Shift+D runs the most
recent one; the key can be changed in Edit ▸ Keyboard Shortcuts. An entry
records whether it ran a file or an installed script, so a file named like a
bundled script runs again as that file.
2026-10-24 06:08:26 -07:00
onlykshitijandClaude Opus 5.5 bdcd2dc051 Scripts: subfolders of the Scripts and ScriptUI Panels folders become submenus
An extension that ships several panels no longer adds one Window menu entry per panel:
`Scripts/ScriptUI Panels/Motion Kit/Animate.jsx` shows as Window > Motion Kit > Animate.jsx,
and a subfolder of `Scripts/` is a submenu of File > Scripts. Top-level scripts are unchanged.

- Script names may be `Folder/File.jsx`: exactly one folder, each part a bare name (no `..`,
  `\`, NUL or empty part), never `ScriptUI Panels/...` under `Scripts/`.
- window.scriptPanel, file.runScript, file.uninstallScript, file.scripts.list and
  scriptui_panels.json take such names; a panel's tab is titled after its file.
- ConfigStore::list_dirs (DirConfig, MemoryConfig) lists the subfolders.
- menus::dynamic_nodes groups a folder's entries into a MenuNode::Submenu; the egui and native
  menus draw it with their submenu code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:05:59 +05:30
dotCooCoo efe7581f93 Scripts: the Startup and Shutdown folders run at launch and quit
Scripts in Scripts/Startup and Scripts/Shutdown in the settings folder
were never run. The desktop and web apps now run the Startup scripts at
launch, before a project opens, and the desktop app runs the Shutdown
scripts as it quits, after the project is closed. Each folder runs in
alphabetical order with case ignored, and files larger than 16 MiB are
not run. A Startup script that fails is reported in a toast and in the
log, a Shutdown script that fails is reported in the log, and the next
one still runs.

A Startup script that waits in a modal dialog goes on when the dialog
closes, while the next scripts run. A Shutdown script's dialogs close
as if canceled. With the settings folder on disk, each script runs as
its file, so #include, $.fileName and $.evalFile resolve against the
script's own folder.

The new commands file.runStartupScripts and file.runShutdownScripts
return the scripts that ran, those still waiting in a dialog, and those
that failed. Like script.run, both are refused in confined automation
sessions, in batches and in script steps, which now read one list of
script commands (RUNS_SCRIPTS). The list also holds window.scriptPanel
and window.restoreScriptPanels, which run ScriptUI panel scripts and
were not refused on main either. The apps still call
window.restoreScriptPanels at launch. plugins.md, scripting-parity.md,
gaps.md, target-app-parity.md and agents.md describe the folders. Each script
still runs in its own global scope, so a Startup script's variables are
not available to later scripts.
2026-10-24 02:44:05 -07:00
Dylan M. TaylorandClaude Sonnet 5.5 4e9fa51f12 sonar: method paths instead of closures (rust:S1612)
Poison recovery, OsStr::to_str and char predicates only; S4325 casts are
f64 to f32 and stay.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 08:17:16 +00:00
Christopher Linnett 048d6c4a25 Text: duplicate ends the open text edit and selects the copy (#2226)
A deselected layer never keeps an open text edit: duplicate now commits any open
edit through the same route as text.endEdit before the selection moves, from every
route (menu, shortcut, palette, MCP, script). The editor-held keyboard matches the
chord against each command's current binding, like Deselect All already did, so
rebound shortcuts keep working and keys a focused editor must never surrender stay
typing.
2026-10-24 14:35:45 +08:00
aiongg 9a9574baaa Merge #2016: Settings: list the languages alphabetically by their own name (@nikolainyegaard) 2026-10-24 02:12:11 +00:00
aiongg 0db7ddb089 Merge #2228: End an open text edit when Deselect All runs (#2096) (@ChristopherLinnett) 2026-10-24 02:10:16 +00:00
aiongg ad2f3a5e96 Merge #2184: Reload a numbered still as its own file (#2074) (@HEOJUNFO) 2026-10-24 01:12:00 +00:00
aiongg 34e2facd54 Merge #2177: engine: deleting a layer clears the references to it (#2153) (@Anubhav-Rai) 2026-10-24 01:11:56 +00:00
aiongg cce71f69fc Merge #1946: #1926: an edit no longer stamps every image-sequence frame for the disk cache (@ded-achtung) 2026-10-24 01:10:55 +00:00
aiongg 46302da794 Merge #2178: Reject malformed thumbnail text without crashing (#2135) (@Maokus) 2026-10-24 01:10:42 +00:00
aiongg b6c1cff444 Merge #1833: Tests: serialise disk-cache-opening tests with the roto tests (@iannitram) 2026-10-24 00:13:55 +00:00
aiongg d664e403ae Merge #2185: Project Settings: Frame Count, Start at 0 or Start at 1 (@marcoarnulfo) 2026-10-24 00:13:55 +00:00
Christopher Linnett a661f2ed60 Merge main into fix/issue-2096-deselect-during-text-edit
main moved crates/ui-egui/tests/ui_text_edit.rs to tests/it/ (one test binary per crate); this branch's test changes follow the move.
2026-10-24 07:59:29 +08:00
aiongg ac5fc076df Merge #2050: 3D: add a New 3D Primitive dialog to Layer > New (@tylerjenningsw) 2026-10-23 23:33:19 +00:00
aiongg bae27ebbe7 Merge #1841: Cryptomatte: pick the ID under a point into the Selection (effect.pickCryptomatte) (@MatejKozlovsky) 2026-10-23 23:33:10 +00:00
aiongg b7f8eb9019 Merge #2106: engine: a keyframe on the out point no longer warns (#2084) (@gmrmad1) 2026-10-23 23:21:33 +00:00
aiongg 4db5e01f69 Merge #2108: engine: resolve effect match-name paths in prop_ref (#2064) (@gmrmad1) 2026-10-23 23:21:33 +00:00
aiongg fefe4b8a91 Merge #1843: i18n: Georgian (ka) interface language (@devius) 2026-10-23 21:45:52 +00:00
aiongg 295d104e19 Merge #1699: Preview: replaced browser media gets a new disk-cache key (#1253) (@vbasky) 2026-10-23 21:36:58 +00:00
aiongg ce73d42c42 Merge #1929: Layer: replace a layer's source with Alt-drag and Ctrl+Alt+/ (#991) (@ki2pixel) 2026-10-23 21:26:51 +00:00
aiongg d0675be78b Merge #1850: Timeline: layer switches, labels and modes act on the whole selection (#1849) (@ki2pixel) 2026-10-23 21:26:33 +00:00
aiongg 52a0c6626d Merge #1931: Layer: Reveal Layer Source in Project from the context menus (#1809) (@ki2pixel) 2026-10-23 21:26:24 +00:00
aiongg 95c4dc6a28 Merge #2114: engine: split layers above the original by default (#2083) (@gmrmad1) 2026-10-23 21:26:10 +00:00
aiongg 7e49932caf GPU tests: pass on Mesa lavapipe (boundary flips, skip the Reshape case that crashes it); cargo fmt (#2239)
* cargo fmt

* GPU tests: llvmpipe on Vulkan gets the boundary-flip allowance; skip the Reshape boundary cases that crash lavapipe (#2238)

* GPU tests: the llvmpipe boundary-flip allowance is an opt-in of the two tests that need it
2026-10-23 14:16:37 -04:00
aiongg 89b8db7718 Footage, Lottie and models: resolve stored paths with craft-docpath's resolve_absolute (#1975)
* Footage: use resolve_absolute for stored paths

* Lottie and models: use resolve_absolute for linked files

* CI: release only with git dependencies from storytold

* Use the shared git-sources check and pin checkout in release.yml

* Resolve a model's side files through resolve_absolute; report C:\x as invalid

* CI: parse cargo config, including the legacy .cargo/config, in the git-sources check
2026-10-23 13:02:36 -04:00
aiongg 0173abde8c CI: dev-profile gate, one test binary per crate, nextest, line tables only (#2235)
* Dev profile: line tables only for workspace crates, no debug info for dependencies

* xtask ci: dev-profile gate, nextest, tests without examples, wasm --keep-going

* One integration-test binary per crate

* xtask ci: run effectcraft-gpu's shared-device tests in one cargo test process

* xtask ci: run every stage and report all failures

* xtask ci: name the stage type (clippy type_complexity)

* xtask ci: run every test pass and report all failures

* cargo fmt
2026-10-23 12:59:51 -04:00
vangel76andClaude Opus 5.5 eb1726fbb0 Merge main into rfx-text
Conflict in crates/text/src/layout.rs (line metrics): main's partition_point
slice from #2193 plus this branch's first-baseline maximum. rustfmt applied to
main's paragraph() signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:05:14 +02:00
Dylan M. Taylor f6ef1a7906 Use chrono instead of hand-rolled UTC conversion (#2023)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days / days_from_civil copies (log
timestamp, ISO metadata dates, Align Video to Data timestamp parsing, the
Numbers effect's date, the Home and Render Queue date columns, and the
benchmark report date) with chrono as a workspace dependency. Output formats
are unchanged and pinned by the existing tests. AGENTS.md now says time goes
through chrono.

Follow-up to storytold/cadcraft#1785

* Keep 5-digit years unprefixed; derive Numbers weekday from the same date

chrono's %Y writes a '+' before years past 9999, where the old code wrote
{y:04}. timestamp, iso_date, format_utc, utc_stamp and xtask's utc now write
the year with {:04} again and let chrono format the rest, so a far-future
clock keeps the plain zero-padded year.

date_from_days took its weekday from the raw input modulo while the
year/month/day fell back to the epoch for day counts outside chrono's range,
so an unrepresentable count printed a fallback date paired with the input's
weekday. The weekday now comes from the same resolved date, so both agree.

Tests cover the year-10000 timestamp and the out-of-range Numbers date.

* Keep the whole date in format_ago for five-digit years

format_ago cut format_utc to 10 bytes, so a year past 9999 lost its last
digit (10000-01-0). Take the date up to the space instead. Tested.

* Keep Numbers dates exact past chrono's range

date_from_days converted the day count by building a Unix timestamp and
calling DateTime::from_timestamp, which falls back to the epoch (and so
reads 1970-01-01, a Thursday) once the date leaves chrono's NaiveDate
range near year ±262143. Numbers' `value` slider is a plain scalar with
no range guard, so a keyframed count reaches that: Long Date of
100_000_000 showed "Thursday, January 1, 1970" instead of the real
"Monday, September 13, 275790" -- exactly the plausible unrelated-date
fallback the review rejects, and the derived weekday matched the stand-in
date, not the input.

Route it through the 400-year Gregorian cycle like pdfcraft 9d4cb3ca:
chrono converts the day within one cycle (num_days_from_ce of the rebased
rest, always in range) and the whole cycles are added back as years, in
i128 so i64::MIN/MAX can't overflow. chrono stays the only calendar
logic. The weekday still comes from the resolved date, so it always
agrees with it.

Tested: date_from_days_past_chrono_range pins the pre-chrono dates and
weekdays at/over chrono's bound on both sides of the 2000-01-01 epoch
(and that the extremes don't overflow); numbers_formats adds the same
through the public numbers_text entry (Long/Short Date). cargo test
-p effectcraft-effects, fmt --check and clippy -D warnings all clean.

* time: preserve out-of-range years instead of the epoch

`DateTime::from_timestamp(..).unwrap_or_default()` changed the existing output:
inputs the old hand-rolled civil-from-days converted (e.g. `10_000_000_000_000`
s → 318857-05-20 17:46:40) collapsed to the epoch. Restore the old conversion
domain with a cycle-based helper: reduce by whole 400-year Gregorian cycles
(146_097 days), convert the in-range remainder with chrono, and carry the year
back as i128 (saturating to the output type). Only the year leaves chrono's
range; month, day and time of day stay exact. chrono remains the only calendar
logic (NaiveDate::from_num_days_from_ce_opt); no new features.

Sites (UTC):
- crates/engine logging.rs `timestamp` (u64 ms): far-future clock keeps its year.
  10e12 s → "318857-05-20T17:46:40.000Z", u64::MAX ms → "584556019-04-03T14:25:51.615Z"
  (was "1970-01-01...").
- crates/engine media_browser.rs `iso_date` (u64): 10e12 s → "318857-05-20T17:46:40Z",
  u64::MAX → "584554051223-11-09T07:00:15Z".
- crates/ui-egui panels/home.rs `format_utc` (i64): 10e12 → "318857-05-20 17:46",
  i64::MIN → "-292277022657-01-27 08:29".
- crates/ui-egui panels/render_queue.rs `utc_stamp` (u64): 10e12 → "318857-05-20
  17:46:40", u64::MAX → "584554051223-11-09 07:00:15".
- xtask bench.rs `utc` (u64): 10e12 → "318857-05-20 17:46 UTC", u64::MAX →
  "584554051223-11-09 07:00 UTC"; added a test.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-23 11:29:09 -04:00
Christopher Linnett 0558c9427c fix: end an open text edit when Deselect All runs (#2096)
While a text layer was being edited, Edit > Deselect All and its shortcuts
selected every character (or did nothing) instead of ending the edit and
deselecting the layer.

- deselect_all now commits the open edit first, through the same route as
  text.endEdit: a created-but-empty type-tool layer is discarded, anything
  typed is kept, and no extra undo step is introduced.
- While the in-canvas editor holds the keyboard, the editor forwards the
  command's currently assigned shortcut (and any function-key chord) so
  Deselect All reaches the command from the comp; bare-letter bindings stay
  typing, and Select All still selects characters.
- The Project-panel focus redirects (invocation and menu enabled-ness) stand
  down while a text edit is open, so the menu and shortcut end the edit even
  when focus stayed on Project after Layer > New > Text.

Tests: three engine cases (commit + discard semantics), six UI kittest cases
(chords, F2, rebinding, menu enabled-ness, Project focus), RED at base.
2026-10-23 23:23:59 +08:00
heojunfoandClaude Opus 5.5 058fe9a005 Reload a numbered still as its own file (#2074)
Reload Footage probed every item the way File ▸ Import does by default,
so a still such as Photo_2.jpg with Photo_1.jpg beside it was grouped
into a run and measured from the run's first file. The still took
Photo_1's size. Sequence items still probe as a run; everything else
reads its one file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:21:35 +09:00
Marco Arnulfo 952b04322e Project Settings: Frame Count, Start at 0 or Start at 1 (part of #1807)
file.projectSettings takes frameStart (0|1), the Project Settings dialog shows Frame Count, and scripts get app.project.framesCountType and displayStartFrame. The setting was already read by the Frames and Feet + Frames time display; nothing could change it.
2026-10-23 03:07:01 +02:00
Markus Ang 8ac7b3f508 #2135: reject non-ASCII thumbnail text safely 2026-10-22 19:29:42 -04:00
Anubhav-RaiandClaude Opus 5 6e3b5eb3e3 engine: deleting a layer clears the references to it (#2153)
Nothing cleared a `Value::Layer` reference when its layer was deleted, so the id stayed behind:
`env_pixels` resolved it to nothing and rendered no environment at all, while the UI showed "None",
which is documented as "fall back to the comp's Environment Layer". The delete path already
unparents children and drops a track matte that pointed at a deleted layer; a layer reference now
goes the same way, in the same history step, so undo restores the layer and the reference together.

This covers every layer-reference property, not only an Environment light's Source: 73 of them
across the effects (Clone Source, Set Matte, Back Layer, Water Surface, Sky, Ground…). Keyframes
carry the same value kind as the property, as in `essential.rs`, and are fixed too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-23 04:41:11 +05:30
Christopher Linnett d2fae0a144 fix: bound Classic 3D depth-of-field blur and reject non-finite focus distances (#2092)
The camera's Set Focus Distance to Layer command compared only 'd <= 0.0', which NaN passes, and two finite but extreme positions can overflow their difference to infinity; a stored non-finite focus distance serializes as null and leaves the project unsavable. Reject it, fall back to the zoom plane in the camera evaluation, and count a non-finite focus or depth as in focus. The Classic 3D blur radius itself was unbounded: a 0%-scaled or very distant layer scales it by up to 1e9, and the padded blur buffer sized from it aborts the process. Cap each plane's radius to a padded buffer that fits the renderer's 16 Mpx layer budget, don't blur planes whose pixels cover under a thousandth of a comp pixel, saturate the padded sizes, and refuse padded buffers past the project's maximum layer size. Regression tests for each guard.
2026-10-22 21:57:41 +08:00
skylar taylor-barrick b55cb5bc15 Merge pull request #1363 from DWAA1660/fix/1237-comp-contains-cycles
Project: comp_contains walks precomps with a visited set, not recursion (#1237)
2026-10-22 07:52:56 -06:00
Madhav Agarwal 7fe0513894 engine: split layers above the original by default (#2083) 2026-10-22 19:13:36 +05:30