diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index 74bc7f697..1856c6a15 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -33,7 +33,7 @@ pub mod plotters; mod props; pub use draw2::breakline::BreaklineOptions; pub use props::MatchProps; -pub(crate) use props::set_current_color_value; +pub(crate) use props::{current_color_value, 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 c89204c22..0150c3390 100644 --- a/crates/engine/src/cmd/props.rs +++ b/crates/engine/src/cmd/props.rs @@ -431,7 +431,11 @@ fn run_color(s: &mut Session, p: &Value) -> Result { 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), + // "RGB:r,g,b" is how `getvar` reads a true colour back, so a saved value can be restored. + Value::String(t) => match t.trim().get(..4).filter(|p| p.eq_ignore_ascii_case("rgb:")).and_then(|_| t.trim().get(4..)) { + Some(triple) => Color::parse(triple).filter(|c| matches!(c, Color::True(_))), + None => Color::parse(t), + }, Value::Number(n) => n.as_i64().and_then(|i| Color::parse(&i.to_string())), _ => None, }?; @@ -439,6 +443,20 @@ pub(crate) fn set_current_color_value(s: &mut Session, v: &Value) -> Option<()> Some(()) } +/// `getvar CECOLOR` as AutoCAD gives it, always text: "BYLAYER", "BYBLOCK", the index ("1" for +/// red) or "RGB:r,g,b". +pub(crate) fn current_color_value(d: &cadcraft_doc::Drawing) -> String { + if let Some(rgb) = d.header.get("CECOLOR_RGB").and_then(|v| v.as_i64()).and_then(|v| u32::try_from(v).ok()) { + let cadcraft_color::Rgb(r, g, b) = cadcraft_color::Rgb::from_u32(rgb); + return format!("RGB:{r},{g},{b}"); + } + match Color::from_aci(d.header.i64("CECOLOR", 256) as i16) { + Color::ByLayer => "BYLAYER".into(), + Color::ByBlock => "BYBLOCK".into(), + other => other.to_aci().to_string(), + } +} + /// 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) { diff --git a/crates/engine/src/lisp/tests.rs b/crates/engine/src/lisp/tests.rs index 820a9812b..0d8e12ce0 100644 --- a/crates/engine/src/lisp/tests.rs +++ b/crates/engine/src/lisp/tests.rs @@ -455,6 +455,15 @@ fn getvar_drawing_and_session_variables() { assert_eq!(ok(&mut s, "(getvar \"DWGTITLED\")"), "1"); } +/// CECOLOR is a string in AutoCAD, whatever it was set from (#1999). +#[test] +fn getvar_cecolor_is_a_string() { + let mut s = session(); + assert_eq!(ok(&mut s, "(list (getvar \"CECOLOR\") (type (getvar \"CECOLOR\")))"), "(\"BYLAYER\" STR)"); + assert_eq!(ok(&mut s, "(setvar \"CECOLOR\" \"red\") (getvar \"CECOLOR\")"), "\"1\""); + assert_eq!(ok(&mut s, "(setvar \"CECOLOR\" \"10,20,30\") (getvar \"CECOLOR\")"), "\"RGB:10,20,30\""); +} + #[test] fn input_functions_take_supplied_answers() { let mut s = session(); diff --git a/crates/engine/src/sysvars.rs b/crates/engine/src/sysvars.rs index 042185772..e22529aac 100644 --- a/crates/engine/src/sysvars.rs +++ b/crates/engine/src/sysvars.rs @@ -146,6 +146,8 @@ pub fn get(s: &Session, name: &str) -> Option { // Held in radians (`$ANGBASE` in DXF is in degrees); reported in degrees, like POLARANG. "ANGBASE" => json!(s.doc().ok()?.header.f64("ANGBASE", 0.0).to_degrees()), "CONSTRAINTINFER" => json!(i32::from(s.doc().ok()?.parametric.settings.infer)), + // Text, as AutoCAD reads it back; the header keeps the index (and CECOLOR_RGB) for DXF. + "CECOLOR" => json!(crate::cmd::current_color_value(s.doc().ok()?)), // Process-wide font substitution (profile settings, not saved in the drawing). "FONTALT" => json!(cadcraft_fonts::ttf::font_alt()), "FONTFALLBACK" => json!(cadcraft_fonts::ttf::fallback_fonts()), @@ -447,7 +449,7 @@ pub fn list(s: &Session) -> Value { } if let Ok(d) = s.doc() { for (k, v) in &d.header.vars { - if matches!(k.as_str(), "ANGBASE" | "SNAPUNIT" | "GRIDUNIT") { + if matches!(k.as_str(), "ANGBASE" | "SNAPUNIT" | "GRIDUNIT" | "CECOLOR") { m.insert(k.clone(), get(s, k).unwrap_or(Value::Null)); } else if !k.starts_with("CADCRAFT_") { m.insert(k.clone(), serde_json::to_value(v).unwrap_or(Value::Null)); diff --git a/crates/engine/tests/it/setvar_cecolor.rs b/crates/engine/tests/it/setvar_cecolor.rs index a8347ddcb..e37ac2966 100644 --- a/crates/engine/tests/it/setvar_cecolor.rs +++ b/crates/engine/tests/it/setvar_cecolor.rs @@ -1,5 +1,6 @@ //! `setvar CECOLOR` takes what the `color` command takes: "ByLayer", "ByBlock", a colour name or an -//! index (#1999). +//! index (#1999). `getvar CECOLOR` reads it back as AutoCAD does, as text: "BYLAYER", "BYBLOCK", +//! the index ("1" for red) or "RGB:r,g,b". use cadcraft_engine::Session; use cadcraft_engine::doc::color::{Color, Rgb}; @@ -14,22 +15,66 @@ fn new_line_color(s: &mut Session) -> Color { s.doc().unwrap().model.last().unwrap().common.color } +#[test] +fn getvar_cecolor_of_a_new_drawing_is_bylayer() { + let mut s = Session::new(); + assert_eq!(cecolor(&mut s), json!("BYLAYER")); +} + #[test] fn setvar_cecolor_accepts_names() { - for (value, aci) in [("BYLAYER", 256), ("ByLayer", 256), ("BYBLOCK", 0), ("red", 1), ("250", 250)] { + for (value, shown) in [ + ("BYLAYER", "BYLAYER"), + ("ByLayer", "BYLAYER"), + ("256", "BYLAYER"), + ("BYBLOCK", "BYBLOCK"), + ("0", "BYBLOCK"), + ("red", "1"), + ("7", "7"), + ("250", "250"), + ("RGB:10,20,30", "RGB:10,20,30"), + ("10,20,30", "RGB:10,20,30"), + ] { 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}"); + assert_eq!(cecolor(&mut s), json!(shown), "{value}"); } } +#[test] +fn getvar_cecolor_after_the_color_command() { + let mut s = Session::new(); + s.execute("color", &json!({ "color": "ByBlock" })).unwrap(); + assert_eq!(cecolor(&mut s), json!("BYBLOCK")); + s.execute("color", &json!({ "color": "10,20,30" })).unwrap(); + assert_eq!(cecolor(&mut s), json!("RGB:10,20,30")); +} + +/// The value `getvar` gives back is one `setvar` takes, so saving and restoring keeps a true colour. +#[test] +fn getvar_cecolor_restores_a_true_colour() { + let mut s = Session::new(); + s.execute("color", &json!({ "color": "10,20,30" })).unwrap(); + let saved = cecolor(&mut s); + s.execute("setvar", &json!({ "name": "CECOLOR", "value": "250" })).unwrap(); + s.execute("setvar", &json!({ "name": "CECOLOR", "value": saved })).unwrap(); + assert_eq!(new_line_color(&mut s), Color::True(Rgb(10, 20, 30))); +} + +#[test] +fn setvar_cecolor_lists_cecolor_as_text() { + let mut s = Session::new(); + s.execute("setvar", &json!({ "name": "CECOLOR", "value": "red" })).unwrap(); + assert_eq!(cadcraft_engine::sysvars::list(&s)["CECOLOR"], json!("1")); +} + #[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!(cecolor(&mut s), json!("BYLAYER")); assert_eq!(new_line_color(&mut s), Color::ByLayer); } @@ -44,8 +89,8 @@ fn setvar_cecolor_number_replaces_a_true_colour() { #[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)] { + for bad in [json!("chartreuse"), json!(257), json!(-1), json!(true), json!("RGB:"), json!("RGB:300,0,0")] { assert!(s.execute("setvar", &json!({ "name": "CECOLOR", "value": bad })).is_err(), "{bad}"); } - assert_eq!(cecolor(&mut s), json!(256)); + assert_eq!(cecolor(&mut s), json!("BYLAYER")); }