From ab6bff5e4e9a67d7a2e63ed65e0ad82179549c37 Mon Sep 17 00:00:00 2001 From: Marco Arnulfo <103679419+marcoarnulfo@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:52:00 +0200 Subject: [PATCH] SETVAR CECOLOR takes the colour names and indexes the color command takes (fixes #1999) (#2014) --- crates/engine/src/cmd/mod.rs | 1 + crates/engine/src/cmd/props.rs | 21 +++++++++- crates/engine/src/sysvars.rs | 2 + crates/engine/tests/it/main.rs | 1 + crates/engine/tests/it/setvar_cecolor.rs | 51 ++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 crates/engine/tests/it/setvar_cecolor.rs diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index e1185c14..74bc7f69 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -33,6 +33,7 @@ pub mod plotters; mod props; pub use draw2::breakline::BreaklineOptions; pub use props::MatchProps; +pub(crate) use props::set_current_color_value; mod qselect; mod selecttools; mod settings; diff --git a/crates/engine/src/cmd/props.rs b/crates/engine/src/cmd/props.rs index 2d408b00..7ef3aa68 100644 --- a/crates/engine/src/cmd/props.rs +++ b/crates/engine/src/cmd/props.rs @@ -418,7 +418,25 @@ fn run_set(s: &mut Session, p: &Value) -> Result { fn run_color(s: &mut Session, p: &Value) -> Result { let c = p.get("color").and_then(parse_color).ok_or_else(|| bad("color", "`color` is required"))?; - let d = s.doc_mut()?; + set_current_color(s.doc_mut()?, c); + ok() +} + +/// `setvar CECOLOR`: the same input as the `color` command, stored the same way. +pub(crate) fn set_current_color_value(s: &mut Session, v: &Value) -> Option<()> { + // An out-of-range index is refused here, where `color` folds it to ByLayer. + let c = match v { + Value::String(t) => Color::parse(t), + Value::Number(n) => n.as_i64().and_then(|i| Color::parse(&i.to_string())), + _ => None, + }?; + set_current_color(s.doc_mut().ok()?, c); + Some(()) +} + +/// A true colour keeps its RGB in CECOLOR_RGB (which wins for new objects); any other colour must +/// clear a stale one. +fn set_current_color(d: &mut cadcraft_doc::Drawing, c: Color) { match c { Color::True(rgb) => { d.header.set_i64("CECOLOR", i64::from(cadcraft_color::nearest_aci(rgb))); @@ -429,7 +447,6 @@ fn run_color(s: &mut Session, p: &Value) -> Result { d.header.vars.remove("CECOLOR_RGB"); } } - ok() } mod linfile; diff --git a/crates/engine/src/sysvars.rs b/crates/engine/src/sysvars.rs index b77f0465..89bde769 100644 --- a/crates/engine/src/sysvars.rs +++ b/crates/engine/src/sysvars.rs @@ -276,6 +276,8 @@ pub fn set(s: &mut Session, name: &str, v: &Value) -> Result<()> { let st = &mut s.settings; match n.as_str() { "OSMODE" => st.osmode = v.as_u64().or_else(|| v.as_str().and_then(|s| s.trim().parse().ok())).ok_or_else(bad)? as u32 & 0x7fff, + // The `color` command's input: ByLayer, ByBlock, a name, an index or a true colour. + "CECOLOR" => crate::cmd::set_current_color_value(s, v).ok_or_else(bad)?, "OSNAPHATCH" => st.osnaphatch = as_bool(v).ok_or_else(bad)?, "ANNOAUTOSCALE" => st.annoautoscale = as_i(v).filter(|i| (-4..=4).contains(i)).ok_or_else(bad)? > 0, // A scale of the drawing's list; ANNOAUTOSCALE adds it to the annotative objects. diff --git a/crates/engine/tests/it/main.rs b/crates/engine/tests/it/main.rs index b6e088a1..3edc0db9 100644 --- a/crates/engine/tests/it/main.rs +++ b/crates/engine/tests/it/main.rs @@ -108,6 +108,7 @@ mod select_similar; mod select_underscore_keywords; mod selection_cycling; mod selection_modes; +mod setvar_cecolor; mod setvar_coverage; mod shapes; mod snap_annotations; diff --git a/crates/engine/tests/it/setvar_cecolor.rs b/crates/engine/tests/it/setvar_cecolor.rs new file mode 100644 index 00000000..a8347ddc --- /dev/null +++ b/crates/engine/tests/it/setvar_cecolor.rs @@ -0,0 +1,51 @@ +//! `setvar CECOLOR` takes what the `color` command takes: "ByLayer", "ByBlock", a colour name or an +//! index (#1999). + +use cadcraft_engine::Session; +use cadcraft_engine::doc::color::{Color, Rgb}; +use serde_json::{Value, json}; + +fn cecolor(s: &mut Session) -> Value { + s.execute("getvar", &json!({ "name": "CECOLOR" })).unwrap()["value"].clone() +} + +fn new_line_color(s: &mut Session) -> Color { + s.execute("line", &json!({ "points": [[0, 0], [10, 0]] })).unwrap(); + s.doc().unwrap().model.last().unwrap().common.color +} + +#[test] +fn setvar_cecolor_accepts_names() { + for (value, aci) in [("BYLAYER", 256), ("ByLayer", 256), ("BYBLOCK", 0), ("red", 1), ("250", 250)] { + let mut s = Session::new(); + s.execute("setvar", &json!({ "name": "CECOLOR", "value": value })).unwrap_or_else(|e| panic!("{value}: {e}")); + assert_eq!(cecolor(&mut s), json!(aci), "{value}"); + } +} + +#[test] +fn setvar_cecolor_name_replaces_a_true_colour() { + let mut s = Session::new(); + s.execute("color", &json!({ "color": "10,20,30" })).unwrap(); + assert_eq!(new_line_color(&mut s), Color::True(Rgb(10, 20, 30))); + s.execute("setvar", &json!({ "name": "CECOLOR", "value": "ByLayer" })).unwrap(); + assert_eq!(cecolor(&mut s), json!(256)); + assert_eq!(new_line_color(&mut s), Color::ByLayer); +} + +#[test] +fn setvar_cecolor_number_replaces_a_true_colour() { + let mut s = Session::new(); + s.execute("color", &json!({ "color": "10,20,30" })).unwrap(); + s.execute("setvar", &json!({ "name": "CECOLOR", "value": 250 })).unwrap(); + assert_eq!(new_line_color(&mut s), Color::Index(250)); +} + +#[test] +fn setvar_cecolor_refuses_what_is_not_a_colour() { + let mut s = Session::new(); + for bad in [json!("chartreuse"), json!(257), json!(-1), json!(true)] { + assert!(s.execute("setvar", &json!({ "name": "CECOLOR", "value": bad })).is_err(), "{bad}"); + } + assert_eq!(cecolor(&mut s), json!(256)); +}