Files
cadcraft/crates/render/tests/it/dim_text_format.rs
T
Rahulandrahul05ranjan 03101e8d77 fix(io): a moved DIMENSION draws its block at its insertion point (group 12) (#2634) (#2636)
A DIMENSION moved by an older AutoCAD keeps its *D block contents where
they were and the move in DXF group 12, and is drawn translated by the
insertion point. Group 12 was never read into Dimension, so the block
rendered at its pre-move position and the extents reached there too.

Read group 12 into a new Dimension::insertion_point, draw the block
translated by it, move its bounds with it in the extents, test the
block's text where it draws, and write group 12 back whenever the block
written is the one read from the file. Regenerated geometry (edits drop
the block link) sits at its definition points, so no offset is kept or
written for it.

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
2026-11-02 15:01:26 -07:00

132 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(),
insertion_point: Vec3::ZERO,
}
}
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\"");
}