Merge pull request #1337 from HakanSeven12/plot/b1-plot-settings-compat

fix(plot): write page setups in the file format's canonical form
This commit is contained in:
Panos authored and GitHub committed 2026-09-17 18:14:17 +03:00
commit 472bb9883d
11 files changed
+16779 -278

No files matched your search

+1 -1
View File
@@ -294,7 +294,7 @@ impl OpenCADStudio {
match req["op"].as_str().unwrap_or("") {
"new" => {
let i = self.active_tab;
self.tabs[i].scene.clear();
self.tabs[i].scene.reset_to_new_drawing();
self.tabs[i].scene.material_base_dir = None;
self.tabs[i].current_path = None;
// The headless session starts on the welcome (Start) tab, which
+1 -19
View File
@@ -1,5 +1,4 @@
use crate::command::CadCommand;
use crate::io::linetypes;
use crate::modules::draw::modify::block_edit::BlockEditSession;
use crate::modules::draw::modify::refedit::RefEditSession;
use crate::scene::pick::grip::GripEdit;
@@ -567,24 +566,7 @@ impl DocumentTab {
pub(super) fn new_drawing(n: usize) -> Self {
let mut scene = Scene::new();
linetypes::populate_document(&mut scene.document);
// Paper layouts start as A4 landscape.
for obj in scene.document.objects.values_mut() {
if let acadrust::objects::ObjectType::Layout(l) = obj {
if l.name != "Model" {
l.min_limits = (0.0, 0.0);
l.max_limits = (297.0, 210.0);
l.min_extents = (0.0, 0.0, 0.0);
l.max_extents = (297.0, 210.0, 0.0);
l.paper_width = 297.0;
l.paper_height = 210.0;
l.plot_paper_units = 1;
l.plot_scale_numerator = 1.0;
l.plot_scale_denominator = 1.0;
l.paper_size = "ISO_A4_(297.00_x_210.00_MM)".into();
}
}
}
scene.populate_new_drawing_defaults();
Self {
id: NEXT_DOCUMENT_TAB_ID.fetch_add(1, Ordering::Relaxed),
scene,
+716 -230
View File
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -1508,8 +1508,7 @@ impl OpenCADStudio {
Message::ClearScene => {
let i = self.active_tab;
self.push_undo_snapshot(i, "CLEAR");
self.tabs[i].scene.clear();
crate::io::linetypes::populate_document(&mut self.tabs[i].scene.document);
self.tabs[i].scene.reset_to_new_drawing();
self.tabs[i].properties = PropertiesPanel::empty();
let doc_layers = self.tabs[i].scene.document.layers.clone();
let vp_info = self.tabs[i].scene.viewport_list();
+1 -18
View File
@@ -6033,24 +6033,7 @@ properties={:.1}ms picked={}",
if let acadrust::objects::ObjectType::Layout(l) = obj {
if l.name == new_name {
l.flags = layout_flags;
l.min_limits = (0.0, 0.0);
l.max_limits = (297.0, 210.0);
l.min_extents = (0.0, 0.0, 0.0);
l.max_extents = (297.0, 210.0, 0.0);
l.paper_width = 297.0;
l.paper_height = 210.0;
l.plot_paper_units = 1;
l.plot_scale_numerator = 1.0;
l.plot_scale_denominator = 1.0;
l.plot_scale_type = 16;
l.plot_scale_factor = 1.0;
l.plot_type = 5;
l.plot_flags.use_standard_scale = true;
l.plot_flags.print_lineweights = true;
l.plot_flags.plot_plot_styles = !plot_style.is_empty();
l.plot_flags.show_plot_styles = !plot_style.is_empty();
l.plot_style_sheet = plot_style;
l.paper_size = "ISO_A4_(297.00_x_210.00_MM)".into();
crate::scene::apply_default_page_setup(l, &plot_style);
break;
}
}
+21
View File
@@ -2709,4 +2709,25 @@ impl Scene {
self.named_parameters = crate::scene::named_parameters::ParameterTable::new();
self.bump_geometry();
}
/// Reset to the drawing File → New produces. Every path that starts a
/// fresh drawing (the New tab, CLEAR, the automation `new` op) goes
/// through here so they all agree on what a blank drawing contains.
pub fn reset_to_new_drawing(&mut self) {
self.clear();
self.populate_new_drawing_defaults();
}
/// What a blank document gets on top of the codec's defaults: the
/// standard linetypes and a compatible page setup on every paper layout.
pub fn populate_new_drawing_defaults(&mut self) {
crate::io::linetypes::populate_document(&mut self.document);
for obj in self.document.objects.values_mut() {
if let acadrust::objects::ObjectType::Layout(l) = obj {
if l.name != "Model" {
crate::scene::apply_default_page_setup(l, "");
}
}
}
}
}
+75 -7
View File
@@ -33,6 +33,7 @@ pub(crate) mod centermark;
pub(crate) mod dimension_assoc;
pub(crate) mod dimension_assoc_chain;
pub use dimension_assoc::{ReferenceStatus, ResolvedReference};
pub use page_setup::{apply_default_page_setup, rotated_margins};
mod dwg_native_constraints;
mod entity;
#[cfg(test)]
@@ -4378,13 +4379,8 @@ impl Scene {
None
})?;
// `paper_limits()` already swaps the sheet for a 90°/270° rotation, so the
// margins must rotate to the same edges: a margin on a physical side moves
// to the displayed side that side rotates onto.
let (ml, mb, mr, mt) = match rot {
1 | 3 => (bottom, left, top, right),
2 => (right, top, left, bottom),
_ => (left, bottom, right, top),
};
// margins must rotate to the same edges.
let (ml, mb, mr, mt) = page_setup::rotated_margins((left, bottom, right, top), rot);
// Plot margins are millimetres like the paper size; scale them into the
// layout's paper-space units so the inset matches the (already scaled)
// sheet rect. Without this an inch paper space insets an ~8-inch sheet
@@ -4970,6 +4966,78 @@ impl Scene {
.then_some(owner)
}
/// Millimetres in one unit of the paper side of this drawing's scale
/// family: 25.4 for an imperial drawing (whose `1/8" = 1'-0"` style scales
/// measure paper in inches), 1 for a metric one. Plot-scale factors built
/// from [`Scene::scale_list`] are expressed per this unit; page setups
/// store theirs per the layout's own paper unit, so the two are converted
/// through this value.
pub(crate) fn scale_family_unit_mm(&self) -> f64 {
if self.prefers_imperial_scales() == Some(true) {
25.4
} else {
1.0
}
}
/// The `paper : drawing` ratio behind a scale-list name, from the
/// drawing's own `Scale` object when it has one, otherwise from the
/// built-in family. A ratio keeps the plot scale readable in a page setup
/// (`1 : 250` rather than `0.004 : 1`).
pub(crate) fn scale_ratio(&self, name: &str) -> Option<(f64, f64)> {
use acadrust::objects::ObjectType;
let stored = self.document.objects.values().find_map(|o| match o {
ObjectType::Scale(s) if !s.is_temporary && s.name.eq_ignore_ascii_case(name) => {
Some((s.paper_units, s.drawing_units))
}
_ => None,
});
stored
.or_else(|| Self::parse_scale_name_ratio(name))
.filter(|(paper, drawing)| *paper > 0.0 && *drawing > 0.0)
}
/// The `paper : drawing` ratio spelled by a scale name: `1:250` gives
/// `(1, 250)`, an architectural `1/8" = 1'-0"` gives `(0.125, 12)` in
/// inches. Anything else is not a scale name.
pub(crate) fn parse_scale_name_ratio(name: &str) -> Option<(f64, f64)> {
if let Some((paper, drawing)) = name.split_once(':') {
let paper: f64 = paper.trim().parse().ok()?;
let drawing: f64 = drawing.trim().parse().ok()?;
return (paper > 0.0 && drawing > 0.0).then_some((paper, drawing));
}
let (paper, drawing) = name.split_once('=')?;
let paper = Self::parse_feet_inches(paper)?;
let drawing = Self::parse_feet_inches(drawing)?;
(paper > 0.0 && drawing > 0.0).then_some((paper, drawing))
}
/// Inches in a `F'-I"` / `I"` / `F'` length, where the inch part may be a
/// fraction (`3/32"`).
fn parse_feet_inches(text: &str) -> Option<f64> {
let text = text.trim();
let (feet, inches) = match text.split_once('\'') {
Some((feet, rest)) => (
feet.trim().parse::<f64>().ok()?,
rest.trim_start_matches('-').trim(),
),
None => (0.0, text),
};
let inches = inches.trim_end_matches('"').trim();
let inches = if inches.is_empty() {
0.0
} else if let Some((num, den)) = inches.split_once('/') {
let den: f64 = den.trim().parse().ok()?;
if den <= 0.0 {
return None;
}
num.trim().parse::<f64>().ok()? / den
} else {
inches.parse::<f64>().ok()?
};
Some(feet * 12.0 + inches)
}
/// Handle of the real (non-temporary) `Scale` object with this display name.
/// Scales are matched by their `name` field, not the dictionary key — some
/// files key the entries by an internal identifier (`*A1`, `A0`, …) that
+52 -1
View File
@@ -5,9 +5,60 @@
//! dictionary + objects consistent so the result round-trips through DWG/DXF.
use super::Scene;
use acadrust::objects::{Dictionary, ObjectType, PlotSettings};
use acadrust::objects::{Dictionary, Layout, ObjectType, PlotSettings};
use acadrust::Handle;
/// Give a freshly created paper layout the page setup a new drawing starts
/// with — ISO A4 landscape on no plotter, plotted 1:1 as a layout — stored the
/// way the file format expects it: the portrait medium under its canonical
/// name with the orientation in the rotation code. `plot_style` names the
/// plot style table to attach, or is empty.
pub fn apply_default_page_setup(layout: &mut Layout, plot_style: &str) {
use acadrust::objects::{PlotPaperUnits, PlotRotation, PlotType, ScaledType};
let a4 = crate::io::paper_catalog::default_paper();
let (paper_width, paper_height) = a4.portrait_mm();
layout.min_limits = (0.0, 0.0);
layout.max_limits = (paper_height, paper_width);
layout.min_extents = (0.0, 0.0, 0.0);
layout.max_extents = (paper_height, paper_width, 0.0);
layout.paper_width = paper_width;
layout.paper_height = paper_height;
layout.plot_rotation = PlotRotation::Degrees90.to_code();
layout.plot_paper_units = PlotPaperUnits::Millimeters.to_code();
layout.plot_type = PlotType::Layout.to_code();
layout.plot_scale_type = ScaledType::OneToOne.to_code();
layout.plot_scale_numerator = 1.0;
layout.plot_scale_denominator = 1.0;
layout.plot_scale_factor = 1.0;
layout.plot_flags.use_standard_scale = true;
layout.plot_flags.print_lineweights = true;
layout.plot_flags.draw_viewports_first = true;
layout.plot_flags.plot_plot_styles = !plot_style.is_empty();
layout.plot_flags.show_plot_styles = !plot_style.is_empty();
layout.plot_style_sheet = plot_style.to_string();
layout.paper_size = a4.canonical.to_string();
layout.plot_printer_name = crate::io::plot_device::PlotDevice::None.canonical_name();
}
/// The unprintable margins of a sheet as they appear on the rotated layout,
/// `(left, bottom, right, top)`. Stored margins belong to the medium's own
/// edges; a plot rotated by a quarter turn (`rotation` is the file's 0–3
/// code) shows the medium turned on the layout, so each margin moves to the
/// edge its side lands on — verified against limits written by the
/// commercial application: at 90° the medium's top margin is the layout's
/// left one and its left margin the layout's bottom one.
pub fn rotated_margins(
(left, bottom, right, top): (f64, f64, f64, f64),
rotation: i16,
) -> (f64, f64, f64, f64) {
match rotation {
1 => (top, left, bottom, right),
2 => (right, top, left, bottom),
3 => (bottom, right, top, left),
_ => (left, bottom, right, top),
}
}
impl Scene {
/// Handle of the `ACAD_PLOTSETTINGS` dictionary, located robustly.
///
+29
View File
@@ -0,0 +1,29 @@
Page-setup fixtures saved by a commercial CAD application (2016 release, DXF
AC1027 and the matching DWG). Provided by Panos for the plotting parity track;
the drawing holds one 10 m line (`$INSUNITS` = 6, metres) and three paper
layouts, each with a viewport of that line and its dimension:
| Layout | Device | Media (`4`) | `44`×`45` | `73` | `72` | `142`/`143` | `147` | `75` |
|---|---|---|---|---|---|---|---|---|
| `A4 1-100` | `DWG To PDF.pc3` | `ISO_A4_(297.00_x_210.00_MM)` | 297 × 210 | 0 | mm | 1 / 1 | 25.4 | 16 |
| `A3 - plotter` | system printer `HPB602DE (HP OfficeJet Pro 7740 series)` | `A3` | 297.01 × 419.99 | 1 | mm | 1 / 1 | 25.4 | 16 |
| `ARCH D - PLOTTER` | the application's second PDF plotter configuration (`… PDF (High Quality Print).pc3`) | `ARCH_D_(36.00_x_24.00_Inches)` | 914.4 × 609.6 | 0 | in | 1 / 25.4 | 0.03937 | 16 |
What the file establishes (see `src/app/update/file.rs`, `plot_settings_from_dialog`):
- A landscape sheet is written either as the driver's own landscape medium
with rotation 0 (the PDF driver lists both orientations) or as a portrait
medium with rotation 90 (system printers) — rotation is relative to the
medium's orientation, and the medium's exact driver dimensions are kept.
- Margins (`40`–`43`, mm) belong to the medium's own edges: the PDF driver's
A4 has 5.79375 left/right and 17.79375 bottom/top. Layout limits (`10`/`20`)
put the sheet at minus the *displayed* margins: at rotation 90 the medium's
top margin (20.02) becomes the layout's left one, its left margin (5.00) the
layout's bottom one.
- Plot flags `70` = 672 on every layout: draw viewports first, print
lineweights, plot with plot styles — the "use standard scale" bit stays clear
even with a standard code in `75`; the model tab (fit to paper) sets it.
- `147` is `142/143` × 25.4 on millimetre page setups and `142/143` itself on
inch ones.
- An inch page setup over a millimetre paper space stores "1:1" as
`1 in = 25.4 units` (`75` = 16 kept).
Binary file not shown.
File diff suppressed because it is too large. Load diff