From f63858876d1d0800e2d68d9163200cebda5aa648 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 23 Apr 2026 14:21:03 +0300 Subject: [PATCH] fix: align white paper fill with entity borders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - paper_entity_extents() computes the bounding box of actual DXF entities so the white fill always matches the drawn title-block / frame borders, regardless of whether Layout min/max_limits match entity positions - Falls back to paper_limits() (→ A4 297×210) when layout has no entities - Removed paper_boundary_wire: the white fill now acts as the visual paper edge so the near-white wire (invisible on white) was redundant - LayoutCreate sets limits to A4 landscape immediately after add_layout() to override acadrust's imperial default (12×9) Co-Authored-By: Claude Sonnet 4.6 --- src/app/update.rs | 12 +++++++ src/scene/mod.rs | 66 +++++++++++++++++++-------------------- src/scene/paper_canvas.rs | 9 +++--- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/app/update.rs b/src/app/update.rs index 4378f191..ab0c4935 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -2268,6 +2268,18 @@ impl H7CAD { self.push_undo_snapshot(i, "LAYOUT"); match self.tabs[i].scene.document.add_layout(&new_name) { Ok(_) => { + // Override the acadrust default limits (12×9 imperial) with A4 landscape. + for obj in self.tabs[i].scene.document.objects.values_mut() { + if let acadrust::objects::ObjectType::Layout(l) = obj { + if l.name == new_name { + 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); + break; + } + } + } self.tabs[i].scene.current_layout = new_name.clone(); self.tabs[i].scene.deselect_all(); self.tabs[i].scene.fit_all(); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index a94fba23..be31bff6 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -348,10 +348,6 @@ impl Scene { let layout_block = self.current_layout_block_handle(); let mut wires: Vec = self.wires_for_block(layout_block); if self.current_layout != "Model" { - // Draw the paper boundary rectangle first (rendered beneath everything else). - if let Some(((x0, y0), (x1, y1))) = self.paper_limits() { - wires.insert(0, paper_boundary_wire(x0 as f32, y0 as f32, x1 as f32, y1 as f32)); - } wires.extend(self.viewport_content_wires(layout_block, None, None)); } wires @@ -2247,11 +2243,40 @@ impl Scene { pub(super) fn paper_sheet_wires(&self) -> Vec { let layout_block = self.current_layout_block_handle(); - let mut wires = self.wires_for_block(layout_block); - if let Some(((x0, y0), (x1, y1))) = self.paper_limits() { - wires.insert(0, paper_boundary_wire(x0 as f32, y0 as f32, x1 as f32, y1 as f32)); + self.wires_for_block(layout_block) + } + + /// Bounding box (min_x, min_y), (max_x, max_y) of all DXF entities in the + /// current paper layout, in paper-space coordinates. Used by the 2-D canvas + /// to position the white paper fill so it aligns with the drawn entity borders. + /// Falls back to `paper_limits()` when the layout has no entities. + pub fn paper_entity_extents(&self) -> Option<((f32, f32), (f32, f32))> { + let layout_block = self.current_layout_block_handle(); + let wires = self.wires_for_block(layout_block); + + let mut min_x = f32::MAX; + let mut min_y = f32::MAX; + let mut max_x = f32::MIN; + let mut max_y = f32::MIN; + + for wire in &wires { + for &[x, y, _] in &wire.points { + if x.is_finite() && y.is_finite() { + min_x = min_x.min(x); + min_y = min_y.min(y); + max_x = max_x.max(x); + max_y = max_y.max(y); + } + } + } + + if min_x == f32::MAX { + // No entities — fall back to layout limits. + self.paper_limits() + .map(|((x0, y0), (x1, y1))| ((x0 as f32, y0 as f32), (x1 as f32, y1 as f32))) + } else { + Some(((min_x, min_y), (max_x, max_y))) } - wires } /// Build a Camera oriented and scaled to match a paper-space Viewport entity. @@ -2463,28 +2488,3 @@ fn clip_polyline_to_rect( result } -/// A thin white rectangle wire that represents the printable-area boundary -/// of the active paper layout. Rendered beneath all other paper-space -/// geometry so it acts as a visual "page" backdrop. -fn paper_boundary_wire(x0: f32, y0: f32, x1: f32, y1: f32) -> WireModel { - WireModel { - name: "__paper_boundary__".to_string(), - points: vec![ - [x0, y0, 0.0], - [x1, y0, 0.0], - [x1, y1, 0.0], - [x0, y1, 0.0], - [x0, y0, 0.0], - ], - // Near-white so it stands out against the dark paper-space background. - color: [0.95, 0.95, 0.95, 1.0], - selected: false, - pattern_length: 0.0, - pattern: [0.0; 8], - line_weight_px: 1.5, - snap_pts: vec![], - tangent_geoms: vec![], - aci: 0, - key_vertices: vec![], - } -} diff --git a/src/scene/paper_canvas.rs b/src/scene/paper_canvas.rs index 5194a863..8567d000 100644 --- a/src/scene/paper_canvas.rs +++ b/src/scene/paper_canvas.rs @@ -68,10 +68,11 @@ impl<'a> canvas::Program for PaperCanvas<'a> { const DESK: Color = Color { r: 0.22, g: 0.24, b: 0.28, a: 1.0 }; frame.fill_rectangle(Point::ORIGIN, bounds.size(), DESK); - // ── White paper area ────────────────────────────────────────────────── - if let Some(((px0, py0), (px1, py1))) = self.scene.paper_limits() { - let tl = to_px(px0 as f32, py1 as f32); - let br = to_px(px1 as f32, py0 as f32); + // ── White paper area — use entity extents so the fill aligns with the + // drawn DXF entity borders (title block frame, viewport border, etc.). + if let Some(((px0, py0), (px1, py1))) = self.scene.paper_entity_extents() { + let tl = to_px(px0, py1); + let br = to_px(px1, py0); let pw = br.x - tl.x; let ph = br.y - tl.y; if pw > 0.0 && ph > 0.0 {