Merge pull request #294 from pat-the-wizard/docs/261-environment-light

3D: layer.newLight lists the Environment light and how to set its source; parity notes reflections (#261)
This commit is contained in:
Brandon Thomas
2026-10-08 23:50:36 -04:00
3 changed files with 36 additions and 7 deletions
+10 -6
View File
@@ -220,7 +220,9 @@ fn new_light(s: &mut Session, p: &Value) -> Result<Value> {
let cid = comp_id(s, p)?;
let comp = s.project.comp(cid).ok_or(EngineError::NoComp)?.clone();
let kind = match p.get("kind").or_else(|| p.get("type")) {
Some(_) => light_kind_p(p, "kind").or_else(|| light_kind_p(p, "type")).ok_or_else(|| bad("layer.newLight", "kind: Parallel|Spot|Point|Ambient"))?,
Some(_) => {
light_kind_p(p, "kind").or_else(|| light_kind_p(p, "type")).ok_or_else(|| bad("layer.newLight", "kind: Parallel|Spot|Point|Ambient|Environment"))?
}
None => LightKind::Spot,
};
let name = str_p(p, "name").unwrap_or("Light 1").to_string();
@@ -239,9 +241,11 @@ fn light_settings(s: &mut Session, p: &Value) -> Result<Value> {
let t = time_p(s, p, Some(&comp));
let name = str_p(p, "name").map(str::to_string);
let kind = match p.get("kind").or_else(|| p.get("type")) {
Some(_) => {
Some(light_kind_p(p, "kind").or_else(|| light_kind_p(p, "type")).ok_or_else(|| bad("layer.lightSettings", "kind: Parallel|Spot|Point|Ambient"))?)
}
Some(_) => Some(
light_kind_p(p, "kind")
.or_else(|| light_kind_p(p, "type"))
.ok_or_else(|| bad("layer.lightSettings", "kind: Parallel|Spot|Point|Ambient|Environment"))?,
),
None => None,
};
s.edit("Light Settings", None, |proj, _| {
@@ -662,7 +666,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Light...",
["Layer", "New"],
Some("Cmd+Alt+Shift+L"),
"{kind?: Parallel|Spot|Point|Ambient (default Spot), name?, color?, intensity?, coneAngle?, coneFeather?, falloff?: None|Smooth|Inverse Square Clamped, radius?, falloffDistance?, castsShadows?, shadowDarkness?, shadowDiffusion?, position? [x,y,z], poi? [x,y,z]}",
"{kind?: Parallel|Spot|Point|Ambient|Environment (default Spot; Environment lights and reflects from an equirectangular image layer: prop.set its lightOptions/source to that layer's id, or leave it empty for the comp's Environment Layer, see layer.environment), name?, color?, intensity?, coneAngle?, coneFeather?, falloff?: None|Smooth|Inverse Square Clamped, radius?, falloffDistance?, castsShadows?, shadowDarkness?, shadowDiffusion?, position? [x,y,z], poi? [x,y,z]}",
has_comp,
new_light
),
@@ -689,7 +693,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Light Settings...",
[],
None,
"{layer?, name?, kind?, color?, intensity?, coneAngle?, coneFeather?, falloff?, radius?, falloffDistance?, castsShadows?, shadowDarkness?, shadowDiffusion?, position?, poi?}",
"{layer?, name?, kind?: Parallel|Spot|Point|Ambient|Environment, color?, intensity?, coneAngle?, coneFeather?, falloff?, radius?, falloffDistance?, castsShadows?, shadowDarkness?, shadowDiffusion?, position?, poi?}",
has_comp,
light_settings
),
+25
View File
@@ -106,6 +106,31 @@ fn new_light_and_change_type() {
assert!(s.execute("layer.newLight", json!({"kind": "Laser"})).is_err());
}
/// The Environment light is listed in `layer.newLight`'s help and in the kind errors, and takes
/// its equirectangular source as a layer id (#261).
#[test]
fn environment_light_is_listed_and_takes_a_source_layer() {
let mut s = session();
let spec = crate::command_specs().iter().find(|c| c.id == "layer.newLight").unwrap();
assert!(spec.params.contains("lightOptions/source"), "{}", spec.params);
let e = s.execute("layer.newLight", json!({"kind": "Laser"})).unwrap_err().to_string();
let point = s.execute("layer.newLight", json!({"kind": "Point"})).unwrap()["layer"].as_u64().unwrap();
let e2 = s.execute("layer.lightSettings", json!({"layer": point, "kind": "Laser"})).unwrap_err().to_string();
for k in LightKind::ALL {
assert!(spec.params.contains(k.label()), "help lists {}: {}", k.label(), spec.params);
assert!(e.contains(k.label()) && e2.contains(k.label()), "{e} / {e2}");
}
let sky = s.execute("layer.newSolid", json!({"width": 256, "height": 128, "name": "Sky"})).unwrap()["layer"].as_u64().unwrap();
let env = s.execute("layer.newLight", json!({"kind": "Environment", "name": "Env"})).unwrap()["layer"].as_u64().unwrap();
let source = s.active_comp().unwrap().layer(effectcraft_project::LayerId(env)).unwrap().source.clone();
assert_eq!(source, LayerSource::Light { kind: LightKind::Environment });
assert_eq!(prop(&s, env, "lightOptions/source").as_layer(), None, "empty: the comp's Environment Layer");
s.execute("prop.set", json!({"layer": env, "path": "lightOptions/source", "value": sky})).unwrap();
assert_eq!(prop(&s, env, "lightOptions/source").as_layer(), Some(sky));
s.undo();
assert_eq!(prop(&s, env, "lightOptions/source").as_layer(), None);
}
#[test]
fn three_d_switch_undo_redo() {
let mut s = session();