From ac4126072c6ecd6edfc4f0eee7acec00b45fd692 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 2 Jun 2026 20:33:51 +0300 Subject: [PATCH] refactor(paper): drop sheet viewport border + clean stale PaperCanvas docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overall "sheet" viewport now IS the paper view, so its own border rectangle must not be drawn as an entity — filter it out of paper_sheet_wires_arc (content viewport borders are kept). Also update the doc comments that still referenced the removed PaperCanvas widget / paper_canvas_wires to describe the GPU sheet path. Co-Authored-By: Claude Opus 4.8 --- src/app/view.rs | 13 ++++++------ src/scene/mod.rs | 50 +++++++++++++++++++++++++-------------------- src/scene/render.rs | 11 ++++------ 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/app/view.rs b/src/app/view.rs index 8dd4acdc5..86430aeab 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -248,15 +248,14 @@ impl OpenCADStudio { let i = self.active_tab; let tab = &self.tabs[i]; let is_paper = tab.scene.current_layout != "Model"; - // Start tab: render welcome page in place of the model/paper canvas. + // Start tab: render welcome page in place of the viewport. // Surrounding chrome (tab bar, status bar) stays; the welcome widget // returned here also flags the rest of `view` to skip drawing-only // overlays via `tab.is_start`. - // Unified GPU widget for both layouts. In a paper layout the - // PaperCanvas (2-D sheet + paper entities + viewport borders) is - // layered underneath this shader so the area outside floating - // viewports shows the sheet; the shader only blits inside each - // content viewport's scissor rect. + // Unified GPU widget for both layouts. A paper layout renders through + // the same shader as model space: a full-canvas top-locked "sheet" + // viewport draws the layout's own geometry (white sheet + entities + + // borders) and the floating content viewports blit on top. let viewport_3d: Element<'_, Message> = if tab.is_start { start_page_view() } else { @@ -576,7 +575,7 @@ impl OpenCADStudio { // Active paper-space viewport overlays: a render-mode picker in // its top-left corner and a ViewCube hit area in its top-right, // both layered ABOVE the viewport mouse_area so they receive - // clicks (the paper canvas itself sits below it). Positioned with + // clicks (the shader viewport sits below it). Positioned with // leading Spaces sized to the viewport's screen rectangle. let active_vp_rect: Option = if is_paper && !tab.is_start { tab.scene.active_viewport.and_then(|h| { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 3cd962e8d..2e3f7bdb6 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -600,11 +600,11 @@ pub struct Scene { /// Maps vp_handle → (geometry_epoch, Arc>). viewport_wire_cache: RefCell>)>>, /// Cached tessellation of paper-space layout block entities (title block, annotations, etc.). - /// Separate from `wire_cache` so paper_canvas_wires() doesn't re-tessellate on every frame. + /// Separate from `wire_cache` so the GPU sheet viewport doesn't re-tessellate every frame. /// Keyed by `(geometry_epoch, camera_generation)` — paper view changes /// on zoom too, so culled wire output depends on camera. paper_sheet_cache: RefCell>)>>, - /// Per-viewport projected wire cache for the paper canvas (2-D Iced widget). + /// Per-viewport projected wire cache for paper-space content viewports. /// Stores projected + clipped wires in paper-space coordinates. /// Maps vp_handle → (geometry_epoch, Vec). paper_projected_cache: RefCell)>>, @@ -732,7 +732,7 @@ impl Scene { pub(super) fn view_world_aabb(&self) -> Option<[f32; 4]> { if self.current_layout != "Model" { // Paper-space viewport composition handles its own culling; the - // top-level paper canvas is small enough not to need it. + // top-level paper view is small enough not to need it. return None; } // Until the first explicit camera move (typically `fit_all()` after @@ -771,12 +771,10 @@ impl Scene { /// World units per screen pixel at the current viewport size. Returns /// `None` until the first render captures real bounds. /// - /// Also returns `None` in paper space: `set_render_pixel_scale` is only - /// fed by the shader pipeline (`build_primitive`), and the paper layout - /// renders through `PaperCanvas` (an Iced 2-D canvas) which never touches - /// it. Whatever value is cached would be a stale model-world wpp and, - /// applied to mm-sheet entity AABBs, would cull every paper-space - /// annotation. Matches the same skip already in `view_world_aabb`. + /// Also returns `None` in paper space: `last_world_per_pixel` tracks the + /// model camera, so a cached value applied to mm-sheet entity AABBs would + /// be a stale model-world wpp and cull every paper-space annotation. + /// Matches the same skip already in `view_world_aabb`. pub(super) fn world_per_pixel(&self) -> Option { if self.current_layout != "Model" { return None; @@ -1438,9 +1436,8 @@ impl Scene { arc } - /// Cached tessellation of the current layout block's paper-space entities. - /// Shared by both `entity_wires_arc()` and `paper_canvas_wires()` so a single - /// cache miss triggers only one tessellation pass, not two. + /// Cached tessellation of the current layout block's paper-space entities, + /// shared by `entity_wires_arc()` and the GPU sheet viewport. fn paper_sheet_wires_arc(&self) -> Arc> { let key = (self.geometry_epoch, self.camera_generation); { @@ -1452,7 +1449,15 @@ impl Scene { } } let layout_block = self.current_layout_block_handle(); - let arc = Arc::new(self.wires_for_block(layout_block)); + let mut wires = self.wires_for_block(layout_block); + // The overall "sheet" viewport now IS the paper view itself, so its own + // border rectangle must not be drawn as an entity on the sheet. + let sheet = self.current_layout_sheet_viewport_handle(); + if sheet.is_valid() { + let sheet_name = sheet.value().to_string(); + wires.retain(|w| w.name != sheet_name); + } + let arc = Arc::new(wires); *self.paper_sheet_cache.borrow_mut() = Some((key, Arc::clone(&arc))); arc } @@ -5784,11 +5789,11 @@ impl Scene { } /// Convert a paper-space Viewport entity's position/size into a pixel - /// `Rectangle` relative to the top-left of the paper canvas. + /// `Rectangle` relative to the top-left of the canvas. /// - /// Uses the same camera-based ortho transform as `PaperCanvas::draw()` so - /// that the overlay lands exactly over the drawn viewport border regardless - /// of zoom or pan level. + /// Uses the same top-down ortho transform as the GPU sheet viewport so the + /// overlay lands exactly over the drawn viewport border regardless of zoom + /// or pan level. pub fn viewport_screen_rect( &self, vp_handle: Handle, @@ -5812,7 +5817,7 @@ impl Scene { let ty = cam.target.y; drop(cam); - // Mirror the to_px closure in PaperCanvas::draw(). + // Top-down ortho mapping matching the GPU sheet viewport's camera. let to_px = |wx: f32, wy: f32| -> (f32, f32) { let x = (wx - tx + half_w) / (2.0 * half_w) * canvas_w; let y = (ty + half_h - wy) / (2.0 * half_h) * canvas_h; @@ -5840,7 +5845,8 @@ impl Scene { // ── Paper-space helpers ─────────────────────────────────────────────── - /// Hatch fills for the paper-space 2-D canvas. The GPU-rendered + /// Paper-layout hatch fills, restricted to the active layout block (used by + /// paper-space hatch hit-testing / export). The GPU-rendered /// content viewports already draw model-block hatches inside their /// own scissor; including those here would also draw them on the /// paper sheet through the paper camera (huge / off-position), so @@ -5886,7 +5892,7 @@ impl Scene { Arc::new(models) } - /// Wipeout fills for the paper-space 2-D canvas. Same rationale as + /// Paper-layout wipeout fills (paper hit-testing / export). Same rationale as /// `paper_canvas_hatches` — only include wipeouts owned by the /// active paper layout block, so model wipeouts (drawn through their /// content viewport's GPU pipeline) don't get a second mis-projected @@ -5940,8 +5946,8 @@ impl Scene { } /// Build a Camera oriented and scaled to match a paper-space Viewport entity. - /// Used by `ViewportPane::Paper` to render model-space content through the - /// viewport's own view direction and scale. + /// Used by `active_viewports` to render model-space content through each + /// content viewport's own view direction and scale. fn camera_for_viewport(&self, vp_handle: Handle) -> Option { let vp = match self.document.get_entity(vp_handle) { Some(EntityType::Viewport(vp)) => vp, diff --git a/src/scene/render.rs b/src/scene/render.rs index 401d78e2a..831a913e9 100644 --- a/src/scene/render.rs +++ b/src/scene/render.rs @@ -535,18 +535,15 @@ impl Scene { let canvas = (bounds.width.max(1.0), bounds.height.max(1.0)); let instances = self.active_viewports(canvas.0, canvas.1, model_render_mode); // Transparent clear — outside drawn geometry the resolve texture - // stays at alpha=0, so the alpha-blended blit reveals whatever the - // underlying widget painted (model layout: container bg; paper - // layout: PaperCanvas sheet — including inside content viewport - // rectangles). + // stays at alpha=0, so the alpha-blended blit reveals the container + // background (model bg, or the desk colour in a paper layout). let bg_color = [0.0, 0.0, 0.0, 0.0]; let viewports: Vec = instances .iter() .filter_map(|inst| self.viewport_data_for(inst, canvas, hover_region)) .collect(); - // Empty viewports → blit nothing. The widget container's - // background (model bg or the PaperCanvas widget below) stays - // visible. This happens in paper layouts without content viewports. + // Empty viewports → blit nothing; the container background (model bg + // or the paper desk colour) stays visible. Primitive { viewports, bg_color } }