Plot: apply an explicit scale to non-fitted layout plots (#490) (#598)

Squash of PR #598 by @MarekSpodBiedry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
MarekSpodBiedryandClaude Opus 5.5 authored and GitHub committed 2026-10-11 14:04:10 -04:00
1 parent 73c91cbac5
commit 0b5a0a2eb8
3 files changed
+47 -5

No files matched your search

+12 -1
View File
@@ -339,7 +339,7 @@ fn run_preview(s: &mut Session, p: &Value) -> Result<Value> {
let (from, k, to) = if fit || space == Space::Model {
(if b.is_empty() { Vec2::ZERO } else { b.center() }, plot_scale(&b, &sheet, fit, scale), sheet.printable.center())
} else {
(Vec2::ZERO, 1.0, Vec2::ZERO)
(Vec2::ZERO, plot_scale(&b, &sheet, fit, scale), Vec2::ZERO)
};
let world = Bounds2::new((Vec2::ZERO - to) / k + from, (sheet.size - to) / k + from);
if ![world.min.x, world.min.y, world.max.x, world.max.y].iter().all(|v| v.is_finite()) {
@@ -576,6 +576,17 @@ mod tests {
}
}
#[test]
fn preview_applies_an_explicit_layout_scale() {
let mut s = Session::new();
s.execute("layout.set", &json!({ "name": "Layout1" })).unwrap();
s.execute("line", &json!({ "points": [[1.0, 1.0], [5.0, 1.0]] })).unwrap();
let png = |s: &mut Session, p: Value| s.execute("preview", &p).unwrap()["data"].as_str().unwrap_or_default().to_string();
let default = png(&mut s, json!({ "layout": "Layout1", "width": 200 }));
assert_eq!(png(&mut s, json!({ "layout": "Layout1", "width": 200, "fit": false, "scale": 1 })), default, "scale 1 is the 1:1 default");
assert_ne!(png(&mut s, json!({ "layout": "Layout1", "width": 200, "fit": false, "scale": 0.5 })), default, "scale 0.5 is not 1:1");
}
#[test]
fn publish_plots_every_sheet() {
our_hooks();
+4 -4
View File
@@ -1,9 +1,9 @@
//! Vector PDF plotting (PLOT / EXPORTPDF): a minimal PDF writer of our own (pure Rust, no file
//! access, so it runs on the web too).
//!
//! One page per sheet: model-space extents fitted to a sheet, or a layout at 1:1 on its paper
//! (paper units are millimetres in metric drawings, inches otherwise). Lines are stroked paths
//! (`m`/`l`/`S`), fills are `f` paths, colours are RGB with colour 7 printing black on white
//! One page per sheet: model-space extents fitted to a sheet, or a layout at 1:1 (or the given
//! `scale`) on its paper (paper units are millimetres in metric drawings, inches otherwise).
//! Lines are stroked paths (`m`/`l`/`S`), fills are `f` paths, colours are RGB with colour 7 printing black on white
//! paper, and line widths come from lineweights when enabled. The page setup's plot style
//! table (or the `plotStyleTable` option) sets the printed colours, linetypes and lineweights.
//!
@@ -318,7 +318,7 @@ fn sheet(d: &Drawing, space: &Space, o: &PdfOptions) -> Result<SheetPage> {
let from = if b.is_empty() { Vec2::ZERO } else { b.center() };
Map { from, s, to: sheet.printable.center(), k }
} else {
Map { from: Vec2::ZERO, s: 1.0, to: Vec2::ZERO, k }
Map { from: Vec2::ZERO, s, to: Vec2::ZERO, k }
};
// Plot style ends, joins and fill patterns at the plot scale (a layout's are already drawn).
let mut list = list;
+31
View File
@@ -0,0 +1,31 @@
//! An explicit numeric `scale` on a non-fitted layout plot scales the geometry (issue #490).
use cadcraft_doc::{Common, Drawing, EntityKind, Line, Space};
use cadcraft_geom::Vec3;
use serde_json::{Value, json};
/// Plotted length in millimetres of the one 90-unit line in Layout1.
fn plotted_mm(opts: Value) -> f64 {
let mut d = Drawing::new_metric();
let line = EntityKind::Line(Line { a: Vec3::new(10.0, 10.0, 0.0), b: Vec3::new(100.0, 10.0, 0.0) });
d.add(&Space::Paper("Layout1".into()), Common::default(), line).unwrap();
let mut o = json!({"compress": false});
if let (Some(o), Some(e)) = (o.as_object_mut(), opts.as_object()) {
o.extend(e.clone());
}
let bytes = cadcraft_io::plot(&d, &Space::Paper("Layout1".into()), &o).unwrap();
let text = String::from_utf8_lossy(&bytes).into_owned();
let xs: Vec<f64> = text.lines().filter(|l| l.ends_with(" m") || l.ends_with(" l")).filter_map(|l| l.split(' ').next()?.parse().ok()).collect();
assert_eq!(xs.len(), 2, "{text}");
(xs[1] - xs[0]).abs() * 25.4 / 72.0
}
#[test]
fn layout_plot_honours_explicit_scale_without_fit() {
for (scale, mm) in [(1.0, 90.0), (0.5, 45.0), (2.0, 180.0)] {
let got = plotted_mm(json!({"fit": false, "scale": scale}));
assert!((got - mm).abs() < 0.01, "scale {scale}: {got} mm, want {mm}");
}
let default = plotted_mm(json!({}));
assert!((default - 90.0).abs() < 0.01, "default stays 1:1: {default}");
}