Merge pull request #3442 from alpha-rock/feature/3d-light-presets

Add lighting presets to 3D effects
This commit is contained in:
BFlat
2026-10-22 00:26:40 +07:00
committed by GitHub
39 changed files with 475 additions and 16 deletions
+3 -1
View File
@@ -9,7 +9,7 @@
**Stage: alpha** · next: beta, ~13 points (ready for real work ~62% → ~75%, and reliable `.ai` exchange) and ~170–270 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (Print: Japanese paper sizes in the Print dialog's Media list — JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 128 × 182 mm beside the ISO B4/B5 (which keep their ISO 216 sizes), はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; about 90 community PRs of 2026-10-19: Objects on Path, the Simplify slider, Pencil Edit Selected Paths in the middle of a path, Georgian, and more; Background Export progress dialog with Cancel, #2564; about 85 community PRs of 2026-10-18: Justification…, Word .docx placed as area type, CCITT Group 3 TIFF, Roman Hanging Punctuation, blend spines edited with the Anchor Point tool, shadows and glows in stack order, coloured 3D lights, a Dutch catalog completed; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; about 145 community PRs of 2026-10-17: image-decode overflow guards, opaque floating panels, MCP export cancel and progress, editable Illustrator EPS import, scoped `doc_inspect`, Dutch, Danish and Indonesian, Simplified Chinese messages, screen-reader names; headline numbers unchanged; about 160 community PRs of 2026-10-15/16: PDF import crash fixes, Dimension tool, legacy `.ai` save, Intertwine, web pen input, Image Trace fixes, `.ai`/EPS/PDF import fidelity, Bulgarian, Greek, Swedish and Finnish catalogs; headline numbers unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; Print: Japanese paper sizes in the Print dialog's Media list — JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 128 × 182 mm beside the ISO B4/B5 (which keep their ISO 216 sizes), はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; about 90 community PRs of 2026-10-19: Objects on Path, the Simplify slider, Pencil Edit Selected Paths in the middle of a path, Georgian, and more; Background Export progress dialog with Cancel, #2564; about 85 community PRs of 2026-10-18: Justification…, Word .docx placed as area type, CCITT Group 3 TIFF, Roman Hanging Punctuation, blend spines edited with the Anchor Point tool, shadows and glows in stack order, coloured 3D lights, a Dutch catalog completed; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; about 145 community PRs of 2026-10-17: image-decode overflow guards, opaque floating panels, MCP export cancel and progress, editable Illustrator EPS import, scoped `doc_inspect`, Dutch, Danish and Indonesian, Simplified Chinese messages, screen-reader names; headline numbers unchanged; about 160 community PRs of 2026-10-15/16: PDF import crash fixes, Dimension tool, legacy `.ai` save, Intertwine, web pen input, Image Trace fixes, `.ai`/EPS/PDF import fidelity, Bulgarian, Greek, Swedish and Finnish catalogs; headline numbers unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
VectorCraft is a clean-room, open-source, pure-Rust reimplementation of the Adobe Illustrator workflow. It runs
on macOS, Windows, Linux, FreeBSD and the web (WASM), and agents can drive all of it over MCP, the CLI and a JSON
@@ -134,6 +134,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap
| Date | What landed |
|---|---|
| 2026-10-21 | 3D lighting: four original lighting presets shared by Revolve, Extrude & Bevel, Inflate and Rotate, with command access and manual Custom settings; scores and estimates unchanged. |
| 2026-10-21 | Print: the Print dialog's Media list has the Japanese papers — JIS B4 (257 × 364 mm), JIS B5 (182 × 257 mm) and JIS B6 next to the ISO B4/B5 (which keep their ISO 216 dimensions for settings saved before them), はがき 100 × 148 mm, 往復はがき 148 × 200 mm, 封筒 長形3号 120 × 235 mm / 長形4号 90 × 225 mm / 角形2号 240 × 332 mm / 角形3号 216 × 277 mm and 四六判 127 × 188 mm. A paper is named by its JSON id or its dialog label, any case and spacing (`B5 (JIS)` or `b5jis`), and every added size prints as its own PDF page size. Headline numbers unchanged. |
| 2026-10-18 | Inch rulers divide the inch in half, not in ten. Zooming in stepped an inch ruler through 0.2, 0.1 and 0.05 of an inch, marks an inch ruler does not carry; below an inch the steps are binary fractions now, 1/2 down to 1/16, with the unlabelled ticks between them binary too (down to 1/128, so the 32nds, 64ths and 128ths appear as the report expects), while the metric units, points, pixels and picas keep their tenths. A label carries the decimals its step needs, so a sixteenth reads 0.0625 rather than the rounded 0.06, with trailing zeros dropped (#2827). Headline numbers unchanged. |
| 2026-10-18 | Exported and saved files can repeat their bytes: PNG, PDF, EPS, legacy `.ai`, packaged zips and `.vectorcraft` saves recorded the wall-clock time they were made in, so the same commands run twice gave different bytes though the pixels matched. `SOURCE_DATE_EPOCH` now stands in for the clock in everything a file records, as the reproducible-builds specification defines it; a date a command is given still wins, and a malformed value is ignored rather than refusing the export. The recovery heartbeat and the seed PDF encryption draws on keep the real clock, so a password-protected PDF still differs between runs (#2880). Headline numbers unchanged. |
@@ -186,6 +187,7 @@ The full pre-standard record of what landed is kept in [docs/roadmap.md](docs/ro
| Date | Change | Summary |
|---|---|---|
| 2026-10-21 | minor | 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged |
| 2026-10-21 | minor | Print: Japanese paper sizes in the Print dialog's Media list (JIS B4/B5/B6, はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号, 四六判); ISO B4/B5 keep their sizes, and a paper is named by id or label, any case and spacing. Headline numbers unchanged. |
| 2026-10-17 | minor | Colored Inflate: shared membrane normals and smooth RGB vector ramps replace flat polygon bands; channel groups are batched per solid-fill surface. White-light Inflate retains its original polygons, paint composition and seam joins; the 0.05 seam minimum is restored. Scores and hour estimates unchanged. |
| 2026-10-16 | minor | 3D lighting: one to ten directional lights, a selectable light list, Add Light and Delete Light, independent settings and artboard gestures; ambient applied once and smooth RGB vector export; scores and hour estimates unchanged |
+3
View File
@@ -147,3 +147,6 @@ sync_github_name = true
[people.addibble]
sync_github_name = true
[people.alpha-rock]
display_name = "Aditya Sisodiya"
+2
View File
@@ -40,6 +40,7 @@ mod clip;
mod distort;
mod group;
mod lighting;
mod lighting_presets;
mod live;
mod marks;
pub mod pixel;
@@ -72,6 +73,7 @@ pub use group::{
OutlineHook, PATHFINDER_EFFECTS, evaluate_container, has_container_appearance, has_pathfinder, is_pathfinder, member_shapes, paints,
pathfinder_children,
};
pub use lighting_presets::{LIGHTING_PRESETS, LightingPreset, lighting_preset_params, matching_lighting_preset};
pub use live::{expand_live, expand_live_deep, expanded_live_group, text_outliner};
pub use marks::{CROP_MARKS, crop_marks_art, has_crop_marks};
pub use pixel::{PIXEL_EFFECTS, PixelFx, PixelSpace};
+112
View File
@@ -0,0 +1,112 @@
//! Original lighting rigs for the publicly documented 3D preset names.
//! Adobe publishes the names, not numeric settings. These values are our own; Standard
//! retains the existing default. A preset replaces the light rig and ambient only.
use serde_json::{Value, json};
use vectorcraft_three_d::{DEFAULT_LIGHT, Light};
#[derive(Clone, Copy, Debug)]
pub struct LightingPreset {
pub id: &'static str,
pub label: &'static str,
rotation: f64,
height: f64,
softness: f64,
ambient: f64,
}
pub const LIGHTING_PRESETS: [LightingPreset; 4] = [
LightingPreset { id: "standard", label: "Standard", rotation: DEFAULT_LIGHT.0, height: DEFAULT_LIGHT.1, softness: 0.0, ambient: 25.0 },
LightingPreset { id: "diffuse", label: "Diffuse", rotation: DEFAULT_LIGHT.0, height: DEFAULT_LIGHT.1, softness: 100.0, ambient: 35.0 },
LightingPreset { id: "topLeft", label: "Top Left", rotation: 135.0, height: 45.0, softness: 0.0, ambient: 25.0 },
LightingPreset { id: "right", label: "Right", rotation: 0.0, height: 30.0, softness: 0.0, ambient: 25.0 },
];
impl LightingPreset {
/// The same patch is used by commands and the dialog's preview transaction. Null
/// `lights` replaces an explicit multi-light rig with this single white light.
pub fn params(self) -> Value {
json!({
"lights": null, "lightColor": "#ffffff", "lightIntensity": 80.0,
"lightRotation": self.rotation, "lightHeight": self.height,
"lightSoftness": self.softness, "lightBehind": false, "ambient": self.ambient,
})
}
fn matches(self, light: Light, ambient: f64) -> bool {
let close = |a: f64, b: f64| (a - b).abs() < 1e-6;
!light.behind
&& light.color.iter().all(|c| close(*c, 1.0))
&& close(light.intensity, 0.8)
&& close(light.rotation, self.rotation)
&& close(light.height, self.height)
&& close(light.softness, self.softness / 100.0)
&& close(ambient, self.ambient / 100.0)
}
}
pub fn lighting_preset_params(id: &str) -> Option<Value> {
LIGHTING_PRESETS.iter().find(|p| p.id == id).map(|p| p.params())
}
/// Identify the effective rig, including an equivalent explicit single light. Names
/// are UI state derived from settings, so edits and older files cannot leave stale names.
pub fn matching_lighting_preset(params: &Value) -> Option<usize> {
let upgraded = crate::upgrade_params(crate::REVOLVE, params);
let o = crate::revolve_options(&upgraded);
let light = if let Some(lights) = &o.lights {
let mut entries = lights.iter();
let light = *entries.next()?;
if entries.next().is_some() {
return None;
}
light
} else {
Light {
rotation: o.light_rotation,
height: o.light_height,
behind: o.light_behind,
color: o.light_color,
intensity: o.light_intensity,
softness: o.light_softness,
}
};
LIGHTING_PRESETS.iter().position(|preset| preset.matches(light, o.ambient))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lighting_presets_are_valid_unique_and_standard_preserves_the_default() {
for (i, preset) in LIGHTING_PRESETS.iter().enumerate() {
let patch = preset.params();
crate::lighting::validate(&patch).unwrap();
assert_eq!(lighting_preset_params(preset.id), Some(patch.clone()));
assert_eq!(matching_lighting_preset(&patch), Some(i));
assert!(!patch.as_object().unwrap().contains_key("shade"));
assert!(!patch.as_object().unwrap().contains_key("rotationX"));
}
assert_eq!(matching_lighting_preset(&json!({})), Some(0));
assert!(lighting_preset_params("unknown").is_none());
assert_eq!(crate::revolve_options(&LIGHTING_PRESETS[0].params()).light_direction(), crate::revolve_options(&json!({})).light_direction());
}
#[test]
fn lighting_preset_matching_uses_effective_lights_and_detects_custom_edits() {
let mut p = LIGHTING_PRESETS[3].params();
p["lightColor"] = json!("#ff0000");
p["lights"] = json!([{"rotation":0,"height":30}]);
assert_eq!(matching_lighting_preset(&p), Some(3), "explicit light overrides stale legacy color");
p["lights"][0]["color"] = json!("#4080ff");
assert_eq!(matching_lighting_preset(&p), None);
p["lights"] = json!([{}, {}]);
assert_eq!(matching_lighting_preset(&p), None);
for (key, value) in [("ambient", json!(26)), ("lightIntensity", json!(79)), ("lightBehind", json!(true))] {
let mut p = LIGHTING_PRESETS[0].params();
p[key] = value;
assert_eq!(matching_lighting_preset(&p), None, "{key}");
}
}
}
+37
View File
@@ -55,6 +55,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_doc,
set_params
),
cmd!(
"effect.lightingPreset",
"3D Lighting Preset",
[],
None,
"{index: int, preset: standard|diffuse|topLeft|right, item?: appearance item index|null (omitted: the active item, else the object's), ids?|id?: [..], target?: object|contents} replace an applied 3D effect's light rig and ambient with an original lighting preset; preserve shading, geometry and view; all targets must have a 3D effect at index; one undo step → {ids}",
has_doc,
lighting_preset
),
cmd!(
"effect.expandAppearance",
"Expand Appearance",
@@ -300,6 +309,30 @@ pub(crate) fn set_params_effects(s: &Session, p: &Value) -> Vec<String> {
ids.iter().filter_map(effect).collect()
}
fn lighting_preset(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "effect.lightingPreset";
let patch = str_param(p, "preset")
.and_then(effects::lighting_preset_params)
.ok_or_else(|| bad(C, "preset must be standard, diffuse, topLeft or right"))?;
let index = index_param(p, "index", C)?;
let item = item_target(s, p, C)?;
// Validate the whole selection before changing any object. An unrelated effect or
// a missing index must not leave just some objects with a new lighting rig.
for id in appearance_targets(s, p)? {
let n = s.doc()?.doc.node(id).ok_or(EngineError::NoNode(id))?;
let slot = item.effects_item(&n.appearance, C)?;
let effect = n.appearance.effects_at(slot).and_then(|fx| fx.get(index));
if !effect.is_some_and(|e| effects::is_three_d(&e.id)) {
return Err(bad(C, format!("object {} needs a 3D effect at index {index}", id.0)));
}
}
let mut options = p.clone();
let map = options.as_object_mut().ok_or_else(|| bad(C, "parameters must be an object"))?;
map.remove("preset");
map.insert("params".into(), patch);
set_params(s, &options)
}
fn set_params(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "effect.setParams";
let index = index_param(p, "index", C)?;
@@ -673,6 +706,10 @@ fn expand_appearance(s: &mut Session, p: &Value) -> Result<Value> {
Ok(out)
}
#[cfg(test)]
#[path = "tests_lighting_presets.rs"]
mod lighting_presets_tests;
#[cfg(test)]
mod tests {
use serde_json::json;
@@ -0,0 +1,93 @@
#![allow(clippy::unwrap_used, clippy::panic)]
use serde_json::{Value, json};
use vectorcraft_doc::NodeId;
use vectorcraft_render::effects::{LIGHTING_PRESETS, matching_lighting_preset};
use crate::Session;
fn setup(effect: &str, item: Value) -> (Session, NodeId) {
let mut s = Session::new();
s.execute("file.new", &json!({"width":400,"height":400})).unwrap();
let id = NodeId(s.execute("path.create", &json!({"d":"M100 100L200 100L200 200L100 200Z"})).unwrap()["id"].as_u64().unwrap());
s.execute("paint.setFill", &json!({"color":"#338ad6"})).unwrap();
s.execute(
"effect.apply",
&json!({"effect":effect,"item":item,"params":{
"rotationX":13,"rotationY":17,"rotationZ":9,"shade":false,"ambient":17,
"lights":[{"color":"#ff8040"},{"color":"#4080ff","rotation":-35}]
}}),
)
.unwrap();
(s, id)
}
#[test]
fn lighting_presets_preserve_source_and_view_and_round_trip_in_one_undo_step() {
for effect in ["threeD.revolve", "threeD.extrude", "threeD.inflate", "threeD.rotate"] {
for (i, preset) in LIGHTING_PRESETS.iter().enumerate() {
let (mut s, id) = setup(effect, Value::Null);
s.doc_mut().unwrap().mark_saved();
let before = s.doc().unwrap().doc.clone();
let history = s.doc().unwrap().history.undo.len();
let p = json!({"index":0,"item":null,"preset":preset.id});
s.check_params("effect.lightingPreset", &p).unwrap();
s.execute("effect.lightingPreset", &p).unwrap();
let after = s.doc().unwrap().doc.clone();
let actual = &after.node(id).unwrap().appearance.effects[0].params;
let mut expected = before.node(id).unwrap().appearance.effects[0].params.clone();
expected.as_object_mut().unwrap().extend(preset.params().as_object().unwrap().clone());
assert_eq!(*actual, expected);
assert_eq!(matching_lighting_preset(actual), Some(i));
assert_eq!(after.node(id).unwrap().path_data(), before.node(id).unwrap().path_data());
assert_eq!(actual["rotationX"], 13);
assert_eq!(actual["shade"], false);
assert_eq!(s.doc().unwrap().history.undo.len(), history + 1);
let reopened = vectorcraft_format::load(&vectorcraft_format::save(&after, false)).unwrap();
assert_eq!(reopened.node(id).unwrap().appearance.effects[0].params, *actual);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(s.doc().unwrap().doc, before);
assert!(!s.doc().unwrap().is_dirty());
s.execute("edit.redo", &json!({})).unwrap();
assert_eq!(s.doc().unwrap().doc, after);
}
}
}
#[test]
fn lighting_presets_target_fill_effects_and_reject_invalid_requests_atomically() {
let (mut s, id) = setup("threeD.rotate", json!(0));
for p in [
json!({"index":0,"item":0,"preset":"unknown"}),
json!({"index":0,"item":0,"preset":12}),
json!({"index":1,"item":0,"preset":"right"}),
json!({"index":0,"item":null,"preset":"right"}),
json!({"index":0,"item":0,"preset":"right","params":{"ambient":200}}),
] {
s.doc_mut().unwrap().mark_saved();
let before = s.doc().unwrap().doc.clone();
let history = s.doc().unwrap().history.undo.len();
assert!(s.execute("effect.lightingPreset", &p).is_err(), "{p}");
assert_eq!(s.doc().unwrap().doc, before);
assert_eq!(s.doc().unwrap().history.undo.len(), history);
assert!(!s.doc().unwrap().is_dirty());
}
s.execute("effect.lightingPreset", &json!({"ids":[id.0],"index":0,"item":0,"preset":"topLeft"})).unwrap();
let n = s.doc().unwrap().doc.node(id).unwrap();
assert!(n.appearance.effects.is_empty());
assert_eq!(n.appearance.effects_at(Some(0)).unwrap()[0].params["lightRotation"], 135.0);
}
#[test]
fn lighting_presets_reject_a_mixed_selection_before_changing_any_effect() {
let (mut s, first) = setup("threeD.rotate", Value::Null);
let second = s.execute("shape.rectangle", &json!({"x":250,"y":100,"width":80,"height":80})).unwrap()["id"].clone();
s.execute("effect.apply", &json!({"effect":"path.offsetPath","item":null})).unwrap();
s.execute("select.set", &json!({"ids":[first.0,second]})).unwrap();
s.doc_mut().unwrap().mark_saved();
let before = s.doc().unwrap().doc.clone();
let history = s.doc().unwrap().history.undo.len();
assert!(s.execute("effect.lightingPreset", &json!({"index":0,"item":null,"preset":"right"})).is_err());
assert_eq!(s.doc().unwrap().doc, before);
assert_eq!(s.doc().unwrap().history.undo.len(), history);
assert!(!s.doc().unwrap().is_dirty());
}
+21
View File
@@ -293,6 +293,7 @@ fn lighting_controls(ui: &mut egui::Ui, d: &mut Dialog) {
}
});
ui.add_enabled_ui(d.bool("shade"), |ui| {
lighting_presets(ui, d);
light_list(ui, d);
let mut selected = selected_light(d);
let before = light_record(&selected);
@@ -310,6 +311,26 @@ fn lighting_controls(ui: &mut egui::Ui, d: &mut Dialog) {
});
}
fn lighting_presets(ui: &mut egui::Ui, d: &mut Dialog) {
let names = [tl!("Standard"), tl!("Diffuse"), tl!("Top Left"), tl!("Right")];
let current = vectorcraft_effects::matching_lighting_preset(&super::form::params(d));
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 6.0;
label(ui, tl!("Presets"));
let shown = current.and_then(|i| names.get(i).copied()).unwrap_or(tl!("Custom"));
if let Some(preset) = widgets::dropdown(ui, "three-d-lighting-presets", shown, &names, VALUE_W + rail_width(ui) + 6.0)
.and_then(|i| vectorcraft_effects::LIGHTING_PRESETS.get(i))
&& let Some(patch) = preset.params().as_object()
{
d.fields.extend(patch.clone());
d.fields.insert("__light".into(), json!(0));
ui.memory_mut(|m| m.stop_text_input());
super::revolve_gizmo::clear(ui.ctx());
}
});
ui.add_space(4.0);
}
const LIGHT_KEYS: [(&str, &str); 6] = [
("color", "lightColor"),
("intensity", "lightIntensity"),
@@ -438,3 +438,63 @@ fn light_list_stops_at_ten_and_cancel_restores_the_source() {
dialogs::cancel(&mut h.app);
assert_eq!(h.app.session.doc().unwrap().doc, before);
}
#[test]
fn lighting_presets_preview_custom_edits_reset_and_commit_as_one_undo_step() {
for effect in [REVOLVE, EXTRUDE, INFLATE, vectorcraft_effects::ROTATE] {
let mut h = Harness::with("M100 100L200 100L200 200L100 200Z", effect, vec2(1280.0, 900.0));
let before = h.app.session.doc().unwrap().doc.clone();
h.settle();
h.click(h.rect("Lighting").center());
let view = h.app.ui.dialog.as_ref().unwrap().fields["rotationX"].clone();
h.click(h.rect("Standard").center());
h.click(h.rect("Right").center());
assert_eq!(h.value("lightRotation"), 0.0);
assert_eq!(h.app.ui.dialog.as_ref().unwrap().fields["rotationX"], view);
h.drag(h.rect("Height").center(), vec2(20.0, 0.0));
assert!(!h.rect("Custom").is_negative());
h.click(h.rect("Custom").center());
h.click(h.rect("Diffuse").center());
assert_eq!(h.value("lightSoftness"), 100.0);
assert_eq!(h.value("ambient"), 35.0);
h.click(h.rect("Add Light").center());
assert!(!h.rect("Custom").is_negative());
h.click(h.rect("Custom").center());
h.click(h.rect("Top Left").center());
let d = h.app.ui.dialog.as_ref().unwrap();
assert!(d.fields["lights"].is_null());
assert_eq!(d.fields["__light"], 0);
assert_eq!(d.f64("lightRotation", 0.0), 135.0);
h.click(h.response("revolve-reset-all").center());
assert!(!h.rect("Standard").is_negative());
h.click(h.rect("Standard").center());
h.click(h.rect("Right").center());
dialogs::confirm(&mut h.app).unwrap();
h.app.run("edit.undo", json!({})).unwrap();
assert_eq!(h.app.session.doc().unwrap().doc, before);
}
}
#[test]
fn lighting_presets_cancel_restores_an_existing_custom_light_rig() {
let mut h = Harness::new(vec2(1280.0, 900.0));
h.settle();
dialogs::confirm(&mut h.app).unwrap();
h.app
.run(
"effect.setParams",
json!({"index":0,"item":null,"params":{
"ambient":19,"lights":[{"color":"#ff8040"},{"color":"#4080ff"}]
}}),
)
.unwrap();
let before = h.app.session.doc().unwrap().doc.clone();
h.app.run("effect.dialog", json!({"effect":REVOLVE,"index":0,"item":null})).unwrap();
h.settle();
h.click(h.rect("Lighting").center());
h.click(h.rect("Custom").center());
h.click(h.rect("Right").center());
assert_ne!(h.app.session.doc().unwrap().doc, before);
dialogs::cancel(&mut h.app);
assert_eq!(h.app.session.doc().unwrap().doc, before);
}
+3
View File
@@ -5065,6 +5065,9 @@ theme Appearance Външен вид
Softness Мекота
Ambient Light Околна светлина
Light behind the object Светлина зад обекта
3D Lighting Preset Предварителна настройка за 3D осветление
Standard Стандартно
Diffuse Дифузно
# 3D light management.
Lights Светлини
Add Light Добавяне на светлина
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5165,6 +5165,9 @@ mark-style Japanese Japonské
Softness Měkkost
Ambient Light Okolní světlo
Light behind the object Světlo za objektem
3D Lighting Preset Předvolba 3D osvětlení
Standard Standardní
Diffuse Rozptýlené
# 3D light management.
Lights Světla
Add Light Přidat světlo
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4828,6 +4828,9 @@ mark-style Japanese Japansk
Softness Blødhed
Ambient Light Omgivelseslys
Light behind the object Lys bag objektet
3D Lighting Preset Forudindstilling for 3D-belysning
Standard Standard
Diffuse Diffust
# 3D light management.
Lights Lyskilder
Add Light Tilføj lyskilde
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4741,6 +4741,9 @@ effect Soft Weich
Softness Weichheit
Ambient Light Umgebungslicht
Light behind the object Licht hinter dem Objekt
3D Lighting Preset 3D-Beleuchtungsvorgabe
Standard Standard
Diffuse Diffus
# 3D light management.
Lights Lichter
Add Light Licht hinzufügen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4872,6 +4872,9 @@ theme Appearance Εμφάνιση
to set spacing and orientation για ορισμό απόστασης και προσανατολισμού
: :
Light behind the object Φως πίσω από το αντικείμενο
3D Lighting Preset Προεπιλογή φωτισμού 3D
Standard Τυπικός
Diffuse Διάχυτος
# 3D light management.
Lights Φώτα
Add Light Προσθήκη φωτός
Can't render this file because it contains an unexpected character in line 2031 and column 19.
+3
View File
@@ -5106,6 +5106,9 @@ mark-style Japanese Japonesas
Softness Suavidad
Ambient Light Luz ambiental
Light behind the object Luz detrás del objeto
3D Lighting Preset Ajuste preestablecido de iluminación 3D
Standard Estándar
Diffuse Difusa
# 3D light management.
Lights Luces
Add Light Añadir luz
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5054,6 +5054,9 @@ mark-style Japanese Japanilainen
Softness Pehmeys
Ambient Light Ympäristövalo
Light behind the object Valo objektin takana
3D Lighting Preset 3D-valaistuksen esiasetus
Standard Vakio
Diffuse Hajavalo
# 3D light management.
Lights Valot
Add Light Lisää valo
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5165,6 +5165,9 @@ theme Appearance Apparence
Softness Douceur
Ambient Light Lumière ambiante
Light behind the object Lumière derrière l'objet
3D Lighting Preset Préréglage d’éclairage 3D
Standard Standard
Diffuse Diffus
# 3D light management.
Lights Lumières
Add Light Ajouter une lumière
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5151,6 +5151,9 @@ imageTrace Outline Körvonal
Softness Lágyság
Ambient Light Környezeti fény
Light behind the object Fény az objektum mögött
3D Lighting Preset 3D világítási készlet
Standard Normál
Diffuse Szórt
# 3D light management.
Lights Fények
Add Light Fény hozzáadása
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4828,6 +4828,9 @@ mark-style Japanese Jepang
Softness Kelembutan
Ambient Light Cahaya ambient
Light behind the object Cahaya di belakang objek
3D Lighting Preset Preset Pencahayaan 3D
Standard Standar
Diffuse Difus
# 3D light management.
Lights Lampu
Add Light Tambah Lampu
Can't render this file because it contains an unexpected character in line 3801 and column 19.
+3
View File
@@ -4775,6 +4775,9 @@ lighting Light Luce
Softness Morbidezza
Ambient Light Luce ambientale
Light behind the object Luce dietro l'oggetto
3D Lighting Preset Predefinito illuminazione 3D
Standard Standard
Diffuse Diffusa
# 3D light management.
Lights Luci
Add Light Aggiungi luce
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4814,6 +4814,9 @@ lighting Light ライト
Softness 柔らかさ
Ambient Light 環境光
Light behind the object オブジェクトの背後のライト
3D Lighting Preset 3D ライティングプリセット
Standard 標準
Diffuse 拡散
# 3D light management.
Lights ライト一覧
Add Light ライトを追加
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4740,6 +4740,9 @@ imageTrace Outline კონტური
In the File ფაილში
Keys კლავიშები
Light behind the object სინათლე ობიექტის უკან
3D Lighting Preset 3D განათების წინასწარი პარამეტრი
Standard სტანდარტული
Diffuse დიფუზური
Line {line} სტრიქონი {line}
Line {line}: {name}: {status} სტრიქონი {line}: {name}: {status}
Needs Attention Only მხოლოდ საყურადღებო
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4738,6 +4738,9 @@ lighting Light 광원
Softness 부드러움
Ambient Light 주변광
Light behind the object 개체 뒤의 조명
3D Lighting Preset 3D 조명 사전 설정
Standard 표준
Diffuse 확산
# 3D light management.
Lights 광원 목록
Add Light 광원 추가
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4840,6 +4840,9 @@ mark-style Japanese Japans
Softness Zachtheid
Ambient Light Omgevingslicht
Light behind the object Licht achter het object
3D Lighting Preset Voorinstelling 3D-belichting
Standard Standaard
Diffuse Diffuus
# 3D light management.
Lights Lichtbronnen
Add Light Lichtbron toevoegen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5002,6 +5002,9 @@ mark-style Japanese Japońskie
Softness Miękkość
Ambient Light Światło otoczenia
Light behind the object Światło za obiektem
3D Lighting Preset Ustawienie oświetlenia 3D
Standard Standardowe
Diffuse Rozproszone
# 3D light management.
Lights Światła
Add Light Dodaj światło
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4082,6 +4082,9 @@ axis Both Ambos
Softness Suavidade
Ambient Light Luz ambiente
Light behind the object Luz atrás do objeto
3D Lighting Preset Predefinição de iluminação 3D
Standard Padrão
Diffuse Difusa
# 3D light management.
Lights Luzes
Add Light Adicionar luz
Can't render this file because it contains an unexpected character in line 6 and column 47.
+3
View File
@@ -4771,6 +4771,9 @@ lighting Light Свет
Softness Мягкость
Ambient Light Окружающий свет
Light behind the object Свет позади объекта
3D Lighting Preset Предустановка 3D освещения
Standard Стандартное
Diffuse Рассеянное
# 3D light management.
Lights Источники света
Add Light Добавить свет
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5070,6 +5070,9 @@ theme Appearance Utseende
Softness Mjukhet
Ambient Light Omgivande ljus
Light behind the object Ljus bakom objektet
3D Lighting Preset Förinställning för 3D-belysning
Standard Standard
Diffuse Diffust
# 3D light management.
Lights Ljuskällor
Add Light Lägg till ljus
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -4755,6 +4755,9 @@ lighting Light Світло
Softness М'якість
Ambient Light Навколишнє світло
Light behind the object Світло позаду об'єкта
3D Lighting Preset Попереднє налаштування 3D освітлення
Standard Стандартне
Diffuse Розсіяне
# 3D light management.
Lights Джерела світла
Add Light Додати світло
Can't render this file because it contains an unexpected character in line 726 and column 40.
+3
View File
@@ -4757,6 +4757,9 @@ theme Appearance Giao diện
Softness Độ mềm
Ambient Light Ánh sáng môi trường
Light behind the object Ánh sáng phía sau đối tượng
3D Lighting Preset Thiết lập sẵn ánh sáng 3D
Standard Tiêu chuẩn
Diffuse Khuếch tán
# 3D light management.
Lights Danh sách đèn
Add Light Thêm đèn
Can't render this file because it contains an unexpected character in line 4003 and column 10.
+3
View File
@@ -5030,6 +5030,9 @@ lighting Light 光源
Softness 柔和度
Ambient Light 环境光
Light behind the object 光源位于对象后方
3D Lighting Preset 3D 光照预设
Standard 标准
Diffuse 漫射
# 3D light management.
Lights 光源列表
Add Light 添加光源
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -4837,6 +4837,9 @@ lighting Light 光源
Softness 柔和度
Ambient Light 環境光
Light behind the object 光源位於物件後方
3D Lighting Preset 3D 光照預設
Standard 標準
Diffuse 漫射
# 3D light management.
Lights 光源清單
Add Light 新增光源
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4 -3
View File
@@ -1,6 +1,6 @@
# Effects parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (G2: Effect › Sketch › Note Paper; Halftone Pattern and Note Paper defaults documented as unverified VectorCraft choices; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 46/57, 11 remaining (8–14 h); G2: Sketch › Chrome; Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 37/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 46/57, 11 remaining (8–14 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; G2: Effect › Sketch › Note Paper; Halftone Pattern and Note Paper defaults documented as unverified VectorCraft choices; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 46/57, 11 remaining (8–14 h); G2: Sketch › Chrome; Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 37/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 46/57, 11 remaining (8–14 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x)
Every entry of Illustrator's Effect menu, by its public feature name, against VectorCraft's effects catalogue
(`effect_catalog()` in `crates/effects/src/lib.rs`) and the Effect menu built from it
@@ -165,19 +165,20 @@ for VectorCraft and are not verified against proprietary defaults. Evidence: `cr
|---|---|
| Revolve | initial: live, editable profile, rotation and light gizmos, default-on Cap with continuous curved rims, smooth vector shading and round seam joins, SVG/PDF output, Expand Appearance ([revolve.md](revolve.md), #846) |
| Extrude & Bevel, Inflate, Rotate | initial: live, depth, twist, taper, solid or hollow caps, five original bevel shapes, Inflate volume, both sides, twist, taper and cap, Rotate of every fill and stroke, Position presets, rotation and light gizmos, SVG/PDF output, Expand Appearance ([three-d.md](three-d.md)) |
| Materials panel, lighting, ray-traced render | one to ten directional lights with per-light Color (opaque RGB, white by default), Intensity, Rotation, Height, Softness and neutral Ambient Light, Revolve and colored Inflate use smooth vector shading; Extrude & Bevel and Rotate retain planar shading; no materials, light presets, shadows or ray tracing |
| Materials panel, lighting, ray-traced render | one to ten directional lights with per-light Color (opaque RGB, white by default), Intensity, Rotation, Height, Softness and neutral Ambient Light, Revolve and colored Inflate use smooth vector shading; Extrude & Bevel and Rotate retain planar shading; original Standard, Diffuse, Top Left and Right lighting rigs; no materials, shadows or ray tracing |
| 3D (Classic): Extrude & Bevel, Revolve, Rotate | none |
| Turntable (30.0 beta) | none |
| Export 3D objects (OBJ, USDA, glTF) | none |
The `three-d` crate (depends on `geom` and `pathops`, with no UI or GPU dependencies) builds, rotates, projects, shades and depth-sorts the surfaces;
the effects crate turns them into art. What's left (materials, light presets and shadows, ray-traced rendering,
the effects crate turns them into art. What's left (materials and shadows, ray-traced rendering,
angular cut caps, exact visibility, 3D Classic, Turntable, 3D export) is 25–45 h.
## Revision history
| Date | Change | Summary |
|---|---|---|
| 2026-10-21 | minor | 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged |
| 2026-10-20 | minor | G2: Effect › Sketch › Water Paper (Fiber Length, Brightness, Contrast; colours flow on damp fibrous paper, defaults unverified, #2955); measured Photoshop filters 47/57, Sketch 12/14. |
| 2026-10-20 | minor | G2: Effect › Sketch › Note Paper (Image Balance, Graininess, Relief; embossed tones on fibrous paper); Halftone Pattern and Note Paper parameter ranges follow public control names, numeric defaults are unverified VectorCraft choices (public help gives no numbers); measured Photoshop filters 46/57, Sketch 11/14. |
| 2026-10-19 | minor | Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap landed (#2972, #2967, #2976, #3034): 36/57 Photoshop effects (estimated; defaults unverified) |
+4 -3
View File
File diff suppressed because one or more lines are too long
+18
View File
@@ -1440,6 +1440,24 @@ See [Revolve](revolve.md) for the scope and limitations.
{"name":"run_command","arguments":{"command":"effect.expandAppearance","params":{}}}
```
## 3D lighting presets
`effect.lightingPreset {index: int, preset: "standard" | "diffuse" | "topLeft" | "right", item?, ids?, target?}`
replaces the lighting rig and ambient of an existing Revolve, Extrude & Bevel, Inflate or Rotate
effect. `item: null` addresses object-level effects; an item index addresses a fill/stroke's
effects. All targets must have a 3D effect at `index`, otherwise nothing changes. The operation
is one undo step and preserves Shading, geometry, source paint and the view.
The four original rigs use one white front light at 80% intensity. Standard keeps the existing
default; Diffuse uses 100% Softness and 35% Ambient Light, Top Left uses Rotation 135 and Height
45, and Right uses Rotation 0 and Height 30. Full values and the public behavior reference are in
[3D lighting presets](three-d.md#presets). These are original settings, not imported preset files.
The document stores their numeric settings; manual edits remain possible through `effect.setParams`.
```json
{"name":"run_command","arguments":{"command":"effect.lightingPreset","params":{"index":0,"item":null,"preset":"topLeft"}}}
```
## Extrude & Bevel, Inflate and Rotate
`effect.apply {effect: "threeD.extrude" | "threeD.inflate" | "threeD.rotate", params: {...}, ids?, item?}` applies
+2
View File
@@ -86,6 +86,8 @@ with the standard command.
## Initial limits
- Lighting offers Standard, Diffuse, Top Left and Right, using the [shared original rigs](three-d.md#presets).
Other effective settings show Custom; changing the view does not change the recognized lighting preset.
- Paths and compound paths only. Type, images and groups must be converted to paths first.
- Open profiles have circular end caps by default; turning Cap off keeps them open. Partial-sweep angular cut sides remain open. Closed profile boundaries are swept as provided.
- One Revolve per source object. Edit its options rather than applying another.
+3 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30 -1
View File
@@ -101,6 +101,34 @@ saves the angle.
## Lighting
### Presets
The Lighting tab has **Standard**, **Diffuse**, **Top Left** and **Right** choices. Choosing one
replaces the whole light rig with one white front light at 80% intensity and sets Ambient Light.
It leaves Shading, the object's view, geometry and other effect options unchanged. Manual edits
remain available; the list shows **Custom** whenever the effective lighting matches none of the
presets, including when there is more than one light. An equivalent explicit single light is
recognized too. Reset all returns to Standard. Preview, Cancel and Undo work as for other options.
These are original VectorCraft settings based on the public preset names and directions. Adobe's
documentation does not publish numeric rigs; the values are not copied from its software or files
and are not claimed to reproduce its presets exactly. Standard retains the existing default.
| Preset | Rotation | Height | Softness | Ambient Light |
|---|---:|---:|---:|---:|
| Standard | 125.26438968275465 degrees | 30 degrees | 0% | 25% |
| Diffuse | 125.26438968275465 degrees | 30 degrees | 100% | 35% |
| Top Left | 135 degrees | 45 degrees | 0% | 25% |
| Right | 0 degrees | 30 degrees | 0% | 25% |
`effect.lightingPreset {index: 0, item: null, preset: "standard" | "diffuse" | "topLeft" | "right", ids?, target?}`
applies the same settings through commands, CLI and MCP. The index and item address an already
applied 3D effect. Every target must have a 3D effect at that position; validation is atomic, and
the whole operation is one undo step. The numeric light settings are saved, not a preset name.
Existing documents keep their appearance unless a preset is chosen.
### Light settings
One to ten directional lights. **Lights** selects the light whose settings and artboard gizmo are edited. **Add Light** adds and selects a white front light; **Delete Light** removes the selected light while retaining at least one. Ambient Light is shared by all lights. The public panel options' Light Settings are kept in their order:
| Option | |
@@ -138,7 +166,7 @@ and [Create 3D objects](https://helpx.adobe.com/illustrator/desktop/special-effe
pages, and the archived Extrude & Bevel option list (depth 0–2,000, cap on/off, bevel height, bevel extent in or
out, perspective). No Adobe software, assets, presets or bevel shapes were used.
- **Materials** (base, Adobe Substance and graphics), light presets, **shadows**
- **Materials** (base, Adobe Substance and graphics), **shadows**
and **ray-traced rendering** are not built: shading uses directional lights, in the paint of the fill or
stroke the solid is built for ([Fills and strokes](#fills-and-strokes)).
- The bevel shapes and their names are our own; custom bevel paths are not supported.
@@ -161,4 +189,5 @@ Release measurements and a repeatable CLI scene are in [3D lighting performance]
| Date | Change |
|---|---|
| 2026-10-21 | Four original lighting presets, shared by all four 3D effects and commands, with effective-setting recognition and existing preview/history behavior. |
| 2026-10-17 | Smooth colored Inflate lighting and surface batching; legacy white-light appearance and the 0.05 seam minimum preserved; shared light Color and multiple lights remain vector output. |
+3 -2
View File
File diff suppressed because one or more lines are too long