Commit Graph
509 Commits
Author SHA1 Message Date
aiongg d664e403ae Merge #2185: Project Settings: Frame Count, Start at 0 or Start at 1 (@marcoarnulfo) 2026-10-24 00:13:55 +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 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
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
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
Madhav Agarwal 963f489eab engine: resolve effect match-name paths in prop_ref (#2064) 2026-10-22 18:47:59 +05:30
Madhav Agarwal 3f4ffdc0fc engine: a keyframe on the out point no longer warns (#2084) 2026-10-22 18:41:26 +05:30
Camille Moine c7eb48dc3a Layer: reveal layer sources in the Project panel (#1809) 2026-10-20 13:59:01 +02:00
Camille Moine 8d887fd6da Layer: replace a layer's source with Alt-drag and Ctrl+Alt+/ (#991, #813)
Alt/Option-drag a Project item onto a Timeline layer, or press Ctrl+Alt+/ (Cmd+Opt+/) with layers and an item selected, to swap the layer's source. The layer keeps its name, timing, transforms, keyframes, expressions, effects, masks, switches, parenting and track matte; a source with audio adds Audio properties when missing. Precompositions can't be nested inside themselves.

AVLayer.replaceSource now runs the same engine command and closes the replaceSource part of #813.
2026-10-20 12:18:46 +02:00
Camille Moine f72e17a2bb Engine: Video's Alt-click and Hide Other Video take cameras and lights along
The Timeline draws the Video switch (the eye) on every layer's row, nulls,
cameras and lights included, but set_switch_others and hide_other_video
skipped cameras and lights: Alt-clicking the eye of a selected layer neither
set them apart with the selection nor hid them with the rest.

Both now use the rule the eye cell shows: every layer but track mattes.
2026-10-19 10:25:17 +02:00
Matej Kozlovský d74cb70938 Cryptomatte pick: snap to the effect's input buffer grid, not the preview scale
At preview scales from 0.75 to 1 footage keeps native pixels, a proxy sets
its own buffer scale at any render scale, a Preserve Resolution precomp is
drawn at full size, and earlier effects can pad or offset the buffer, so
snapping to the preview grid could pick a different ID than the rendered
matte. Use the scale and offset of the buffer the Cryptomatte effect
receives (layer_input, rendered once and then cached).

The pixel centre goes to an aux point through effectcraft_effects::aux_point,
which index_map_at now calls too, so the two round identically (a division
vs the reciprocal picked the other ID on a 104 px proxy of 1920 px footage).

An unknown effect uid errors "no such effect" and a layer without pixels
"this layer has no pixels", as effect.pickColor does.
2026-10-19 08:40:16 +02:00
Matej Kozlovský 76098665b0 Cryptomatte: effect.pickCryptomatte picks the ID under a point into the Selection (#473)
Engine part of viewer picking: replace / add / remove the top-coverage ID at a
layer-space point in one undo step, at the time the Selection was read. The
aux channels come from the render's own path (Renderer::layer_aux, shared with
the effect host; footage at its source time, 3D precomps) through a renderer
set up like Session::render (expressions, render prefs, layer cache). An
optional scale snaps the point to the pixel a preview at that resolution
samples. New effects::crypto_select quotes names and falls back to <hex> for
unnamed, wildcard-looking or ambiguous names. effect.cryptomatteAt is the
read-only hover query.
2026-10-19 06:10:27 +02:00
Brandon Thomas 7804cfd223 Format merged community PRs 2026-10-18 09:37:06 -04:00
Brandon Thomas 10cf6690c4 Merge pull request #1730
# Conflicts:
#	crates/engine/src/commands/prop_groups.rs
2026-10-18 09:36:40 -04:00
Brandon Thomas a594abf4f7 Merge pull request #1652
# Conflicts:
#	docs/gaps.md
2026-10-18 09:35:51 -04:00
Brandon Thomas dfff3f9010 Merge pull request #1785 2026-10-18 09:35:35 -04:00
Brandon Thomas 1dac4c0e6b Merge pull request #1759 2026-10-18 09:35:35 -04:00
Brandon Thomas 415e353dc0 Merge pull request #1711 2026-10-18 09:35:35 -04:00
Brandon Thomas fcc9e853de Merge pull request #1709 2026-10-18 09:35:35 -04:00
Brandon Thomas 972a2d21d2 Merge pull request #1659 2026-10-18 09:35:34 -04:00
Brandon Thomas 226fbf5810 Merge pull request #1485 2026-10-18 09:35:33 -04:00
Brandon Thomas 736c0ec4b4 Merge pull request #1430 2026-10-18 09:35:33 -04:00
Brandon Thomas bb8dd41af1 Merge pull request #1087 2026-10-18 09:35:32 -04:00
Kristian Myram cffa1f25d0 fix(shapes): update inner controls when Polystar type changes (#1685) 2026-10-18 10:05:57 +00:00
MarekSpodBiedry 0e7f525a81 Keep the target's paint when pasting Merge Paths (#1681) 2026-10-18 10:57:23 +02:00
MarekSpodBiedry 4f49017329 Merge remote-tracking branch 'origin/main' into review-1730 2026-10-18 10:56:01 +02:00
MarekSpodBiedry 651ee5a78e Resolve effects/<match name> property paths in anim.applyPreset (#1746) 2026-10-18 01:10:30 +02:00
MarekSpodBiedry b8d52cd8c4 Paste path operators below the shapes they act on (#1681)
Pasting Trim Paths, Wiggle Paths or a paint into a shape layer with nothing
selected put it on top of Contents, where it acts on no shapes. It now goes
to the bottom, as Add places it. Also fixes #1629.
2026-10-17 14:37:42 +02:00
MarekSpodBiedry 7146006c15 Duplicate the selected text animator or selector instead of the whole text layer (#1665) 2026-10-17 07:50:03 +02:00
MarekSpodBiedry 8f74c3790e Replace non-ASCII characters in EXR layer names so ProEXR export no longer panics (#1702) 2026-10-17 07:46:32 +02:00
aiongg 816ca95800 Footage: follow stored footage paths only on this computer or the project's share
Footage paths stored in a project are resolved through craft-docpath against
the project file when it is opened or imported: relative paths are taken from
the project's folder, and an item whose path is on another computer, names a
device or is a non-file URL is marked missing without being looked at. The
footage check, the changed-on-disk scan, Reload Footage, Relink, Edit Original
and Reveal in Finder resolve the path again before touching the file, and the
Project panel, Metadata panel and waveforms read no file details of missing
footage.

Lottie image assets are resolved against the Lottie file; one on another
computer is skipped with a warning. Files a 3D model names are read only when
local or on the model's own share.

Reveal in Finder refuses a folder that would open as an app or other bundle.

Files imported or relinked in this session are opened as given.
2026-10-16 17:43:58 +00:00
aiongg 1064911fc4 Edit: Edit Original opens only regular files with a footage media extension
A folder or bundle, a link to another kind of file, or a file whose extension
the footage importer doesn't take is refused with a message instead of being
handed to the system's default app.
2026-10-16 16:14:02 +00:00
aiongg 299223f112 Templates: Import Template writes embedded media only into its item folders
Each 'media/<item>/<file>' entry is written to '<template> Media/<item>/<file>'
only when <item> is all digits and <file> is a plain file name (not empty, '.'
or '..', no separators, and on Windows no drive, stream or device name), and
the written path is checked to sit in an item folder of '<template> Media'.
read_stored skips entries with absolute, drive or '..' names.
2026-10-16 16:14:02 +00:00
detthandClaude Sonnet 5.5 2823abc40c fix(engine): report the keys keys.delete removed, not the keys selected
keys.delete returned the number of selected keys even when some of them
no longer existed, so a delete that removed nothing still reported success.
It now returns the number of keys actually removed, and a key that is gone
or listed twice is not counted.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JaA7tHy731qtvscjulrcfz
2026-10-16 13:55:29 +00:00
Hamza 0c39995d0f Convert a shape layer's parametric path to a bezier path (#932)
`path.convertToBezier` only cleared `roto_bezier` on mask groups, so on a shape
layer it did nothing at all: a Rectangle, Ellipse or Polystar path stayed
parametric and its vertices could not be edited or animated. In After Effects
the command replaces such a path with an editable Path 1 of the same geometry.

The geometry is not re-derived. `parametric_path` calls the same
`effectcraft_path::{rect, ellipse, polystar}` functions `render::shapes::collect`
calls, with the same defaults for a property a group doesn't carry, so the path
that comes out is the shape that was on screen; `from_kurbo` and
`build::shape_path` then make an ordinary Path 1.

Masks are unchanged: shape items take precedence when selected, and with none
selected the command falls through to the old mask behaviour. Path Direction is
carried over rather than reset, since reversing a path is a separate command,
and the new path is left selected so its vertices can be edited straight away.

The parametric properties are read at the current time and the result is a
static path: a Size or Roundness animation cannot survive becoming a vertex
list, in After Effects either. Undo restores the parametric shape.
2026-10-16 18:13:08 +05:00
BFlat 3e2793af49 Merge pull request #1318 from DWAA1660/fix/1250-mask-tangent-lengths
Masks: addVertex and setVertex fill in missing tangents of a loaded path (#1250)
2026-10-16 19:03:32 +07:00
BFlat fa5eff7210 Merge pull request #1332 from Themolx/null-visibility-switch
Null layers: the Video switch hides them in the Composition viewer, as in After Effects
2026-10-16 19:02:24 +07:00
BFlat 8e96fdd0fc Merge pull request #1351 from DWAA1660/fix/1246-script-confirmed-flag
Scripts: a script can't confirm executing a file for itself (#1246)
2026-10-16 19:01:15 +07:00
bflatasticandClaude Opus 5.5 c0b505b41d Masks: deleting vertices fills in missing tangents first, like add and set (#1250)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:40:14 +07:00
BFlat 9eec6ea705 Merge pull request #1344 from dotCooCoo/fix/find-missing-fonts-postscript-names
Find Missing Fonts: fonts found by their PostScript or full name are no longer listed
2026-10-16 18:12:47 +07:00
bflatasticandClaude Opus 5.5 86dff3ce78 Footage: relinking a moved project keeps Windows separators and UNC shares
project_dir rebuilt the folder with `/` and dropped a UNC share's leading
`\`, so a project opened from \server\share\Job looked for its moved
footage under the relative path server/share/Job and never found it. It now
keeps the path's own separator and its leading `/` or `\`.

The relink tests built mixed-separator paths (`…\Job/p.ecproj`) and so failed
on Windows; they now use native paths and check the separator is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:13:40 +07:00
BFlat 7dc488b79d Merge pull request #1222 from arnav-hiwarkar/perf/preset-panel-cache
perf(presets): reuse cached text and user preset data
2026-10-16 17:03:05 +07:00
BFlat c044e7a872 Merge pull request #1543 from arnav-hiwarkar/fix/1536-ai-retain-layer-sizes
Import: Composition - Retain Layer Sizes for PDF / Illustrator files (#1536)
2026-10-16 17:02:43 +07:00
arnav-hiwarkarandClaude Opus 5.5 c4c079d4f4 fix(engine): copy only the selected keyframes across properties (#1552)
Clicking keyframes on Position, Rotation and Scale highlights those
properties, and Copy then added every key of a highlighted property that
had none selected, so deselected keys came back on paste. The keyframe
copy branch also left a stale value clipboard that Paste preferred over
the new keys.

Copy now takes whole animations only when no key is selected or the
Transform group is selected, and every copy branch clears the clipboards.

Also collapses a nested `if` in the STL importer into a match guard,
which Clippy 1.95 flags under -D warnings (no behaviour change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:57:50 +05:30
arnav-hiwarkarandClaude Opus 5.5 2d216151c6 Import: Composition - Retain Layer Sizes for PDF / Illustrator files (#1536)
The PDF / Illustrator import dialog now offers Composition - Retain Layer
Sizes. Each file layer's footage is cropped to its artwork bounds (fills,
stroke outlines, images, clips) and positioned to keep its place on the
page. Decoding, continuous rasterisation, shape conversion and reload share
one crop (effectcraft_pdf::layer_doc_with_sizes), so they agree.

Also collapses a match guard in the STL reader for current stable clippy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:33:50 +05:30
DWAA1660andClaude Opus 5.5 642eed0802 Properties/Timeline/Effect Controls: a typed component edits only that component of the other selected layers (#1434)
With several layers selected, typing a value into one dimension (say Z
Position) sent the edited layer's whole value with selected: true, and
prop.set gave every selected layer that same value: they all took the first
layer's X and Y and stacked on top of it. Scrubbing was fine (it sends an
offset).

prop.set takes dims: with selected and a typed value, the other selected
layers get only those components and keep the rest of their own value (Position
with Separate Dimensions included). The three panels send the components the
edit changed (both for linked Scale). Without dims, agents still set the whole
value on every selected layer.

Fixes #1434

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kwMcw2pYQ5fSjKa3ZPnh
2026-10-14 14:39:26 -06:00