fix(paper): derive paper-space unit from plot scale, not a limits heuristic

The previous limits/paper-size ratio heuristic only recognised millimetre
(1.0) and inch (1/25.4) paper spaces. Issue #341's layout uses an
arbitrary architectural scale (~11.84 mm per paper-space unit), which the
snap-to-inch-or-mm logic missed, so the sheet was drawn 11.8x too large
and the drawing shrank into the corner.

Derive the mm->paper-space factor from the plot scale instead, matching
how the DXF format defines the paper-space/paper relationship:
  page_unit_mm = 25.4 if plot_paper_units==inch else 1.0
  mm_per_unit  = (scale_numerator / scale_denominator) * page_unit_mm
  factor       = 1 / mm_per_unit   (fallback 1.0 when scale is degenerate)
The scale and page unit are a matched pair, so this is correct even when
the page unit alone looks wrong (an inch page at 1:25.4 is a millimetre
paper space). Uses scale_numerator/denominator (codes 142/143), not the
scale_factor field (code 147), whose value is unreliable for Scale-To-Fit.

Verified against budweiser2018 (inch + mm layouts) and the #341 AEC Bldg
Plan Sample (11.84 mm/unit): every sheet now matches its viewport bbox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-20 17:32:30 +03:00
1 parent 7d68eb0f10
commit bda79c5967
1 file changed
+25 -31
+25 -31
View File
@@ -2160,17 +2160,20 @@ impl Scene {
}
/// PlotSettings store the paper size and plot margins in millimetres, but a
/// layout's paper space may be laid out in inches — its viewports, drawing
/// limits and the sheet the user sees are all in inch units. Return the
/// factor that converts those millimetre plot-settings values into the
/// active layout's paper-space units: `1.0` for a millimetre layout,
/// `1/25.4` for an inch layout.
/// layout's paper space is laid out in its own units — its viewports,
/// drawing limits and the sheet the user sees can be in inches, millimetres,
/// or an arbitrary scaled unit. Return the factor that converts those
/// millimetre plot-settings values into the active layout's paper-space
/// units (`1.0` for a millimetre paper space, `1/25.4` for an inch one, and
/// anything else for a scaled layout).
///
/// The unit is inferred from the layout's drawing limits — the only
/// per-layout tie between paper-space coordinates and the physical sheet.
/// `plot_paper_units` (code 72) records the plot *output* unit and can
/// disagree with the paper-space geometry (a layout can carry inch output
/// units over a millimetre paper space and vice-versa), so it is not used.
/// The unit is derived from the plot scale, which maps paper-space units to
/// the physical page: `mm_per_unit = (scale_num / scale_den) × page_unit_mm`
/// where `page_unit_mm` is 25.4 for an inch page (code 72 == 0) or 1 for a
/// millimetre page. The scale and the page unit are a matched pair, so this
/// is correct even when the page unit alone looks wrong (an inch page at
/// scale 1:25.4 is a millimetre paper space). Falls back to `1.0` when the
/// scale is missing or degenerate.
pub fn paper_space_unit_factor(&self) -> f64 {
if self.current_layout == "Model" {
return 1.0;
@@ -2190,28 +2193,19 @@ impl Scene {
/// Millimetre → paper-space-unit factor for a specific layout (see
/// [`Scene::paper_space_unit_factor`]).
fn layout_unit_factor(l: &acadrust::objects::Layout) -> f64 {
if l.paper_width <= 1e-6 || l.paper_height <= 1e-6 {
return 1.0;
}
// Match the rotation swap `paper_limits` applies so the span/paper axes
// line up.
let (pw, ph) = if l.plot_rotation == 1 || l.plot_rotation == 3 {
(l.paper_height, l.paper_width)
} else {
(l.paper_width, l.paper_height)
};
let span_w = (l.max_limits.0 - l.min_limits.0).abs();
let span_h = (l.max_limits.1 - l.min_limits.1).abs();
// The paper-space span ≈ physical_mm × factor. Solve factor = span / mm
// on whichever axis carries a usable span, then snap to inch when it
// lands near 1/25.4; otherwise treat the layout as millimetre (default).
let ratio = [(span_w, pw), (span_h, ph)]
.into_iter()
.find_map(|(s, p)| (s > 1e-6 && p > 1e-6).then_some(s / p));
match ratio {
Some(r) if (r - 1.0 / 25.4).abs() < 0.15 / 25.4 => 1.0 / 25.4,
_ => 1.0,
// Millimetres represented by one plotted page unit: an inch page
// (plot_paper_units == 0) is 25.4 mm, a millimetre page is 1 mm.
let page_unit_mm = if l.plot_paper_units == 0 { 25.4 } else { 1.0 };
let (num, den) = (l.plot_scale_numerator, l.plot_scale_denominator);
if num > 1e-12 && den > 1e-12 {
// One paper-space unit spans this many millimetres on the page.
let mm_per_unit = (num / den) * page_unit_mm;
if mm_per_unit > 1e-9 {
return 1.0 / mm_per_unit;
}
}
// No usable plot scale — assume the paper space is already millimetres.
1.0
}
pub fn paper_limits(&self) -> Option<((f64, f64), (f64, f64))> {