diff --git a/crates/dwg/src/lib.rs b/crates/dwg/src/lib.rs index 9fe25dc3..7c7da02a 100644 --- a/crates/dwg/src/lib.rs +++ b/crates/dwg/src/lib.rs @@ -330,7 +330,8 @@ mod native { /// the HEADER section: (name, group code, value). fn unwritten_header_vars(h: &acadrust::document::HeaderVariables) -> [(&'static str, i32, String); 7] { [ - ("$ANGBASE", 50, h.angle_base.to_string()), + // DWG holds radians; `$ANGBASE` is in degrees. + ("$ANGBASE", 50, h.angle_base.to_degrees().to_string()), ("$ANGDIR", 70, h.angle_direction.to_string()), ("$FILLETRAD", 40, h.fillet_radius.to_string()), ("$CHAMFERA", 40, h.chamfer_distance_a.to_string()), diff --git a/crates/engine/src/sysvars.rs b/crates/engine/src/sysvars.rs index 0dd7503b..6cb6b9e9 100644 --- a/crates/engine/src/sysvars.rs +++ b/crates/engine/src/sysvars.rs @@ -130,7 +130,7 @@ pub fn get(s: &Session, name: &str) -> Option { "MAXARRAY" => json!(st.maxarray), "ZOOMFACTOR" => json!(st.zoomfactor), "LASTPOINT" => json!([s.last_point.x, s.last_point.y, 0.0]), - // Held in radians (as `$ANGBASE` in DXF); reported in degrees, like POLARANG. + // Held in radians (`$ANGBASE` in DXF is in degrees); reported in degrees, like POLARANG. "ANGBASE" => json!(s.doc().ok()?.header.f64("ANGBASE", 0.0).to_degrees()), "CONSTRAINTINFER" => json!(i32::from(s.doc().ok()?.parametric.settings.infer)), // Process-wide font substitution (profile settings, not saved in the drawing). diff --git a/crates/engine/src/units.rs b/crates/engine/src/units.rs index dd15111c..44af14f9 100644 --- a/crates/engine/src/units.rs +++ b/crates/engine/src/units.rs @@ -157,7 +157,7 @@ fn parse_mixed(s: &str) -> Option { pub struct AngleSettings { /// AUNITS: 0 decimal degrees, 1 degrees/minutes/seconds, 2 grads, 3 radians, 4 surveyor's. pub aunits: i64, - /// ANGBASE: the direction of angle 0, in radians counterclockwise from +X (as `$ANGBASE` in DXF). + /// ANGBASE: the direction of angle 0, in radians counterclockwise from +X (`$ANGBASE` in DXF is in degrees). pub angbase: f64, /// ANGDIR 1: positive angles turn clockwise. pub clockwise: bool, diff --git a/crates/io/src/dxf_read.rs b/crates/io/src/dxf_read.rs index c043842e..92d803e1 100644 --- a/crates/io/src/dxf_read.rs +++ b/crates/io/src/dxf_read.rs @@ -1341,6 +1341,9 @@ fn header(tags: &[Tag], d: &mut Drawing) { }, // $DIMJOGANG is in radians; the variable (like the style) holds degrees. HVal::Real(r) if name == "DIMJOGANG" => HVal::Real((r.to_degrees() * 1e9).round() / 1e9), + // $ANGBASE is in degrees (a drawing measured from north has 90.0); the variable + // holds radians. + HVal::Real(r) if name == "ANGBASE" => HVal::Real(r.to_radians()), hv => hv, }; d.header.set(&name, hv); diff --git a/crates/io/src/dxf_write.rs b/crates/io/src/dxf_write.rs index 3fdf3258..521654e3 100644 --- a/crates/io/src/dxf_write.rs +++ b/crates/io/src/dxf_write.rs @@ -358,6 +358,8 @@ fn header_vars(w: &mut W, d: &Drawing) { (-2 | -3, _) => {} (c @ (40 | 50), v) => { if let Some(f) = v.as_f64() { + // ANGBASE is held in radians; `$ANGBASE` is in degrees. + let f = if *name == "ANGBASE" { f.to_degrees() } else { f }; w.s(9, format!("${name}")); w.f(c, f); } diff --git a/crates/io/tests/angle_header.rs b/crates/io/tests/angle_header.rs index 1b39f820..e9b29da3 100644 --- a/crates/io/tests/angle_header.rs +++ b/crates/io/tests/angle_header.rs @@ -1,5 +1,6 @@ -//! The angle settings round-trip through DXF: `$ANGBASE` (group 50) in radians, `$ANGDIR` and -//! `$AUNITS` (group 70) as integers. +//! The angle settings round-trip through DXF: `$ANGBASE` (group 50) in degrees, as AutoCAD writes +//! it (a drawing measured from north has 90.0), held in radians; `$ANGDIR` and `$AUNITS` +//! (group 70) as integers. use cadcraft_doc::{Drawing, HVal}; @@ -10,9 +11,23 @@ fn angbase_angdir_aunits_round_trip_through_dxf() { d.header.set_i64("ANGDIR", 1); d.header.set_i64("AUNITS", 3); let text = cadcraft_io::write_dxf(&d); + // Written in degrees. + let lines: Vec<&str> = text.lines().map(str::trim).collect(); + let at = lines.iter().position(|l| *l == "$ANGBASE").expect("$ANGBASE is written"); + let written: f64 = lines.get(at + 2).and_then(|v| v.parse().ok()).expect("a number"); + assert!((written - 90.0).abs() < 1e-9, "$ANGBASE written as {written}"); let r = cadcraft_io::read_dxf(text.as_bytes()).unwrap(); let base = r.header.f64("ANGBASE", 0.0); assert!((base - std::f64::consts::FRAC_PI_2).abs() < 1e-12, "$ANGBASE read back as {base}"); assert_eq!(r.header.get("ANGDIR"), Some(&HVal::Int(1))); assert_eq!(r.header.get("AUNITS"), Some(&HVal::Int(3))); } + +#[test] +fn angbase_in_degrees_is_read_as_radians() { + // What AutoCAD writes for a drawing whose angles run clockwise from north. + let text = "0\nSECTION\n2\nHEADER\n9\n$ANGBASE\n50\n90.0\n9\n$ANGDIR\n70\n1\n0\nENDSEC\n0\nEOF\n"; + let d = cadcraft_io::read_dxf(text.as_bytes()).unwrap(); + let base = d.header.f64("ANGBASE", 0.0); + assert!((base - std::f64::consts::FRAC_PI_2).abs() < 1e-12, "$ANGBASE 90.0 read as {base} radians"); +}