DXF/DWG: write the Model layout's plot type as extents, not "Layout information" (#765)

The Model layout was written with the default page setup, whose "layout"
plot area is plot type 5, "Layout information" (DXF Reference, PLOTSETTINGS
group 74). That means nothing for model space: opened in AutoCAD 2027, a
drawing saved by CADCraft had a blank "What to plot", no preview and no scale
in the Plot dialog for Model. Model space now gets what PLOT does for it: the
drawing extents scaled to fit (74 = 1, 75 = 0). DWG saves go through the same
writer; the reader already ignores the Model layout's plot settings.

Co-authored-by: EP995LAB <293597958+EP995LAB@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EP995LAB
2026-10-13 04:34:35 -04:00
committed by GitHub
co-authored by EP995LAB Claude Opus 5.5
parent 488308a2de
commit b19188919d
3 changed files with 76 additions and 2 deletions
+6 -1
View File
@@ -2299,7 +2299,12 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Tag> {
w.s(1002, "}");
}
};
layout_obj(&mut w, &model_layout, "Model", 0, &ms_br, 1, &PageSetup::default(), None);
// Model space has no page setup of its own: write what PLOT does for it, the drawing extents
// fitted to the paper (plot type 1, scaled to fit). The default page's "layout" area is
// plot type 5, "Layout information" (DXF Reference, PLOTSETTINGS group 74), which means
// nothing for model space: another program's Plot dialog then shows no plot area or scale.
let model_page = PageSetup { plot_area: "extents".into(), scale_to_fit: true, ..PageSetup::default() };
layout_obj(&mut w, &model_layout, "Model", 0, &ms_br, 1, &model_page, None);
for (i, (_, brh, l)) in ps_brs.iter().enumerate() {
let h = layout_handles.get(i).cloned().unwrap_or_default();
layout_obj(&mut w, &h, &l.name, i64::from(l.tab_order.max(1)), brh, 1, &l.page, l.view);
+68
View File
@@ -0,0 +1,68 @@
//! The Model layout's plot settings say what CADCraft plots for model space: the drawing extents
//! fitted to the paper. Plot type 5 ("Layout information", DXF Reference, PLOTSETTINGS group 74)
//! means nothing for model space; another program's Plot dialog then showed no plot area and no
//! scale for Model.
#![cfg(not(target_arch = "wasm32"))]
use cadcraft_doc::*;
use cadcraft_geom::Vec3;
/// The (code, value) pairs of every `kind` record in DXF text.
fn records(text: &str, kind: &str) -> Vec<Vec<(String, String)>> {
let lines: Vec<&str> = text.lines().map(str::trim).collect();
let mut out: Vec<Vec<(String, String)>> = Vec::new();
let mut inside = false;
for pair in lines.chunks(2) {
let [code, value] = pair else { break };
if *code == "0" {
inside = *value == kind;
if inside {
out.push(Vec::new());
}
} else if inside && let Some(rec) = out.last_mut() {
rec.push((code.to_string(), value.to_string()));
}
}
out
}
/// A LAYOUT record's name (the code 1 after the `AcDbLayout` marker), plot type (74) and
/// standard scale type (75).
fn layout_plot(rec: &[(String, String)]) -> (String, Option<i64>, Option<i64>) {
let at = rec.iter().position(|(c, v)| c == "100" && v == "AcDbLayout").unwrap_or(rec.len());
let name = rec.iter().skip(at).find(|(c, _)| c == "1").map(|(_, v)| v.clone()).unwrap_or_default();
let int = |code: &str| rec.iter().take(at).find(|(c, _)| c == code).and_then(|(_, v)| v.trim().parse().ok());
(name, int("74"), int("75"))
}
fn plots(text: &str) -> Vec<(String, Option<i64>, Option<i64>)> {
records(text, "LAYOUT").iter().map(|r| layout_plot(r)).collect()
}
fn drawing() -> Drawing {
let mut d = Drawing::new_metric();
d.add(&Space::Model, Common::default(), EntityKind::Line(Line { a: Vec3::ZERO, b: Vec3::new(100.0, 50.0, 0.0) })).unwrap();
d
}
#[test]
fn model_layout_plots_extents_fitted_and_layouts_their_sheet() {
let p = plots(&cadcraft_io::write_dxf(&drawing()));
let model = p.iter().find(|(n, _, _)| n == "Model").unwrap();
assert_eq!((model.1, model.2), (Some(1), Some(0)), "Model: drawing extents, scaled to fit");
// Paper-space layouts keep "Layout information" at 1:1.
let layouts: Vec<_> = p.iter().filter(|(n, _, _)| n != "Model").collect();
assert!(!layouts.is_empty());
assert!(layouts.iter().all(|(_, t, s)| (*t, *s) == (Some(5), Some(16))), "{layouts:?}");
}
#[test]
fn model_layout_plot_type_survives_the_dwg_bridge() {
let dwg = cadcraft_io::write(&drawing(), "x.dwg").unwrap();
// What the DWG holds, as the bridge's own DXF.
let dxf = String::from_utf8_lossy(&cadcraft_dwg::dwg_to_dxf(&dwg).unwrap()).to_string();
let p = plots(&dxf);
let model = p.iter().find(|(n, _, _)| n.eq_ignore_ascii_case("Model")).unwrap();
assert_eq!(model.1, Some(1), "{p:?}");
assert_ne!(p.iter().find(|(n, _, _)| !n.eq_ignore_ascii_case("Model")).and_then(|l| l.1), None);
}
+2 -1
View File
@@ -1,6 +1,6 @@
# File-format parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (code pages, #505; native MULTILEADER, #459; foreign data kept, #528; PDF text, layers and sheets, #567, #571; templates and user files, #552; plotter devices, #564) · **Target:** Autodesk AutoCAD 2027
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (the Model layout's plot type is written as extents, not "Layout information") · **Target:** Autodesk AutoCAD 2027
Every format AutoCAD 2027 reads or writes, with CADCraft's support, fidelity and tests. Work
items are in [gaps.md](gaps.md#file-formats); the weighted score feeds
@@ -134,6 +134,7 @@ our output in AutoCAD black-box.
| Date | Change | Summary |
|---|---|---|
| 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 |
| 2026-10-11 | minor | Rows refreshed for merged work: code pages and `\M+` (#505), MULTILEADER (#459), IMAGE, MLINE/HELIX/REGION/TOLERANCE, VPORT/VIEW/UCS tables, modelled and raw objects, foreign data (#528), DWT templates and user `.lin`/`.pat`/`.pgp` files (#552), PDF output (#567, #571), plotter devices (#564), data extraction (#433, #534); weighted percentages not re-measured |
| 2026-10-11 | minor | DXF (ASCII and binary) and DWG saved at 2000–2018, 2018 by default (#421) |
| 2026-10-10 | minor | WIPEOUT and gradients written since #147/#162; other save losses remain (#201, #204, #208, #209, #213, #223) |