fix(layout): faithful paper-space round-trip and page setup (#156)
Builds on the acadrust viewport/PlotSettings round-trip fixes: - Write the sheet viewport's view position into view_center (DCS) with view_target at the origin, matching AutoCAD; the old view_target form shifted the layout. - PAGESETUP now writes paper size, rotation, origin and units onto the Layout's embedded PlotSettings (not just a side object), and bumps geometry, so an edit shows on the sheet immediately and survives a save. - Draw the printable-area guide (paper inset by plot margins, rotation-aware) as a dashed rectangle, matching AutoCAD's layout view. - Plot/PDF export falls back to the Layout's embedded PlotSettings via effective_plot_settings(), so a loaded file's rotation/origin/scale apply. Bumps acadrust to pick up the viewport-status and PlotSettings round-trip fixes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9d5ee75bcc
commit
ba834fb166
3 changed files with 148 additions and 30 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -70,7 +70,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
|||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.3.4"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#3a1b66d7ec79fe576da465de48210d002dd52985"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#1b97dd3d52729ddf68ad6a542efab1eac3ebe9c3"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
|
|
|||
|
|
@ -6461,7 +6461,13 @@ impl OpenCADStudio {
|
|||
let rotation: i16 = self.page_setup_rotation.parse().unwrap_or(0);
|
||||
let scale_str = self.page_setup_scale.clone();
|
||||
|
||||
// Update the Layout object's limits.
|
||||
// Update the Layout object's limits AND its embedded
|
||||
// PlotSettings fields. `paper_limits()` (sheet rendering) and
|
||||
// the DWG writer both read these from the Layout, so a page
|
||||
// setup that only touched a side PlotSettings object would not
|
||||
// reflect on screen or survive a save. The dialog's w/h are
|
||||
// the final sheet dimensions, so store them verbatim with no
|
||||
// further rotation swap (#156).
|
||||
for obj in self.tabs[i].scene.document.objects.values_mut() {
|
||||
if let acadrust::objects::ObjectType::Layout(l) = obj {
|
||||
if l.name == layout_name {
|
||||
|
|
@ -6469,6 +6475,14 @@ impl OpenCADStudio {
|
|||
l.max_limits = (w, h);
|
||||
l.min_extents = (0.0, 0.0, 0.0);
|
||||
l.max_extents = (w, h, 0.0);
|
||||
l.paper_width = w;
|
||||
l.paper_height = h;
|
||||
l.plot_rotation = 0;
|
||||
l.plot_paper_units = 1; // millimetres
|
||||
l.plot_origin_x = offset_x;
|
||||
l.plot_origin_y = offset_y;
|
||||
// Custom dimensions no longer match a named size.
|
||||
l.paper_size = String::new();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -6542,6 +6556,9 @@ impl OpenCADStudio {
|
|||
}
|
||||
|
||||
self.tabs[i].dirty = true;
|
||||
// The paper sheet fill is cached; bump geometry so the new
|
||||
// sheet size re-tessellates and shows immediately.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.command_line.push_info(&format!(
|
||||
"Page setup: {w:.1}×{h:.1} mm area={plot_area} \
|
||||
center={center} rot={rotation}°"
|
||||
|
|
@ -6569,24 +6586,13 @@ impl OpenCADStudio {
|
|||
Message::PlotExportPath(Some(path)) => {
|
||||
let i = self.active_tab;
|
||||
let scene = &self.tabs[i].scene;
|
||||
let layout_name = scene.current_layout.clone();
|
||||
let wires = scene.entity_wires();
|
||||
let hatches = scene.paper_canvas_hatches();
|
||||
let wipeouts = scene.paper_canvas_wipeouts();
|
||||
|
||||
// Read PlotSettings for current layout (if available).
|
||||
use acadrust::objects::{ObjectType, PlotType};
|
||||
let ps_snap = scene.document.objects.values().find_map(|obj| {
|
||||
if let ObjectType::PlotSettings(ps) = obj {
|
||||
if ps.page_name == layout_name {
|
||||
Some(ps.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
use acadrust::objects::PlotType;
|
||||
let ps_snap = scene.effective_plot_settings();
|
||||
|
||||
// Determine paper size and drawing offset.
|
||||
let (paper_w, paper_h, mut draw_ox, mut draw_oy, rotation_deg) =
|
||||
|
|
@ -6691,22 +6697,11 @@ impl OpenCADStudio {
|
|||
Message::PrintToPrinter => {
|
||||
let i = self.active_tab;
|
||||
let scene = &self.tabs[i].scene;
|
||||
let layout_name = scene.current_layout.clone();
|
||||
let wires = scene.entity_wires();
|
||||
let hatches: Vec<_> = scene.paper_canvas_hatches().as_ref().clone();
|
||||
let wipeouts: Vec<_> = scene.paper_canvas_wipeouts().as_ref().clone();
|
||||
use acadrust::objects::{ObjectType, PlotType};
|
||||
let ps_snap = scene.document.objects.values().find_map(|obj| {
|
||||
if let ObjectType::PlotSettings(ps) = obj {
|
||||
if ps.page_name == layout_name {
|
||||
Some(ps.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
use acadrust::objects::PlotType;
|
||||
let ps_snap = scene.effective_plot_settings();
|
||||
let (paper_w, paper_h, draw_ox, draw_oy, rotation_deg) =
|
||||
if let Some(((x0, y0), (x1, y1))) = scene.paper_limits() {
|
||||
let (pw, ph) = (x1 - x0, y1 - y0);
|
||||
|
|
|
|||
127
src/scene/mod.rs
127
src/scene/mod.rs
|
|
@ -1435,6 +1435,119 @@ impl Scene {
|
|||
})
|
||||
}
|
||||
|
||||
/// Dashed rectangle marking the printable area — the paper inset by the
|
||||
/// layout's plot margins. AutoCAD draws this guide on every layout; with the
|
||||
/// margins now preserved we can reflect it too. `None` in model space, when
|
||||
/// the layout has no margins, or when the inset would be degenerate.
|
||||
pub(super) fn printable_area_wire(&self) -> Option<WireModel> {
|
||||
if self.current_layout == "Model" {
|
||||
return None;
|
||||
}
|
||||
let ((x0, y0), (x1, y1)) = self.paper_limits()?;
|
||||
let (left, bottom, right, top, rot) =
|
||||
self.document.objects.values().find_map(|obj| {
|
||||
if let ObjectType::Layout(l) = obj {
|
||||
if l.name == self.current_layout {
|
||||
return Some((
|
||||
l.plot_margin_left,
|
||||
l.plot_margin_bottom,
|
||||
l.plot_margin_right,
|
||||
l.plot_margin_top,
|
||||
l.plot_rotation,
|
||||
));
|
||||
}
|
||||
}
|
||||
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),
|
||||
};
|
||||
// Nothing to show when there are no margins (printable area == sheet).
|
||||
if ml <= 0.0 && mb <= 0.0 && mr <= 0.0 && mt <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let (px0, py0, px1, py1) = (x0 + ml, y0 + mb, x1 - mr, y1 - mt);
|
||||
if px1 - px0 < 1e-3 || py1 - py0 < 1e-3 {
|
||||
return None;
|
||||
}
|
||||
let (px0, py0, px1, py1) = (px0 as f32, py0 as f32, px1 as f32, py1 as f32);
|
||||
let mut wire = WireModel::solid(
|
||||
"paper_printable_area".to_string(),
|
||||
vec![
|
||||
[px0, py0, 0.0],
|
||||
[px1, py0, 0.0],
|
||||
[px1, py1, 0.0],
|
||||
[px0, py1, 0.0],
|
||||
[px0, py0, 0.0],
|
||||
],
|
||||
[0.5, 0.5, 0.5, 1.0],
|
||||
false,
|
||||
);
|
||||
// Dashed: 4 mm dash, 3 mm gap.
|
||||
wire.pattern_length = 7.0;
|
||||
wire.pattern = [4.0, -3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
|
||||
Some(wire)
|
||||
}
|
||||
|
||||
/// The effective plot settings for the current layout: a standalone
|
||||
/// PlotSettings page setup if one exists, otherwise the settings embedded in
|
||||
/// the LAYOUT object (paper size, margins, origin, rotation, scale). Loaded
|
||||
/// AutoCAD files keep their settings embedded, so without this fallback the
|
||||
/// plot/PDF path would ignore the file's rotation, origin and scale.
|
||||
pub fn effective_plot_settings(&self) -> Option<acadrust::objects::PlotSettings> {
|
||||
use acadrust::objects::{
|
||||
ObjectType, PaperMargin, PlotPaperUnits, PlotRotation, PlotSettings, PlotType,
|
||||
PlotWindow, ScaledType,
|
||||
};
|
||||
let name = &self.current_layout;
|
||||
if let Some(ps) = self.document.objects.values().find_map(|o| {
|
||||
if let ObjectType::PlotSettings(ps) = o {
|
||||
if &ps.page_name == name {
|
||||
return Some(ps.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}) {
|
||||
return Some(ps);
|
||||
}
|
||||
self.document.objects.values().find_map(|o| {
|
||||
let ObjectType::Layout(l) = o else { return None };
|
||||
if &l.name != name {
|
||||
return None;
|
||||
}
|
||||
let mut ps = PlotSettings::new(l.name.clone());
|
||||
ps.paper_width = l.paper_width;
|
||||
ps.paper_height = l.paper_height;
|
||||
ps.paper_size = l.paper_size.clone();
|
||||
ps.margins = PaperMargin::new(
|
||||
l.plot_margin_left,
|
||||
l.plot_margin_bottom,
|
||||
l.plot_margin_right,
|
||||
l.plot_margin_top,
|
||||
);
|
||||
ps.origin_x = l.plot_origin_x;
|
||||
ps.origin_y = l.plot_origin_y;
|
||||
ps.plot_window = PlotWindow::new(
|
||||
l.plot_window_min_x,
|
||||
l.plot_window_min_y,
|
||||
l.plot_window_max_x,
|
||||
l.plot_window_max_y,
|
||||
);
|
||||
ps.paper_units = PlotPaperUnits::from_code(l.plot_paper_units);
|
||||
ps.rotation = PlotRotation::from_code(l.plot_rotation);
|
||||
ps.plot_type = PlotType::from_code(l.plot_type);
|
||||
ps.scale_type = ScaledType::from_code(l.plot_scale_type);
|
||||
ps.scale_numerator = l.plot_scale_numerator;
|
||||
ps.scale_denominator = l.plot_scale_denominator;
|
||||
Some(ps)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn paper_limits(&self) -> Option<((f64, f64), (f64, f64))> {
|
||||
if self.current_layout == "Model" {
|
||||
return None;
|
||||
|
|
@ -1869,6 +1982,10 @@ impl Scene {
|
|||
self.paper_bg_color
|
||||
};
|
||||
self.apply_refedit_fade(&mut wires, bg);
|
||||
// Printable-area guide (paper inset by plot margins), paper space only.
|
||||
if let Some(pa) = self.printable_area_wire() {
|
||||
wires.push(pa);
|
||||
}
|
||||
let arc = Arc::new(wires);
|
||||
*self.paper_sheet_cache.borrow_mut() = Some((key, Arc::clone(&arc)));
|
||||
arc
|
||||
|
|
@ -6333,8 +6450,14 @@ impl Scene {
|
|||
|
||||
if let Some(handle) = sheet_handle {
|
||||
if let Some(EntityType::Viewport(vp)) = self.document.get_entity_mut(handle) {
|
||||
vp.view_target = target_wcs;
|
||||
vp.view_center = acadrust::types::Vector3::ZERO;
|
||||
// AutoCAD stores the paper-space view position in
|
||||
// `view_center` (DCS) with `view_target` at the origin —
|
||||
// writing it the other way round shifts the layout and
|
||||
// crashes nothing but renders the sheet off-place. Paper
|
||||
// space is always a plan view, so DCS == WCS XY here.
|
||||
vp.view_center =
|
||||
acadrust::types::Vector3::new(target_wcs.x, target_wcs.y, 0.0);
|
||||
vp.view_target = acadrust::types::Vector3::ZERO;
|
||||
vp.view_direction = vd3;
|
||||
vp.view_height = view_height as f64;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue