diff --git a/Cargo.lock b/Cargo.lock index e3b411648..a639b9304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/src/app/update.rs b/src/app/update.rs index 247550008..fe51167e4 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -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); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 22b3b508a..f661d0e36 100644 --- a/src/scene/mod.rs +++ b/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 { + 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 { + 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; }