mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-10 04:19:59 -06:00
Keep a landscape media's size order and rotation when saving DXF/DWG
Page setups keep a portrait size and a landscape flag, so a layout read with 44 = 420, 45 = 297, 73 = 0 and the media ISO_full_bleed_A3_(420.00_x_297.00_MM) was written back as 297 x 420 with rotation 1. ZWCAD takes the media's size from its name and turns it a quarter on top, so the sheet plotted as a shrunk portrait page (#1463). When the media's name gives it wider than tall, write 44/45 in that order and the rotation relative to it. Other media are written as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7bf5905ac9
commit
f84e458ccf
@@ -2559,13 +2559,14 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
|
||||
w.s(2, if device.is_empty() || device.eq_ignore_ascii_case("None") { "none_device" } else { device });
|
||||
w.s(4, page.paper.replace(' ', "_"));
|
||||
w.s(6, "");
|
||||
let (paper_w, paper_h, rotation) = plot_paper(page);
|
||||
for (c, v) in [
|
||||
(40, page.margins_mm[0]),
|
||||
(41, page.margins_mm[1]),
|
||||
(42, page.margins_mm[2]),
|
||||
(43, page.margins_mm[3]),
|
||||
(44, page.width_mm),
|
||||
(45, page.height_mm),
|
||||
(44, paper_w),
|
||||
(45, paper_h),
|
||||
(46, 0.0),
|
||||
(47, 0.0),
|
||||
(48, page.plot_window[0]),
|
||||
@@ -2580,7 +2581,7 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
|
||||
w.i(70, plot_flags);
|
||||
// Plot paper units: 1 millimetres, 0 inches (#1058).
|
||||
w.i(72, i64::from(cadcraft_render::paper::page_unit_mm(d, page) == 1.0));
|
||||
w.i(73, i64::from(page.landscape));
|
||||
w.i(73, rotation);
|
||||
w.i(74, plot_type);
|
||||
w.s(7, &page.plot_style_table);
|
||||
w.i(75, if page.scale_to_fit { 0 } else { 16 });
|
||||
@@ -2639,6 +2640,33 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
|
||||
|
||||
/// Write DXF Reference group 91 on every CLASS record. Count records after `dxf_raw::finish`
|
||||
/// restores foreign classes and objects so every class gets its actual instance count.
|
||||
/// Paper size (groups 44, 45) and plot rotation (group 73) for a LAYOUT object. Page setups
|
||||
/// keep a portrait size and a landscape flag. A media whose name makes it wider than tall, such
|
||||
/// as `ISO_full_bleed_A3_(420.00_x_297.00_MM)`, gets its size in that order and the rotation
|
||||
/// relative to it, so readers that take the media's size from its name agree with groups 44 and
|
||||
/// 45 (#1463). Other media are written as before.
|
||||
fn plot_paper(page: &PageSetup) -> (f64, f64, i64) {
|
||||
if media_is_landscape(&page.paper) {
|
||||
let (short, long) = (page.width_mm.min(page.height_mm), page.width_mm.max(page.height_mm));
|
||||
(long, short, i64::from(!page.landscape))
|
||||
} else {
|
||||
(page.width_mm, page.height_mm, i64::from(page.landscape))
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a media name ends in a size wider than tall, like `… (420.00 x 297.00 MM)`.
|
||||
fn media_is_landscape(paper: &str) -> bool {
|
||||
let name = paper.replace('_', " ");
|
||||
let Some(open) = name.rfind('(') else { return false };
|
||||
let mut sides = name[open + 1..].splitn(2, ['x', 'X', '×']);
|
||||
let num = |s: Option<&str>| {
|
||||
let s = s?.trim_start();
|
||||
let end = s.find(|c: char| !(c.is_ascii_digit() || c == '.')).unwrap_or(s.len());
|
||||
s[..end].parse::<f64>().ok()
|
||||
};
|
||||
matches!((num(sides.next()), num(sides.next())), (Some(w), Some(h)) if w > h)
|
||||
}
|
||||
|
||||
fn add_class_instance_counts(tags: &mut Vec<Tag>) {
|
||||
let mut counts: HashMap<String, i64> = HashMap::new();
|
||||
for t in tags.iter().filter(|t| t.code == 0) {
|
||||
|
||||
@@ -560,6 +560,58 @@ fn dxf_roundtrips_page_setup() {
|
||||
assert_eq!(p.paper, a3.name);
|
||||
}
|
||||
|
||||
/// Groups 44, 45 and 73 of a layout's AcDbPlotSettings in DXF text.
|
||||
fn plot_paper_groups(dxf: &str, layout: &str) -> (f64, f64, i64) {
|
||||
let lines: Vec<&str> = dxf.lines().map(str::trim).collect();
|
||||
let pairs: Vec<(&str, &str)> = lines.chunks(2).filter(|c| c.len() == 2).map(|c| (c[0], c[1])).collect();
|
||||
let name = pairs.windows(2).position(|w| w[0] == ("100", "AcDbLayout") && w[1] == ("1", layout)).unwrap_or_else(|| panic!("no LAYOUT {layout}"));
|
||||
let start = pairs[..name].iter().rposition(|p| *p == ("100", "AcDbPlotSettings")).unwrap();
|
||||
let get = |code: &str| pairs[start..name].iter().find(|p| p.0 == code).map(|p| p.1).unwrap();
|
||||
(get("44").parse().unwrap(), get("45").parse().unwrap(), get("73").parse().unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn landscape_media_keeps_its_size_order_and_rotation() {
|
||||
// A media defined wider than tall, as ZWCAD and AutoCAD name it (#1463).
|
||||
let media = "ISO full bleed A3 (420.00 x 297.00 MM)";
|
||||
for (landscape, rotation) in [(true, 0), (false, 1)] {
|
||||
let mut d = sample();
|
||||
{
|
||||
let p = &mut d.layouts[0].page;
|
||||
p.paper = media.into();
|
||||
p.width_mm = 297.0;
|
||||
p.height_mm = 420.0;
|
||||
p.landscape = landscape;
|
||||
}
|
||||
let dxf = write_dxf(&d);
|
||||
assert_eq!(plot_paper_groups(&dxf, "Layout1"), (420.0, 297.0, rotation), "DXF, landscape {landscape}");
|
||||
let r = read_dxf(dxf.as_bytes()).unwrap();
|
||||
let p = &r.layout("Layout1").unwrap().page;
|
||||
assert_eq!((p.width_mm, p.height_mm, p.landscape), (297.0, 420.0, landscape), "DXF read back");
|
||||
assert_eq!(p.paper, media);
|
||||
|
||||
let dwg = write(&d, "t.dwg").unwrap();
|
||||
let back = String::from_utf8_lossy(&cadcraft_dwg::dwg_to_dxf(&dwg).unwrap()).into_owned();
|
||||
assert_eq!(plot_paper_groups(&back, "Layout1"), (420.0, 297.0, rotation), "DWG, landscape {landscape}");
|
||||
let r = read(&dwg, "t.dwg").unwrap();
|
||||
let p = &r.layout("Layout1").unwrap().page;
|
||||
assert_eq!((p.width_mm, p.height_mm, p.landscape), (297.0, 420.0, landscape), "DWG read back");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portrait_media_is_written_as_before() {
|
||||
let mut d = sample();
|
||||
{
|
||||
let p = &mut d.layouts[0].page;
|
||||
p.paper = "ISO A3 (297.00 x 420.00 MM)".into();
|
||||
p.width_mm = 297.0;
|
||||
p.height_mm = 420.0;
|
||||
p.landscape = true;
|
||||
}
|
||||
assert_eq!(plot_paper_groups(&write_dxf(&d), "Layout1"), (297.0, 420.0, 1));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Round trips of styles, overrides, associativity, constraints and tables.
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ our output in AutoCAD black-box.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-23 | minor | DXF/DWG save: a layout whose media is named wider than tall (`ISO_full_bleed_A3_(420.00_x_297.00_MM)`) keeps its paper size in that order (44 = 420, 45 = 297) and its plot rotation relative to the media, instead of a portrait size with rotation 1 that ZWCAD plotted as a shrunk portrait page (#1463); other media are written as before |
|
||||
| 2026-10-17 | minor | DWG read: DGN line style components no line style definition reaches are left out before the DXF bridge, with a read note; an 83 MB site plan with 888,483 of them (1.2 GB of DXF, over the limit) now opens (#1482) |
|
||||
| 2026-10-12 | minor | PDF output: object transparency plotted as PDF alpha (`/ExtGState` `/ca` `/CA`) when the page setup's or the plot's "Plot transparency" is on; PNG plots blend it, SVG uses `opacity`; plots without it are unchanged byte for byte |
|
||||
| 2026-10-12 | minor | DXF/DWG save: the Model layout's plot settings are written as CADCraft plots model space, drawing extents scaled to fit (PLOTSETTINGS 74 = 1, 75 = 0), instead of plot type 5 "Layout information", which other programs' Plot dialogs can't use for model space |
|
||||
|
||||
Reference in New Issue
Block a user