From 2f4ceea7a99a86778c4a4f0845d67aade59b1f36 Mon Sep 17 00:00:00 2001 From: Valentin Klein Date: Sun, 11 Oct 2026 00:08:59 +0200 Subject: [PATCH] Dimension text: DIMRND rounds only the measurement; no DIMPOST on angles (fixes #373) (#376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tolerances and alternate units copied the primary number format, so they were rounded by DIMRND and took DIMZIN's zero suppression: DIMRND 0.5 turned a ±0.1 tolerance into ±0.0000, and 10.3 in showed as [261.50] mm. Tolerances are no longer rounded and use DIMTZIN. Alternate units use the new DIMALTRND (rounding), DIMALTU (unit format) and DIMALTZ (zero suppression). The new variables can be set with SETVAR, DIMSTYLE and overrides. A DXF keeps them as DIMSTYLE group codes 148, 273, 284 and 285. DIMPOST is a prefix/suffix for linear dimensions, so angular dimensions no longer get it ("90°mm"). A text override still wraps the angle. Co-authored-by: Claude Opus 5.5 --- crates/doc/src/tables.rs | 16 ++++++ crates/io/src/dxf_ext.rs | 4 ++ crates/io/src/tests.rs | 4 ++ crates/render/src/dim.rs | 21 ++++++-- crates/render/tests/dim_text_format.rs | 73 ++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 crates/render/tests/dim_text_format.rs diff --git a/crates/doc/src/tables.rs b/crates/doc/src/tables.rs index dad7b9174..89b4e8712 100644 --- a/crates/doc/src/tables.rs +++ b/crates/doc/src/tables.rs @@ -212,11 +212,19 @@ pub struct DimStyle { pub tol_decimals: u8, /// DIMTFAC tolerance text height relative to the dimension text. pub tol_scale: f64, + /// DIMTZIN zero suppression of tolerance values (DIMZIN bits). + pub tol_zero_suppression: u8, /// DIMALT alternate units, DIMALTF factor, DIMALTD decimals, DIMAPOST template. pub alt: bool, pub alt_factor: f64, pub alt_decimals: u8, pub alt_post: String, + /// DIMALTRND rounding increment of alternate units (0 = none); DIMRND doesn't apply to them. + pub alt_round: f64, + /// DIMALTU: 1 sci, 2 dec, 3 eng, 4 arch stacked, 5 frac stacked, 6 arch, 7 frac, 8 windows. + pub alt_unit: u8, + /// DIMALTZ zero suppression of alternate units (DIMZIN bits). + pub alt_zero_suppression: u8, /// DIMAUNIT: 0 decimal degrees, 1 deg/min/sec, 2 grads, 3 radians. pub angular_unit: u8, /// DIMSE1 / DIMSE2 suppress extension lines. @@ -262,10 +270,14 @@ pub const DIMVARS: &[(&str, &str)] = &[ ("DIMLIM", "limits"), ("DIMTDEC", "tolDecimals"), ("DIMTFAC", "tolScale"), + ("DIMTZIN", "tolZeroSuppression"), ("DIMALT", "alt"), ("DIMALTF", "altFactor"), ("DIMALTD", "altDecimals"), ("DIMAPOST", "altPost"), + ("DIMALTRND", "altRound"), + ("DIMALTU", "altUnit"), + ("DIMALTZ", "altZeroSuppression"), ("DIMAUNIT", "angularUnit"), ("DIMSE1", "suppressExt1"), ("DIMSE2", "suppressExt2"), @@ -312,10 +324,14 @@ impl Default for DimStyle { limits: false, tol_decimals: 4, tol_scale: 1.0, + tol_zero_suppression: 0, alt: false, alt_factor: 25.4, alt_decimals: 2, alt_post: String::new(), + alt_round: 0.0, + alt_unit: 2, + alt_zero_suppression: 0, angular_unit: 0, suppress_ext1: false, suppress_ext2: false, diff --git a/crates/io/src/dxf_ext.rs b/crates/io/src/dxf_ext.rs index c6bc3dda2..2bb1831c6 100644 --- a/crates/io/src/dxf_ext.rs +++ b/crates/io/src/dxf_ext.rs @@ -68,6 +68,7 @@ pub(crate) const DIM_CODES: &[(&str, i32, K)] = &[ ("linearFactor", 144, K::Real), ("tolScale", 146, K::Real), ("textGap", 147, K::Real), + ("altRound", 148, K::Real), ("tolerance", 71, K::Bool), ("limits", 72, K::Bool), ("textInsideHorizontal", 73, K::Bool), @@ -84,11 +85,14 @@ pub(crate) const DIM_CODES: &[(&str, i32, K)] = &[ ("angularDecimals", 179, K::Int), ("decimals", 271, K::Int), ("tolDecimals", 272, K::Int), + ("altUnit", 273, K::Int), ("angularUnit", 275, K::Int), ("fractionFormat", 276, K::Int), ("linearUnit", 277, K::Int), ("decimalSeparator", 278, K::Char), ("textJust", 280, K::Int), + ("tolZeroSuppression", 284, K::Int), + ("altZeroSuppression", 285, K::Int), ("textStyle", 340, K::TextStyle), ("arrowBlock", 342, K::Block), ("arrowBlock1", 343, K::Block), diff --git a/crates/io/src/tests.rs b/crates/io/src/tests.rs index c65167367..fc865d088 100644 --- a/crates/io/src/tests.rs +++ b/crates/io/src/tests.rs @@ -687,10 +687,14 @@ fn full_dim_style() -> DimStyle { limits: true, tol_decimals: 2, tol_scale: 0.75, + tol_zero_suppression: 8, alt: true, alt_factor: 0.03937, alt_decimals: 3, alt_post: "[<>]".into(), + alt_round: 0.005, + alt_unit: 6, + alt_zero_suppression: 4, angular_unit: 1, suppress_ext1: true, suppress_ext2: true, diff --git a/crates/render/src/dim.rs b/crates/render/src/dim.rs index 8799ca4c2..de7d1b93d 100644 --- a/crates/render/src/dim.rs +++ b/crates/render/src/dim.rs @@ -1,7 +1,8 @@ //! Dimension geometry generation from definition points and a dimension style: extension //! lines (DIMEXO/DIMEXE, DIMSE1/2), arrowheads (DIMBLK/DIMBLK1/DIMBLK2, drawn in code) or ticks //! (DIMTSZ, DIMDLE), text placement (DIMTAD, DIMJUST, DIMTIH/DIMTOH, DIMGAP) and text -//! formatting (DIMLUNIT, DIMDEC, DIMZIN, DIMRND, DIMPOST, DIMTOL/DIMLIM, DIMALT). +//! formatting (DIMLUNIT, DIMDEC, DIMZIN, DIMRND, DIMPOST, DIMTOL/DIMLIM with DIMTZIN, DIMALT with +//! DIMALTU/DIMALTZ/DIMALTRND). use cadcraft_doc::{DimKind, DimStyle, Dimension}; use cadcraft_fonts::{MTextParams, TextFont}; @@ -273,7 +274,9 @@ pub fn format_linear_value(v: f64, st: &DimStyle) -> String { } /// The text of a linear measurement: (plain, MTEXT) with DIMPOST, tolerances/limits and -/// alternate units, then the user's override text (`<>` = the measurement). +/// alternate units, then the user's override text (`<>` = the measurement). DIMRND rounds the +/// measurement (and so the limits) only: tolerances are never rounded and alternate units +/// round by DIMALTRND. fn linear_text(meas: f64, d: &Dimension, st: &DimStyle, prefix: &str) -> String { let v = meas * st.linear_factor; let nf = NumFormat::from_style(st); @@ -286,7 +289,7 @@ fn linear_text(meas: f64, d: &Dimension, st: &DimStyle, prefix: &str) -> String }; let mut s = post(&st.post, &main); if st.tolerance && !st.limits { - let tf = NumFormat { decimals: st.tol_decimals, ..nf.clone() }; + let tf = NumFormat { decimals: st.tol_decimals, zin: st.tol_zero_suppression, round: 0.0, ..nf.clone() }; if (st.tol_plus - st.tol_minus).abs() < 1e-12 { s += &format!("%%p{}", format_linear(st.tol_plus, &tf).1); } else { @@ -299,7 +302,14 @@ fn linear_text(meas: f64, d: &Dimension, st: &DimStyle, prefix: &str) -> String } } if st.alt { - let af = NumFormat { decimals: st.alt_decimals, unit: 2, ..nf }; + // DIMALTU 6 and 7 are architectural and fractional without stacking, 8 is decimal. + let (unit, frac) = match st.alt_unit { + u @ 1..=5 => (u, nf.frac), + 6 => (4, 2), + 7 => (5, 2), + _ => (2, nf.frac), + }; + let af = NumFormat { unit, decimals: st.alt_decimals, zin: st.alt_zero_suppression, round: st.alt_round, frac, ..nf }; let alt = format_linear(v * st.alt_factor, &af).1; s += &format!(" [{}]", post(&st.alt_post, &alt)); } @@ -589,7 +599,8 @@ pub fn dimension_geometry_with(d: &Dimension, st: &DimStyle, dimscale: f64, font if cadcraft_geom::angle_in_sweep(vertex.angle_to(arc_pt), s, e) { Arc::new(vertex, r, s, e) } else { Arc::new(vertex, r, e, s) }; let sweep = ccw_sweep(arc.start, arc.end); let txt = format_angle(sweep, st.angular_unit, st.angular_decimals, st.zero_suppression & 12); - g.mtext = apply_override(&post(&st.post, &txt), d); + // DIMPOST is a linear-dimension prefix/suffix; angles have their own format only. + g.mtext = apply_override(&txt, d); let mut pts = Vec::new(); arc.tessellate(r * 1e-3, &mut pts); g.dim(pts); diff --git a/crates/render/tests/dim_text_format.rs b/crates/render/tests/dim_text_format.rs new file mode 100644 index 000000000..79d03027c --- /dev/null +++ b/crates/render/tests/dim_text_format.rs @@ -0,0 +1,73 @@ +//! Dimension text formatting (issue #373): DIMRND rounds the measurement only, tolerances use +//! DIMTZIN, alternate units DIMALTRND/DIMALTU/DIMALTZ, and DIMPOST is not added to angles. + +use cadcraft_doc::{DimKind, DimStyle, Dimension}; +use cadcraft_geom::Vec3; +use cadcraft_render::dimension_geometry; + +fn linear(len: f64) -> Dimension { + Dimension { + kind: DimKind::Linear { rotation: 0.0 }, + defpt: Vec3::new(0.0, 5.0, 0.0), + text_mid: Vec3::ZERO, + p13: Vec3::ZERO, + p14: Vec3::new(len, 0.0, 0.0), + p15: Vec3::ZERO, + p16: Vec3::ZERO, + text: String::new(), + style: "Standard".into(), + measurement: 0.0, + text_rotation: 0.0, + user_text_pos: false, + block: None, + overrides: Default::default(), + assoc: Vec::new(), + } +} + +fn text(d: &Dimension, vars: serde_json::Value) -> String { + let mut st = DimStyle::default(); + let rejected = st.apply_fields(vars.as_object().unwrap()); + assert!(rejected.is_empty(), "unknown variables {rejected:?}"); + dimension_geometry(d, &st, 1.0).value +} + +#[test] +fn dimrnd_rounds_neither_tolerances_nor_alternate_units() { + let d = linear(10.3); + let tol = serde_json::json!({"DIMRND": 0.5, "DIMDEC": 2, "DIMTOL": 1, "DIMTP": 0.1, "DIMTM": 0.1, "DIMTDEC": 2}); + assert_eq!(text(&d, tol), "10.50±0.10"); + let dev = serde_json::json!({"DIMRND": 0.5, "DIMDEC": 2, "DIMTOL": 1, "DIMTP": 0.1, "DIMTM": 0.2, "DIMTDEC": 2}); + assert_eq!(text(&d, dev), "10.50+0.10/-0.20"); + // Tolerance zeros follow DIMTZIN, not DIMZIN. + let tzin = serde_json::json!({"DIMZIN": 8, "DIMDEC": 2, "DIMTOL": 1, "DIMTP": 0.1, "DIMTM": 0.1, "DIMTDEC": 2}); + assert_eq!(text(&d, tzin.clone()), "10.3±0.10"); + let mut tzin8 = tzin; + tzin8["DIMTZIN"] = 8.into(); + assert_eq!(text(&d, tzin8), "10.3±0.1"); + + // 10.3 in = 261.62 mm: DIMRND 0.5 rounds the primary value only. + assert_eq!(text(&d, serde_json::json!({"DIMRND": 0.5, "DIMDEC": 1, "DIMALT": 1})), "10.5 [261.62]"); + // DIMALTRND rounds the alternate value. + assert_eq!(text(&d, serde_json::json!({"DIMDEC": 1, "DIMALT": 1, "DIMALTRND": 0.5})), "10.3 [261.50]"); + // DIMALTZ, not DIMZIN, suppresses alternate zeros. + assert_eq!(text(&linear(10.0), serde_json::json!({"DIMZIN": 8, "DIMALT": 1})), "10 [254.00]"); + assert_eq!(text(&linear(10.0), serde_json::json!({"DIMALT": 1, "DIMALTZ": 8})), "10.0000 [254]"); + // DIMALTU: alternate units in their own format (7 = fractional, not stacked). + assert_eq!(text(&linear(2.0), serde_json::json!({"DIMALT": 1, "DIMALTF": 0.25, "DIMALTU": 7, "DIMALTD": 2})), "2.0000 [1/2]"); +} + +#[test] +fn dimpost_is_not_added_to_angular_dimensions() { + let mut a = linear(0.0); + a.kind = DimKind::Angular3P; + a.p15 = Vec3::ZERO; + a.p13 = Vec3::new(10.0, 0.0, 0.0); + a.p14 = Vec3::new(0.0, 10.0, 0.0); + a.defpt = Vec3::new(3.0, 3.0, 0.0); + assert_eq!(text(&a, serde_json::json!({"DIMPOST": "<>mm"})), "90°"); + // Linear dimensions keep it, and an angular text override still wraps the angle. + assert_eq!(text(&linear(10.0), serde_json::json!({"DIMPOST": "<>mm", "DIMDEC": 1})), "10.0mm"); + a.text = "<> TYP".into(); + assert_eq!(text(&a, serde_json::json!({"DIMPOST": "<>mm"})), "90° TYP"); +}