Files
cadcraft/crates/render/tests/it/dim_text_format.rs
T
WillandBrandon Thomas a0dced6d45 fix(render): honor DIMDSEP in angular dimensions (#1670)
Use the effective dimension style's decimal separator when formatting
angular labels, preserving the public formatter's decimal-point default.

Add rendering and stored DXF/DWG block regression tests, and update the
file-format progress documentation.

Fixes #1646.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 01:53:16 -04:00

131 lines
5.5 KiB
Rust

//! 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(),
marks: Default::default(),
}
}
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");
}
fn angular(radians: f64) -> Dimension {
let mut d = linear(0.0);
d.kind = DimKind::Angular3P;
d.p13 = Vec3::new(10.0, 0.0, 0.0);
d.p14 = Vec3::new(10.0 * radians.cos(), 10.0 * radians.sin(), 0.0);
d.defpt = Vec3::new(5.0 * (radians / 2.0).cos(), 5.0 * (radians / 2.0).sin(), 0.0);
d
}
#[test]
fn angular_dimensions_use_the_style_decimal_separator() {
for (unit, radians, decimals, dot, comma) in
[(0, 39.4_f64.to_radians(), 1, "39.4°", "39,4°"), (2, std::f64::consts::FRAC_PI_4, 1, "50.0g", "50,0g"), (3, 1.25, 2, "1.25r", "1,25r")]
{
let three_point = angular(radians);
let mut two_line = three_point.clone();
two_line.kind = DimKind::Angular;
two_line.p16 = three_point.defpt;
two_line.p15 = three_point.p14;
two_line.p14 = three_point.p13;
two_line.p13 = Vec3::ZERO;
two_line.defpt = Vec3::ZERO;
for d in [three_point, two_line] {
let vars = serde_json::json!({"DIMAUNIT": unit, "DIMADEC": decimals});
assert_eq!(text(&d, vars.clone()), dot);
let mut vars = vars;
vars["DIMDSEP"] = serde_json::json!(",");
assert_eq!(text(&d, vars), comma);
}
}
}
#[test]
fn angular_decimal_separator_respects_overrides_and_literal_text() {
let mut d = angular(39.4_f64.to_radians());
d.text = "<> at 1.5".into();
d.overrides.insert("DIMDSEP".into(), serde_json::json!(","));
assert_eq!(text(&d, serde_json::json!({"DIMADEC": 1, "DIMDSEP": "."})), "39,4° at 1.5");
d.overrides.insert("DIMDSEP".into(), serde_json::json!("."));
assert_eq!(text(&d, serde_json::json!({"DIMADEC": 1, "DIMDSEP": ","})), "39.4° at 1.5");
}
#[test]
fn angular_zero_suppression_works_with_a_decimal_comma() {
let d = angular(0.5_f64.to_radians());
for (zin, expected) in [(0, "0,500°"), (1, ",500°"), (2, "0,5°"), (3, ",5°")] {
assert_eq!(text(&d, serde_json::json!({"DIMADEC": 3, "DIMAZIN": zin, "DIMDSEP": ","})), expected);
}
}
#[test]
fn angular_decimal_separator_keeps_dms_notation() {
let d = angular(45.5125_f64.to_radians());
assert_eq!(text(&d, serde_json::json!({"DIMAUNIT": 1, "DIMADEC": 3, "DIMDSEP": ","})), "45°30'45\"");
}