From 074e589ce89caf969edb3bc72f33267892563156 Mon Sep 17 00:00:00 2001 From: NgoQuocViet2001 Date: Thu, 3 Sep 2026 15:23:42 +0700 Subject: [PATCH] fix(dimension): render DIMAUNIT 4 as a surveyor's bearing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DIMAUNIT 4 is Surveyor's units, but format_angular_value matched only 0-3 and let 4 fall through the catch-all to decimal degrees. An angular dimension in a drawing authored with surveyor units therefore read as plain degrees, while AUNITS 4 in the same drawing writes a bearing for the coordinate readout — the two disagreed on one angle. Format the bearing from the dimension's own DMS path so it carries DIMADEC, DIMAZIN and DIMDSEP, quantise before picking the quadrant so a value just short of a cardinal does not quote a quarter turn, and offer the option in the dimension-style dialog. --- src/entities/dimension.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/ui/style/dimstyle.rs | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index c27fc87db..61364a568 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -6567,6 +6567,8 @@ fn format_angular_value(measurement_deg: f64, style: Option<&DimStyle>) -> Strin swap_decimal_sep(&apply_angular_zero_suppression(&raw, azin), decimal_separator) ) } + // 4 = Surveyor's units + 4 => format_surveyor(measurement_deg, adec, azin, decimal_separator), // 0 or unknown = Decimal Degrees _ => { let raw = format!("{:.*}", adec, measurement_deg); @@ -6578,6 +6580,48 @@ fn format_angular_value(measurement_deg: f64, style: Option<&DimStyle>) -> Strin } } +/// Surveyor's units (DIMAUNIT 4): a bearing away from north or south toward +/// east or west, so the quoted angle never exceeds a quarter turn. Due north, +/// south, east and west have no bearing to quote and are written as the single +/// letter, which is also what keeps a quarter turn from reading as a +/// contradictory `N 90d0'0" E`. The bearing is DMS, so it carries DIMADEC, +/// DIMAZIN and DIMDSEP the same way the DMS format does. +fn format_surveyor(deg: f64, sec_dec: usize, azin: i16, decimal_separator: i16) -> String { + // Quantise before choosing a cardinal or a quadrant, so a value a hair + // under a cardinal direction does not print a quarter-turn bearing. + let scale = 3600.0 * 10f64.powi(sec_dec.min(8) as i32); + let turn = 360.0 * scale; + let ticks = (deg.rem_euclid(360.0) * scale).round().rem_euclid(turn); + let at = |d: f64| (ticks - d * scale).abs() < 0.5; + if at(0.0) { + return "E".into(); + } + if at(90.0) { + return "N".into(); + } + if at(180.0) { + return "W".into(); + } + if at(270.0) { + return "S".into(); + } + let d = ticks / scale; + // Measured from the nearer pole, toward the side the angle falls on. + let (pole, bearing, side) = if d < 90.0 { + ("N", 90.0 - d, "E") + } else if d < 180.0 { + ("N", d - 90.0, "W") + } else if d < 270.0 { + ("S", 270.0 - d, "W") + } else { + ("S", d - 270.0, "E") + }; + format!( + "{pole} {} {side}", + format_dms(bearing, sec_dec, azin, decimal_separator) + ) +} + fn format_dms(deg: f64, sec_dec: usize, azin: i16, decimal_separator: i16) -> String { let sign = if deg < 0.0 { "-" } else { "" }; let scale = 10_u64.pow(sec_dec.min(8) as u32) as f64; @@ -7270,6 +7314,41 @@ mod dimtad_tests { } } +#[cfg(test)] +mod angular_unit_tests { + use super::format_angular_value; + use acadrust::tables::DimStyle; + + fn style(dimaunit: i16, dimadec: i16) -> DimStyle { + let mut s = DimStyle::standard(); + s.dimaunit = dimaunit; + s.dimadec = dimadec; + s + } + + // DIMAUNIT 4 is Surveyor's units. It used to fall through to the decimal + // branch, so an angular dimension in a drawing authored with surveyor + // units read as plain degrees while AUNITS=4 elsewhere wrote a bearing. + #[test] + fn surveyor_units_write_a_bearing() { + assert_eq!(format_angular_value(45.0, Some(&style(4, 0))), "N 45\u{b0}0'0\" E"); + assert_eq!(format_angular_value(135.0, Some(&style(4, 0))), "N 45\u{b0}0'0\" W"); + assert_eq!(format_angular_value(200.0, Some(&style(4, 0))), "S 70\u{b0}0'0\" W"); + assert_eq!(format_angular_value(300.0, Some(&style(4, 0))), "S 30\u{b0}0'0\" E"); + // A hair under a cardinal must not quote a quarter-turn bearing. + assert_eq!(format_angular_value(89.99999, Some(&style(4, 0))), "N"); + assert_eq!(format_angular_value(90.0, Some(&style(4, 0))), "N"); + assert_eq!(format_angular_value(0.0, Some(&style(4, 0))), "E"); + } + + // The other unit formats are untouched. + #[test] + fn other_angular_units_unchanged() { + assert_eq!(format_angular_value(45.5, Some(&style(1, 0))), "45\u{b0}30'0\""); + assert_eq!(format_angular_value(90.0, Some(&style(2, 1))), "100.0g"); + } +} + #[cfg(test)] mod arch_format_tests { use super::{format_architectural, format_fractional}; diff --git a/src/ui/style/dimstyle.rs b/src/ui/style/dimstyle.rs index d7eda2a22..aed2e9b74 100644 --- a/src/ui/style/dimstyle.rs +++ b/src/ui/style/dimstyle.rs @@ -956,7 +956,7 @@ pub fn view_window<'a>( t!("Unit format"), DsField::Dimaunit, vals.dimaunit, - &[("0", "Decimal degrees"), ("1", "Degrees, minutes, seconds"), ("2", "Gradians"), ("3", "Radians")], + &[("0", "Decimal degrees"), ("1", "Degrees, minutes, seconds"), ("2", "Gradians"), ("3", "Radians"), ("4", "Surveyor's units")], ), row![lbl(t!("Precision")), mk_field(DsField::Dimadec, vals.dimadec)] .spacing(8).align_y(iced::Center),