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>
This commit is contained in:
Will
2026-10-24 01:53:16 -04:00
committed by GitHub
co-authored by Brandon Thomas
parent 532c4ccc1c
commit a0dced6d45
5 changed files with 147 additions and 9 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ use cadcraft_doc::{DimKind, DimStyle, Dimension};
use cadcraft_fonts::{MTextParams, TextFont};
use cadcraft_geom::{Arc, Vec2, ccw_sweep};
use crate::units::{NumFormat, format_angle, format_decimal, format_linear};
use crate::units::{NumFormat, format_angle_with_separator, format_decimal, format_linear};
/// Most DIMBREAK gaps drawn per dimension.
const MAX_BREAKS: usize = 1000;
@@ -887,7 +887,7 @@ pub fn dimension_geometry_with(d: &Dimension, st: &DimStyle, dimscale: f64, font
g.measurement = sweep;
// DIMAZIN 1 / 2 suppress leading / trailing zeros (DIMZIN's bits 4 / 8).
let azin = (st.angular_zero_suppression & 3) * 4;
let txt = format_angle(sweep, st.angular_unit, st.angular_decimals, azin);
let txt = format_angle_with_separator(sweep, st.angular_unit, st.angular_decimals, azin, &st.decimal_separator);
// DIMPOST is a linear-dimension prefix/suffix; angles have their own format only.
g.mtext = inspected(apply_override(&txt, d), d);
let mut pts = Vec::new();
+9 -4
View File
@@ -188,8 +188,13 @@ pub fn format_linear(v: f64, f: &NumFormat) -> (String, String) {
}
}
/// Format an angle (radians, non-negative sweep) per DIMAUNIT / DIMADEC.
/// Format an angle (radians, non-negative sweep) per DIMAUNIT / DIMADEC, with a decimal point.
pub fn format_angle(rad: f64, unit: u8, decimals: u8, zin: u8) -> String {
format_angle_with_separator(rad, unit, decimals, zin, ".")
}
/// Format an angle with the dimension style's DIMDSEP separator.
pub(crate) fn format_angle_with_separator(rad: f64, unit: u8, decimals: u8, zin: u8, separator: &str) -> String {
let rad = if rad.is_finite() { rad } else { 0.0 };
let deg = rad.to_degrees();
match unit {
@@ -209,9 +214,9 @@ pub fn format_angle(rad: f64, unit: u8, decimals: u8, zin: u8) -> String {
_ => format!("{d}°{m}'{s}\""),
}
}
2 => format!("{}g", format_decimal(deg / 0.9, decimals, zin, ".")),
3 => format!("{}r", format_decimal(rad, decimals, zin, ".")),
_ => format!("{}°", format_decimal(deg, decimals, zin, ".")),
2 => format!("{}g", format_decimal(deg / 0.9, decimals, zin, separator)),
3 => format!("{}r", format_decimal(rad, decimals, zin, separator)),
_ => format!("{}°", format_decimal(deg, decimals, zin, separator)),
}
}
+56
View File
@@ -72,3 +72,59 @@ fn dimpost_is_not_added_to_angular_dimensions() {
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\"");
}