From 1271b06b0212681fd878a9e75dc6942e4ac95bdf Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 24 Oct 2026 16:19:17 -0700 Subject: [PATCH] Refuse negative values for whole-number DIM* variables DimStyle::apply_fields rounded a value for a whole-number field such as DIMDEC or DIMTMOVE and clamped it to 0..255, so DIMDEC -1 became 0 and DIMALTD "300" became 255. DIMSTYLE, DIMOVERRIDE and the SETVAR overrides that new dimensions take all accepted these values. A value below 0 or one that rounds above 255 is now refused, as an out-of-range value is. DIMADEC still takes -1. A lineweight given as -1, -2 or -3 (ByLayer, ByBlock, Default) is no longer read as 0 when the style's lineweight is positive, and a lineweight above 211 given as text is refused instead of read as 211. --- crates/doc/src/tables.rs | 13 +++++- crates/engine/tests/it/dim_vars.rs | 63 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/crates/doc/src/tables.rs b/crates/doc/src/tables.rs index 69badebb0..68493077c 100644 --- a/crates/doc/src/tables.rs +++ b/crates/doc/src/tables.rs @@ -476,10 +476,19 @@ impl DimStyle { let coerced = match (&old, &coerced) { // Lineweights (i16): whole hundredths of a mm or -1/-2/-3. (Value::Number(_), Value::Number(n)) if matches!(field, "dimLineweight" | "extLineweight") && !n.is_i64() => { - serde_json::json!(n.as_f64().filter(|x| x.is_finite()).unwrap_or(0.0).round().clamp(-3.0, 211.0) as i64) + serde_json::json!(n.as_f64().filter(|x| x.is_finite()).unwrap_or(0.0).round() as i64) } + (Value::Number(_), Value::Number(_)) if matches!(field, "dimLineweight" | "extLineweight") => coerced, + // A value below 0 or one that rounds above 255 is refused; DIMADEC -1 reads as 0. (Value::Number(o), Value::Number(n)) if o.is_u64() && !n.is_u64() => { - serde_json::json!(n.as_f64().filter(|x| x.is_finite()).unwrap_or(0.0).round().clamp(0.0, 255.0) as u64) + let min = if field == "angularDecimals" { -1.0 } else { 0.0 }; + match n.as_f64().filter(|x| x.is_finite() && *x >= min).map(f64::round).filter(|x| *x <= 255.0) { + Some(x) => serde_json::json!(x.max(0.0) as u64), + None => { + rejected.push(k.clone()); + continue; + } + } } _ => coerced, }; diff --git a/crates/engine/tests/it/dim_vars.rs b/crates/engine/tests/it/dim_vars.rs index 018485eea..5ae7813ce 100644 --- a/crates/engine/tests/it/dim_vars.rs +++ b/crates/engine/tests/it/dim_vars.rs @@ -262,6 +262,69 @@ fn linetypes_and_lineweights_of_dimension_and_extension_lines() { assert!(lws.contains(&0.5) && lws.contains(&0.13), "{lws:?}"); } +fn ignored(r: &Value) -> Vec<&str> { + r["ignored"].as_array().map(|a| a.iter().filter_map(Value::as_str).collect()).unwrap_or_default() +} + +#[test] +fn dimstyle_and_dimoverride_refuse_negative_whole_numbers() { + let mut s = Session::new(); + s.execute("dimstyle", &json!({ "name": "Standard", "DIMTMOVE": 2, "DIMLWD": 50 })).unwrap(); + let r = s + .execute( + "dimstyle", + &json!({ + "name": "Standard", + "DIMDEC": -1, + "DIMTMOVE": -1, + "DIMLUNIT": "-1", + "DIMTDEC": -0.6, + "DIMADEC": -2, + "DIMALTD": "300", + "DIMLWE": "300", + }), + ) + .unwrap(); + let ign = ignored(&r); + for k in ["DIMDEC", "DIMTMOVE", "DIMLUNIT", "DIMTDEC", "DIMADEC", "DIMALTD", "DIMLWE"] { + assert!(ign.contains(&k), "{k}: {r}"); + } + let st = s.doc().unwrap().dim_style("Standard").unwrap().clone(); + let def = DimStyle::default(); + assert_eq!((st.decimals, st.text_move, st.linear_unit, st.tol_decimals), (def.decimals, 2, def.linear_unit, def.tol_decimals)); + assert_eq!((st.angular_decimals, st.alt_decimals, st.ext_lineweight), (def.angular_decimals, def.alt_decimals, def.ext_lineweight)); + // DIMADEC -1 is a documented value (stored as 0); ByLayer lineweights stay -1. + let r = s.execute("dimstyle", &json!({ "name": "Standard", "DIMADEC": -1, "DIMLWD": "ByLayer" })).unwrap(); + assert!(r.get("ignored").is_none(), "{r}"); + assert_eq!(s.doc().unwrap().dim_style("Standard").unwrap().dim_lineweight, -1); + + // DIMOVERRIDE keeps the valid value and leaves the negative one out. + linear(&mut s, 10.0); + let h = s.doc().unwrap().model.last().unwrap().handle.hex(); + s.execute("dimoverride", &json!({ "handles": [h], "DIMDEC": -1, "DIMTMOVE": 1 })).unwrap(); + let (dm, _) = last_dim(&s); + assert!(!dm.overrides.contains_key("decimals"), "{:?}", dm.overrides); + assert_eq!(dm.overrides.get("textMove"), Some(&json!(1))); + assert!(s.execute("dimoverride", &json!({ "handles": [h], "DIMZIN": -3 })).is_err()); +} + +#[test] +fn setvar_negative_whole_numbers_do_not_reach_new_dimensions() { + let mut s = Session::new(); + for (name, v) in [("DIMDEC", json!(-1)), ("DIMLUNIT", json!("-1")), ("DIMTDEC", json!(-0.6))] { + // The header may hold the value; new dimensions keep the style's own. + let _ = s.execute("setvar", &json!({ "name": name, "value": v })); + } + let o = s.doc().unwrap().dim_overrides(); + for f in ["decimals", "linearUnit", "tolDecimals"] { + assert!(!o.contains_key(f), "{f}: {o:?}"); + } + linear(&mut s, 10.0); + let (dm, g) = last_dim(&s); + assert_eq!(g.value, "10.0000"); + assert!(dm.overrides.is_empty(), "{:?}", dm.overrides); +} + #[test] fn leaders_use_the_leader_arrow() { let mut s = Session::new();