The explicit-choice check in only_a_writer_before_v0_10_0_gets_the_old_compositing used
`all(|g| g.get(COMPOSITE).is_none_or(|p| p.value == Bool(false)))`, which also passes when the
control is gone: it could not tell "kept off" from "never written". Collect the values that are
there and require exactly one false per particle effect (the Fill below them has none), so a
reviewer can see the control is preserved.
Before v0.10.0 the CC particle systems composited their particles over the
layer, so a particle layer still showed the solid or precomp under it and any
earlier effect in the stack. v0.10.0 (4e7eafa8) made them replace the layer to
match After Effects 2026, which hides that artwork in projects written before it
(#1898).
Both effects get a Composite With Original checkbox, off by default so new
instances keep the After-Effects-matching behaviour and the frames the effects
are matched against stay green. upgrade_effects now takes the savedBy version of
the file a project came from - opened, imported, read from an Essential Graphics
template, or loaded as the New Project template - and turns the control on for
an instance from a project written before v0.10.0 that does not already carry it
(a saved value is the artist's choice).
The legacy plan is the pre-v0.10.0 code restored: Post::Combine with the old
Transfer Mode mapping (Composite, Screen and Add themselves, Black Matte 5) and
the same Add/Over accumulation. savedBy first shipped in M3.14 (948f06fb),
released after v0.2.0, so files from v0.2.1 on name their writer and get the
fix; an older file has no savedBy and keeps replacing rather than migrating on a
guess.
Tests: crates/effects/src/sim/tests.rs
composite_with_original_restores_the_old_particle_transfer_modes checks the
restored mapping against combine() in every Transfer Mode;
crates/engine/src/tests/legacy_particles.rs opens a 0.6.0 project (both particle
effects over a Fill on a solid) and checks the artwork and the earlier effect
show, that a current or anonymous file still replaces, the version gate, and the
New Project template path.
Fixes#1898.
DialogAnswer.paths became a Result in #3304; three import tests merged
since still passed a plain Vec, so the test build of effectcraft-ui-egui
did not compile on main.
paint_pattern draws the tile content as a tree of its own on a fresh tile, so the
plain-clip state inherited from an enclosing group must not apply there: an inner
clip that blends against the tile was drawn as a plain clip and lost its backdrop.
Save and clear SurfaceBudget::plain around the tile draw, as apply_soft_mask does.
Upstream gained region-limited coverage, clip regions, a group nesting cap
in the PDF builder and a render budget. The clipping fast paths (canvas-covering
clips drawn without a surface, plain clip groups on region-sized surfaces) are
ported onto those; the crop-based shape/image/paint changes and the PDF
builder depth cap are superseded by upstream's.
* #3593: render_frame and render_preview tile `times` into one contact sheet
* contributors: credit andreaaazo by their GitHub profile name
* #3593: contact-sheet cells default to 320 px, sheets stay under 8000 px
With `times`, `max_side` now defaults to 320 per cell instead of 960, so
64 frames of a 16:9 comp make a 2560 x 1440 sheet instead of 7680 x 4320.
`sheet::MAX_SIDE` drops from 8192 to 7680. `labels` that isn't true or
false is a bad-arguments error, and a bridged cell keeps the time asked
for when the app's reply leaves `time` out.
* Text animators: fit one animator to a Per-character 3D setting
* Animation presets: save text animators whole and add them to the target layer (#2530)
* Animation presets: a preset that fits no layer is an error, not silence
* Tests: text animators in animation presets (#2530)
* Fix text preset review edge cases
* Keep existing animators consistent when presets turn 3D off
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
After Effects stores a static shape path that an expression draws (a text-box rig writes its
outline from the text's size) with no vertices. The importer dropped the whole path ('its path
has no vertices'), expression and all, so such shapes drew nothing. A path without vertices that
has an expression and no keys now arrives as an empty path with its expression; one without an
expression is still reported.
* Expressions: a text style's justification and direction use After Effects' names
After Effects reads a paragraph's justification as alignLeft, alignCenter,
alignRight, justifyLastLeft, justifyLastCenter, justifyLastRight or
justifyFull, and its direction as left-to-right or right-to-left; its
setters take those names. EffectCraft returned its own keys (left, ltr,
justifyAll...), and the document refused alignLeft/alignCenter/alignRight
and justifyFull. The getters now return After Effects' names and the
parser accepts them; EffectCraft's own names are still accepted.
* Expressions: every accepted justification spelling reads back as After Effects' name
setJustification('CENTER_JUSTIFY').justification returned CENTER_JUSTIFY.
The setter and getter now share one table of the spellings Justify::parse
accepts, looked up as own properties only, so an alias reads back as
alignCenter and an inherited name such as toString is never matched.
After Effects 26.3 numbers MULTIPLE_JUSTIFICATIONS 7412, LEFT_JUSTIFY 7413,
RIGHT_JUSTIFY 7414, CENTER_JUSTIFY 7415, and the justified variants with
the last line left, right, centred and full 7416-7419. EffectCraft had
centre before right (and last-line centre before last-line right), so a
script setting 7414 got a centred paragraph instead of a right-aligned
one, silently.