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"); +}