Commit Graph
3885 Commits
Author SHA1 Message Date
arnav-hiwarkar d7011a36b8 #2817: Closed falls back to the layer's paths when the selection names none
Selecting a non-path property (Position) or a stale selection disabled Closed, unlike
RotoBezier and the other Mask and Shape Path commands, which fall back to every mask of
the selected layers. A selected mask property (Feather, Opacity, Expansion) now selects
its mask. A selected locked mask still counts as a match, so it never falls back to its
unlocked siblings.
2026-11-12 18:22:21 +05:30
arnav-hiwarkar 36aa437d34 #2817: pin RotoBezier tangents, mixed checkmark and static paths for Closed 2026-11-12 18:20:05 +05:30
arnav-hiwarkar 37b0ea1145 Merge upstream main into fix/issue-2817-open-close-path
# Conflicts:
#	crates/engine/src/cmd/mask.rs
2026-11-12 18:14:02 +05:30
Dylan M. Taylor 63fda85a51 3D models: bound and share decoded textures, share skin matrices, index OBJ materials
* fix(model): share decoded textures and skin matrices, bound texture pixels, index OBJ materials

Texture::decode expanded any image to float RGBA without a size check. Repeated mtllib lines
reparsed the library and decoded its textures again; glTF textures with a non-default sampler
each copied their image's pixels; every instance of a skin built its own joint matrices. OBJ
usemtl and faces scanned the growing material and primitive lists, and contour grouping kept
every ancestor of every contour.

* address review: keep largest contours and dedupe mtllib by set

* address review: contour depth counts containers, glTF wrap copies and FBX textures share the import pixel budget

* address review: holes go to the innermost outline; glTF wrap variants keyed by decoded image

* address review: create every outline slot before attaching holes

* Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)

* Models: left-out textures are import warnings; the import texture budget is 2^29 pixels

* Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)

* Import: a model's left-out parts reach the user as an import warning

* FBX textures move to #2955 (it reworks the same code)
2026-11-12 07:00:15 -05:00
ash b9da77c63d Merge pull request #3571 from arnav-hiwarkar/fix/effect-ranges-easy-batch
Effects: 18 parameters take After Effects' valid ranges (Levels IC, 4-Color Gradient, Change Color, Noise HLS, Grid, Venetian Blinds, Radial Wipe, Scatter, Beam, Transform, Turbulent Displace)
2026-11-12 17:19:57 +05:30
ash bde8bea2bb Merge pull request #3564 from arnav-hiwarkar/fix/3535-analysis-inputs
Warp Stabilizer, 3D Camera Tracker: analysis keys cover masks, effects above and precomp contents (#3535)
2026-11-12 17:19:35 +05:30
ash 6ea9476ee4 Merge pull request #3560 from arnav-hiwarkar/fix/3538-ocio-file-reload
OCIO File Transform and custom OCIO configs follow their files on disk (#3538)
2026-11-12 17:08:11 +05:30
ash 46eb4d104d Merge pull request #3561 from arnav-hiwarkar/fix/3539-viewer-reload
Viewers show footage reloaded with unchanged metadata (#3539)
2026-11-12 17:03:17 +05:30
Dylan M. Taylor 740afc2869 FBX: records stay within their parent; textures, materials and links are indexed
* fix(model): FBX records stay within their parent, and textures, materials and links are indexed

A binary child record could end past its parent: the parent's end was then restored and the
child's tail parsed again. A failed array decompression printed the decompressed bytes in the
error. Texture objects aliasing one embedded Video or file each decoded and kept their own
pixels, outside the import budget. Each polygon scanned the primitive groups for its material,
and each object scanned every connection for its children.

* address review: FBX textures: key image cache by the source used, charge decoded size before decoding, propagate budget errors

* Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)

* Import: a model's left-out parts reach the user as an import warning

* FBX: textures past the limits are left out with an import warning, not a failed import; 2^29-pixel allowance
2026-11-12 06:30:20 -05:00
Dylan M. Taylor cb5bc81d28 Automation: refuse Watch Folder and Edit Original while file access is confined
* fix(automation): refuse Watch Folder and Edit Original while file access is confined
2026-11-12 06:30:03 -05:00
Dylan M. Taylor 075aa03ebe Render: memoized audio detection, bounded stretch, preroll, waveform, cache entries, 3D bounds, solids and selector time
* Render: memoized audio detection, bounded stretch, preroll, waveform, cache entries, 3D bounds, solids and selector time

comp_has_audio walked a precomp again for every layer using it; each comp
is answered once. A tiny stored stretch made one audio block fetch a vast
source range; playback speed is capped at 100x. Effect preroll is capped at
10 s. Waveforms stop where the audio ends and summarise at most a day.
Disk cache entries are decompressed only when their stated size matches
what their dimensions need (and at most 1 GiB). Advanced 3D triangle bounds
are clamped on both sides before narrowing. Solids larger than 16 Mpx are
made at a lower scale. A camera-facing layer's parent recursion is bounded.
A layer's Expression Selectors share one time allowance. A disk cache
folder under the temp folder is made private and may not be a link.

* address review: audio detection by breadth-first search, private cache folder resolved before the temp check, oversized layers not written, solid cap after rounding, selector static amount, viewport i32 clamp

* address review: private-folder check skips the temp folder itself; override value in expression fallback; assert alias cache opens

* address review: refuse the shared temp folder itself as a disk cache folder

* address review: shared counted selector budget, 30 s preroll, cache hardening (shared temp roots, matte discard, exact LZ4 length, render version 7), orient nesting guard
2026-11-12 06:29:56 -05:00
Dylan M. Taylor a287044644 Media: check still, EXR channel, GIF and WebP canvas sizes before allocating
* fix(media): check still, EXR channel, GIF and WebP canvas sizes before allocating

* address review: GIF frame allocation limit, lower wasm32 still limit, no EXR fallback after a size-limit error

* address review: the 2^25-pixel still limit also applies on 32-bit native targets

* Media: raise the still pixel limit to 2^28 (16384 x 16384) on 64-bit targets
2026-11-12 06:29:46 -05:00
Dylan M. Taylor 4d6036265b Convert Expression to Keyframes: refuse layers of more than 2^18 frames
* fix(engine): refuse to convert an expression on a layer of more than 2^18 frames

* address review: refuse a non-positive frame duration; count a partial last frame; check every selected layer before converting

* Convert Expression to Keyframes: cap at 2^20 frames (a 3-hour 60 fps comp is 648,000)
2026-11-12 06:29:38 -05:00
Dylan M. Taylor 2b30f53e7a Export: Skip Existing keeps a frame already in the overflow folder
* Export: Skip Existing keeps a frame already in the overflow folder

* Export: Skip Existing checks every overflow folder before a frame is placed
2026-11-12 06:29:31 -05:00
Dylan M. Taylor bdc56ca75e Timewarp: bound the motion search's total work by coarsening the vector grid
* Timewarp: bound the motion search's total comparisons by coarsening the vector grid

* Timewarp: coarsen within the frame, keep the pyramid depth and the window inside the frame
2026-11-12 06:29:25 -05:00
Dylan M. Taylor 9f93ac03fa Text layout: a wrapped line finds its glyphs and end caret by bisection
* Text layout: a wrapped line finds its glyphs and end caret by bisection

* Fix merge of layout tests
2026-11-12 06:29:20 -05:00
Dylan M. Taylor 6e1dd08147 Import: a 3D model's left-out textures reach the user as an import warning
* Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)

* Import: a model's left-out parts reach the user as an import warning
2026-11-12 06:29:14 -05:00
arnav-hiwarkar f88d0d0a8d #3487, #3488, #3489, #3490, #3491: Levels (Individual Controls): skip the overflowing pow with a branch
WGSL's select evaluates both of its arguments, so the previous commit still
ran the overflowing pow and only discarded it. An explicit branch now skips
the power for a curve past the bound.
2026-11-12 16:37:09 +05:30
arnav-hiwarkar 047b912fc5 #3487, #3488, #3489, #3490, #3491: Levels (Individual Controls): GPU saturates the curve before pow can overflow
WGSL leaves the result of an overflowing pow indeterminate, so clamping
after it did not guarantee the CPU's 1e30 bound on every backend. A curve
past the bound (log2|t| / gamma > 99.7) now saturates to +-1e30 without
calling pow; between 1e30 and 2^99.7 the clamp gives the same 1e30, so the
GPU matches the CPU everywhere.
2026-11-12 16:35:08 +05:30
arnav-hiwarkar 28c63ce728 #3535: the analysis key leaves out the effects above while the Effects switch is off
The renderer skips the whole effect stack then, so editing those effects
doesn't change the analysed frames. Roto Brush Input Keys keep their text.
2026-11-12 16:30:24 +05:30
arnav-hiwarkar c0387ea18a #3470, #3472, #3473, #3474, #3475, #3476, #3477, #3480, #3483, #3484, #3487-#3491: test the ends of the new ranges on the GPU
CPU / GPU comparisons at the new ends for Venetian Blinds (width 1, width
and feather 32000), Radial Wipe (feather 32000), Grid (border 2000),
Scatter (1000), Beam (4000), Change Color (1710 degrees) and Turbulent
Displace (seed 100000). The comparison counts infinity on both sides as
equal, so Levels (Individual Controls) gets a direct check that the GPU's
output is finite at the ends of its ranges. The Noise HLS test asserts the
spec's ranges, so it fails without them.
2026-11-12 16:24:17 +05:30
arnav-hiwarkar acf60eeaf3 #3487, #3488, #3489, #3490, #3491: Levels (Individual Controls): bound the curve only where it would overflow
The 1e6 bound also capped curves that were finite before (gamma 0.1 on an
HDR value 4 x Input White is 4^10, about 1.05e6). The bound is now 1e30:
1e30 times the widest output range (2e4) is still finite, and every curve
below it renders as before. A test pins 4^10.
2026-11-12 16:24:17 +05:30
arnav-hiwarkar 22e66e3d97 Merge remote-tracking branch 'upstream/main' into fix/effect-ranges-easy-batch 2026-11-12 16:15:01 +05:30
arnav-hiwarkar b0133cf862 Merge remote-tracking branch 'upstream/main' into fix/3535-analysis-inputs 2026-11-12 16:14:53 +05:30
ash 44aa7302f8 Merge pull request #3550 from arnav-hiwarkar/fix/3534-3536-analysis-signatures
Warp Stabilizer, 3D Camera Tracker, Roto Brush: one explicit analysis key; offline footage keeps analyses (#3534, #3536)
2026-11-12 16:11:59 +05:30
arnav-hiwarkar 64c28b56d7 #3535: docs: which key covers what, why markers and audio are or aren't keyed 2026-11-12 15:47:50 +05:30
arnav-hiwarkar 98d82ae761 #3470: Turbulent Displace: Random Seed takes After Effects' range
Random Seed takes 0..100000, After Effects' recorded valid range (and what
the other effects' Random Seed already takes). The CPU and GPU hash it the
same way with wrapping u32 arithmetic, so any seed costs the same.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar 74bcb30540 #3471: Transform: Scale Height and Scale Width take After Effects' range
Scale Height and Scale Width take -30000..30000 %, After Effects' recorded
valid range in the same units. The CPU and GPU share the matrix
(transform_matrix_px) and the buffer growth, which is capped at 16 Mpx
(transform_grow; the CPU test already renders 100000 %), so larger scales
stay bounded.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar 3bd7de6ad8 #3475: Beam: Starting and Ending Thickness take After Effects' range
Starting and Ending Thickness take 0..4000, After Effects' recorded valid
range in the same units (pixels). The CPU and GPU renderers draw the beam
per pixel from its distance to the segment, within the layer's buffer, so
a thicker beam costs the same.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar c4cd9f1c2e #3477: Scatter: Scatter Amount takes After Effects' range
Scatter Amount takes 0..1000, After Effects' recorded valid range in the
same units (pixels); the slider stays 0..127. The CPU and GPU renderers move
each pixel by one random offset of up to the amount, so a larger amount
costs the same; samples outside the layer are transparent.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar a08240155c #3474: Radial Wipe: Feather takes After Effects' range
Feather takes 0..32000, After Effects' recorded valid range in the same
units. The CPU and GPU renderers evaluate the feather per pixel in closed
form (a Gaussian of the distance to the wedge, radial_wipe_alpha), so a
wide feather costs the same and stays finite.
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar 29c76bb6b4 #3472, #3473: Venetian Blinds: Width and Feather take After Effects' ranges
Width takes 1..32000 and Feather 0..32000, After Effects' recorded valid
ranges in the same units (pixels). The work stays bounded: the renderers
floor the width at 1 buffer pixel, sum at most 40 Gaussian terms per
pixel, and above 8 widths the feather is the even veil
(venetian_cover; the GPU hands that case to the CPU, as before).
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar 85f47ec072 #3476: Grid: Border takes After Effects' range
Border takes 0..2000, After Effects' recorded valid range in the same units
(pixels). The CPU and GPU renderers compute the border per pixel in closed
form, so a wider border costs the same; one wider than the cells covers
them.
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar ce4db78896 #3480: Change Color: Hue Transform takes After Effects' range
Hue Transform takes -1800..1800 degrees, After Effects' recorded valid range
in the same units. The CPU and GPU renderers wrap the hue (rem_euclid /
fract_euclid), so five turns render like the hue they end on, at the same
cost.
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar 244f9bd846 #3483, #3484: Noise HLS: amounts and Grain Size take After Effects' ranges
Hue, Lightness and Saturation go up to 1000 % and Grain Size down to 0 (it
keeps its wider maximum, 100), After Effects' recorded valid ranges in the
same units. Noise HLS Auto shares the controls and widens with them.
Defaults and sliders are unchanged.

The renderers need no change: hue wraps, lightness and saturation clamp per
pixel after the noise is added, and Grain Size is floored at 0.1 px on the
CPU and the GPU, so the new values cost the same and stay finite.

The amounts were written as "percent" in keyframe text because their range
was 0..100, and Grain Size would become "percent" at 0..100; UNITS rows keep
the units they had.
2026-11-12 15:46:48 +05:30
arnav-hiwarkar 36eecad00f #3538: a cached config's size counts the LUTs it holds 2026-11-12 15:30:14 +05:30
arnav-hiwarkar 363a800d3f #3478, #3479: 4-Color Gradient: Blend and Jitter take After Effects' ranges
Blend goes up to 10000 (it keeps its minimum of 1, below After Effects' 5)
and Jitter up to 500 %, After Effects' recorded valid ranges in the same
units. Defaults and sliders are unchanged.

The CPU and GPU renderers capped Jitter's dither at 100 %, so higher values
would be stored but render as 100. Both now take it from one function,
four_color_jitter, which keeps the dither linear up to 500 %. Blend 10000
is a finite softening (Blend squared) at the same cost.

Jitter was written as "percent" in keyframe text because its range was
0..100; a UNITS row keeps that unit.
2026-11-12 15:14:34 +05:30
arnav-hiwarkar 39a3b565d8 #3487, #3488, #3489, #3490, #3491: Levels (Individual Controls): levels and gamma take After Effects' ranges
Input/Output Black and White of every channel take -10000..10000 and Gamma
goes down to 0 (it keeps its wider maximum, 10), After Effects' recorded
valid ranges in the same units (the .aep mapping is Conv::Plain). Defaults
and the 0..1 / 0.1..3 sliders are unchanged.

Gamma 0 renders as the existing 0.01 floor, an exponent of 100, so an HDR
value a few times Input White would overflow f32 and an infinite curve
times an empty output range is NaN. The curve is now bounded at +-1e6 on
the CPU (Lv::apply) and the GPU (fxt_lv), so the output stays finite over
the whole range; values below the bound are unchanged.

The engine regression (tests/effect_ranges.rs) sets both ends of each row
through prop.set, checks undo/redo, save and reopen, and that a project
saved with the narrower ranges opens with the new ones and its values.
2026-11-12 15:11:21 +05:30
arnav-hiwarkar 9831d70bfa #3539: a cache purge gives the viewers' frames new keys
Reload Footage Changed on Disk of a file whose metadata didn't change keeps the
project's revision; PurgeCaches emptied the viewer frame cache but the new
frame came back under the key of the one on screen, so neither the Composition
viewer nor the passive viewers uploaded it. Count purges and mix the count into
the frame key's options, as #3493 does with the LUT generation.
2026-11-12 15:11:13 +05:30
arnav-hiwarkar 4d24233505 #3538: a custom config not read yet is read for its LUT files 2026-11-12 15:07:48 +05:30
arnav-hiwarkar 216fc6052d #3470, #3471, #3472, #3473, #3474, #3475, #3476, #3477, #3478, #3479, #3480, #3483, #3484, #3487, #3488, #3489, #3490, #3491: guard effect hard ranges against After Effects' recorded ranges
A test walks every effect parameter the .aep table maps to a recorded After
Effects definition (fx_defs) and checks that the effect's hard range
(ParamUi::Slider min..max, what prop.set and the value editors clamp to)
covers After Effects' valid range, converted to EffectCraft's units the way
the importer converts values. Conv::number_scale is that conversion, now
shared by the importer and the test.

The parameters still narrower are allow-listed by issue (#3460-#3492); the
test fails on a new narrower parameter and on a row that is no longer
needed, so the list shrinks as the issues are fixed.
2026-11-12 15:05:48 +05:30
arnav-hiwarkar 637d33f796 Merge #3550's branch: the frames key writes names; precomp layer names are keyed too (#3535) 2026-11-12 15:04:14 +05:30
arnav-hiwarkar 385b9530a1 #3535: Warp Stabilizer and 3D Camera Tracker keys cover a precomp's contents
On a precomp layer the key covered only the nested comp's size, rate and
duration, so an edit inside the precomp kept a stale analysis. The key now
writes the precomp layer's Essential Properties and what its comp draws, field
by field and recursively. Each comp is written once per key and nesting is
capped, so shared, deep or cyclic precomps are keyed in bounded time. Docs say
what clears an analysis.
2026-11-12 15:00:14 +05:30
arnav-hiwarkar 51b8521eb3 #3539: test that the viewers show a still reloaded at the same size 2026-11-12 14:53:09 +05:30
arnav-hiwarkar 57990d9a47 #3535: Warp Stabilizer and 3D Camera Tracker keys cover the layer's masks and the effects above them
Their analyses are made from the layer's input to the effect (masks, then the
effects above it), but the key covered only the layer's frames, so a mask or
effect edit kept a stale analysis. frames_key::analysis_key adds the masks,
the effects above the instance and the Effects switch. Keys saved before
(frames only) stay valid while each edit keeps the input.
2026-11-12 14:48:30 +05:30
arnav-hiwarkar 94ab5d77cf #3536: pin the key text of non-default footage, source layer, Cineon, colour space and solid values; every mask mode keys apart 2026-11-12 14:47:05 +05:30
arnav-hiwarkar a700c5c8db #3538: layer keys and render reads include OCIO files and a config's LUTs 2026-11-12 14:43:54 +05:30
arnav-hiwarkar ef137cb2ce #3538: OCIO transform files and configs follow their files through #3493's LUT cache
Move the LUT cache that follows files by size and modification time into
effects::file_cache and use it for ocio::lut::load_file and
ocio_config::load_config. A config's entry depends on the .ocio file, the LUTs
its FileTransforms read and the search-path places it looked for missing ones.
check_lut_files and lut_generation now cover all three caches.
2026-11-12 14:43:54 +05:30
arnav-hiwarkar e04ae226a9 #3536: the frames key writes group and property names, and the uid of a property with an expression
Expressions find effects by name and read names (thisProperty.name,
propertyGroup(1).name), and seed random/wiggle with the property uid, so
both can change the frames. The Debug-based key had them.
2026-11-12 14:37:02 +05:30
arnav-hiwarkar 0547c51e47 #3538: test that OCIO transform files and configs are read again once they change 2026-11-12 14:33:21 +05:30