GETVAR CECOLOR reads back as text, as in AutoCAD (follow-up to #2014) (#2069)

CECOLOR now reads "BYLAYER", "BYBLOCK", the index as text ("1" after
"red") or "RGB:r,g,b" for a true colour, from getvar, LISP and the
SETVAR ? list, where it was the ACI number (256, 0, 1, or the nearest
index of a true colour). The header still keeps the index and
CECOLOR_RGB, so DXF output and new objects are unchanged.

SETVAR CECOLOR takes "RGB:r,g,b" too, so a value saved with getvar
can be set back, true colour included.
This commit is contained in:
Marco Arnulfo
2026-10-25 12:13:18 -04:00
committed by GitHub
parent a0a691e1f7
commit 36fcc896f7
5 changed files with 83 additions and 9 deletions
+1 -1
View File
@@ -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;
+19 -1
View File
@@ -431,7 +431,11 @@ fn run_color(s: &mut Session, p: &Value) -> Result<Value> {
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) {
+9
View File
@@ -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();
+3 -1
View File
@@ -146,6 +146,8 @@ pub fn get(s: &Session, name: &str) -> Option<Value> {
// 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));
+51 -6
View File
@@ -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"));
}