acadrust's DXF writer leaves ANGBASE, ANGDIR, FILLETRAD, CHAMFERA/B, ELEVATION and THICKNESS out of the HEADER although its DWG reader fills them in; the DWG bridge now adds them to the DXF it hands our reader. SNAPBASE/SNAPANG are written to and read from the *ACTIVE VPORT record (groups 13/23 and 50), where R13+ files keep them. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7bed82838d
commit
937dca6590
|
|
@ -79,7 +79,43 @@ mod native {
|
|||
));
|
||||
}
|
||||
written.map_err(|e| format!("DWG→DXF: {e}"))?;
|
||||
Ok(out.buf)
|
||||
Ok(add_header_vars(out.buf, &doc.header))
|
||||
}
|
||||
|
||||
/// Header variables the DWG reader fills in but acadrust's DXF writer (0.6.3) leaves out of
|
||||
/// 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()),
|
||||
("$ANGDIR", 70, h.angle_direction.to_string()),
|
||||
("$FILLETRAD", 40, h.fillet_radius.to_string()),
|
||||
("$CHAMFERA", 40, h.chamfer_distance_a.to_string()),
|
||||
("$CHAMFERB", 40, h.chamfer_distance_b.to_string()),
|
||||
("$ELEVATION", 40, h.elevation.to_string()),
|
||||
("$THICKNESS", 40, h.thickness.to_string()),
|
||||
]
|
||||
}
|
||||
|
||||
/// Add the [`unwritten_header_vars`] to the HEADER section of acadrust's DXF text, before its
|
||||
/// ENDSEC; a variable the writer already wrote is left alone. Text of another layout than
|
||||
/// acadrust's (` 0\r\nSECTION\r\n 2\r\nHEADER\r\n` … ` 0\r\nENDSEC\r\n`) is returned unchanged.
|
||||
fn add_header_vars(mut dxf: Vec<u8>, h: &acadrust::document::HeaderVariables) -> Vec<u8> {
|
||||
const START: &[u8] = b" 0\r\nSECTION\r\n 2\r\nHEADER\r\n";
|
||||
const END: &[u8] = b"\r\n 0\r\nENDSEC\r\n";
|
||||
if !dxf.starts_with(START) {
|
||||
return dxf;
|
||||
}
|
||||
let Some(end) = dxf.windows(END.len()).position(|w| w == END).map(|p| p + 2) else { return dxf };
|
||||
let section = dxf.get(..end).unwrap_or_default();
|
||||
let mut add = String::new();
|
||||
for (name, code, value) in unwritten_header_vars(h) {
|
||||
let written = section.windows(name.len() + 2).any(|w| w.starts_with(name.as_bytes()) && w.ends_with(b"\r\n"));
|
||||
if !written && value.parse::<f64>().is_ok_and(f64::is_finite) {
|
||||
add.push_str(&format!(" 9\r\n{name}\r\n{code:>3}\r\n{value}\r\n"));
|
||||
}
|
||||
}
|
||||
dxf.splice(end..end, add.into_bytes());
|
||||
dxf
|
||||
}
|
||||
|
||||
/// Collects the DXF text and fails the write once it would grow past `limit`.
|
||||
|
|
|
|||
|
|
@ -1076,6 +1076,7 @@ fn tables(tags: &[Tag], d: &mut Drawing, rx: &mut Rx) {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut active_vport = false;
|
||||
for (kind, tg) in recs {
|
||||
let t = T(&tg);
|
||||
match kind.as_str() {
|
||||
|
|
@ -1145,6 +1146,19 @@ fn tables(tags: &[Tag], d: &mut Drawing, rx: &mut Rx) {
|
|||
None => d.text_styles.push(st),
|
||||
}
|
||||
}
|
||||
// The first *ACTIVE viewport holds the snap grid's origin and rotation (degrees).
|
||||
"VPORT" if !active_vport && t.s(2).is_some_and(|n| n.trim().eq_ignore_ascii_case("*ACTIVE")) => {
|
||||
active_vport = true;
|
||||
if let (Some(x), Some(y)) = (t.f(13), t.f(23))
|
||||
&& x.is_finite()
|
||||
&& y.is_finite()
|
||||
{
|
||||
d.header.set("SNAPBASE", HVal::Point(Vec3::new(x, y, 0.0)));
|
||||
}
|
||||
if let Some(a) = t.f(50).filter(|a| a.is_finite()) {
|
||||
d.header.set_f64("SNAPANG", a);
|
||||
}
|
||||
}
|
||||
"VIEW" => {
|
||||
let name = t.s(2).unwrap_or_default();
|
||||
let (h, w) = (t.fd(40, 0.0), t.fd(41, 0.0));
|
||||
|
|
|
|||
|
|
@ -1367,7 +1367,8 @@ pub fn write(d: &Drawing) -> String {
|
|||
w.p2(10, Vec2::ZERO);
|
||||
w.p2(11, Vec2::new(1.0, 1.0));
|
||||
w.p2(12, if ext.is_empty() { Vec2::new(6.0, 4.5) } else { ext.center() });
|
||||
w.p2(13, Vec2::ZERO);
|
||||
// Snap grid origin and rotation (SNAPBASE, SNAPANG in degrees): R13+ files keep them here.
|
||||
w.p2(13, d.header.point("SNAPBASE").map(|p| p.xy()).filter(|p| p.is_finite()).unwrap_or(Vec2::ZERO));
|
||||
w.p2(14, Vec2::new(0.5, 0.5));
|
||||
w.p2(15, Vec2::new(0.5, 0.5));
|
||||
w.p(16, Vec3::Z);
|
||||
|
|
@ -1375,6 +1376,7 @@ pub fn write(d: &Drawing) -> String {
|
|||
w.f(40, if ext.is_empty() { 9.0 } else { (ext.height() * 1.1).max(1e-6) });
|
||||
w.f(41, 1.6);
|
||||
w.f(42, 50.0);
|
||||
w.f(50, d.header.f64("SNAPANG", 0.0));
|
||||
w.s(0, "ENDTAB");
|
||||
// LTYPE. Embedded text and shapes point at STYLE records (340), which get their handles
|
||||
// in the STYLE table below: (tag position, style name, is a shape) are filled in there.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
//! Unit, angle and drafting settings in the drawing header survive saving as DXF or DWG and
|
||||
//! reopening. The DWG bridge converts with acadrust, whose DXF writer leaves some header
|
||||
//! variables out; the snap grid's origin and rotation live in the `*ACTIVE` VPORT record.
|
||||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
use cadcraft_doc::{Drawing, HVal};
|
||||
use cadcraft_geom::Vec3;
|
||||
|
||||
fn drawing() -> Drawing {
|
||||
let mut d = Drawing::new_metric();
|
||||
let h = &mut d.header;
|
||||
h.set_i64("AUNITS", 3);
|
||||
h.set_i64("AUPREC", 5);
|
||||
h.set_i64("LUNITS", 4);
|
||||
h.set_i64("LUPREC", 6);
|
||||
h.set_f64("ANGBASE", 0.5);
|
||||
h.set_i64("ANGDIR", 1);
|
||||
h.set_i64("INSUNITS", 6);
|
||||
h.set_i64("MEASUREMENT", 0);
|
||||
h.set_f64("LTSCALE", 2.5);
|
||||
h.set_f64("DIMSCALE", 4.0);
|
||||
h.set_i64("PDMODE", 34);
|
||||
h.set_f64("PDSIZE", 1.5);
|
||||
h.set_f64("FILLETRAD", 2.0);
|
||||
h.set_f64("CHAMFERA", 1.25);
|
||||
h.set_f64("CHAMFERB", 0.75);
|
||||
h.set_f64("ELEVATION", 3.0);
|
||||
h.set_f64("THICKNESS", 0.25);
|
||||
h.set("SNAPBASE", HVal::Point(Vec3::new(3.0, 4.0, 0.0)));
|
||||
h.set_f64("SNAPANG", 30.0);
|
||||
d
|
||||
}
|
||||
|
||||
fn check(name: &str, r: &Drawing) {
|
||||
let d = drawing();
|
||||
let ints = ["AUNITS", "AUPREC", "LUNITS", "LUPREC", "ANGDIR", "INSUNITS", "MEASUREMENT", "PDMODE"];
|
||||
for v in ints {
|
||||
assert_eq!(r.header.i64(v, -99), d.header.i64(v, -99), "{name}: ${v}");
|
||||
}
|
||||
let reals = ["ANGBASE", "LTSCALE", "DIMSCALE", "PDSIZE", "FILLETRAD", "CHAMFERA", "CHAMFERB", "ELEVATION", "THICKNESS", "SNAPANG"];
|
||||
for v in reals {
|
||||
let (got, want) = (r.header.f64(v, -99.0), d.header.f64(v, -99.0));
|
||||
assert!((got - want).abs() < 1e-9, "{name}: ${v} read back as {got}, not {want}");
|
||||
}
|
||||
let base = r.header.point("SNAPBASE").unwrap_or(Vec3::new(-99.0, -99.0, 0.0));
|
||||
assert!((base.x - 3.0).abs() < 1e-9 && (base.y - 4.0).abs() < 1e-9, "{name}: $SNAPBASE read back as {base:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_settings_round_trip_through_dxf() {
|
||||
let d = drawing();
|
||||
let r = cadcraft_io::read_dxf(cadcraft_io::write_dxf(&d).as_bytes()).unwrap();
|
||||
check("DXF", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_settings_round_trip_through_dwg() {
|
||||
let d = drawing();
|
||||
let dwg = cadcraft_io::write(&d, "x.dwg").unwrap();
|
||||
let r = cadcraft_io::read(&dwg, "x.dwg").unwrap();
|
||||
check("DWG", &r);
|
||||
}
|
||||
Loading…
Reference in New Issue