Files
OneCAD-Draft/VIEWPORT_WIDGET_PLAN.md
T
Hakan SevenandClaude Sonnet 4.6 592e3da6ac docs: mark Steps 5-6 complete in ViewportPane plan
Both items were already implemented prior to this commit:
- MSPACE mouse routing (double-click/pan/zoom/Escape) was fully wired
  through the existing viewport_mouse overlay and scene helpers.
- Per-viewport frozen layer filtering uses matching Handle values from
  the same CadDocument, confirmed correct in model_wires_for_viewport().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 02:47:42 +03:00

7.7 KiB
Raw Blame History

Unified ViewportPane Widget Plan

Current State (as of implementation start)

  • src/scene/render.rs: Scene implements shader::Program<Msg> → single full-screen GPU widget
  • src/app/view.rs:106: shader(&tab.scene).width(Fill).height(Fill) — same widget for both model and paper space, only render content differs
  • src/entities/viewport.rs: Viewport entity is a pure DXF data struct with no shader or widget of its own

Target Architecture

Model tab  →  ViewportPane (mode: Model)    — Fill × Fill
Paper tab  →  paper_canvas_view()
               ├── ViewportPane (mode: PaperSheet)    — Fill × Fill (paper entities + camera)
               ├── ViewportPane (mode: Paper, VP1)    — vp.width × vp.height px
               └── ViewportPane (mode: Paper, VP2)    — vp.width × vp.height px

✅ Step 1 — ViewportPane Struct

File: src/scene/viewport_pane.rs

pub enum ViewportPaneMode {
    Model,
    PaperSheet,              // paper-space entities, paper camera, no viewport projection
    Paper { handle: Handle }, // model content through a specific viewport's camera
}
pub struct ViewportPane<'a> { pub scene: &'a Scene, pub mode: ViewportPaneMode }

Implements shader::Program<Msg>. Scene's own shader::Program impl has been removed — all rendering now goes through ViewportPane.

Scene helper methods added to render.rs:

  • build_primitive() — model/full paper space render (was draw())
  • build_paper_sheet_primitive() — paper entities only, no viewport projection
  • build_viewport_primitive(vp_handle) — model content through viewport camera
  • update_viewcube_state() / viewcube_mouse_interaction()

Scene helper methods added to mod.rs:

  • paper_sheet_wires() — paper-space entity wires without viewport content
  • camera_for_viewport(handle) — build Camera from Viewport entity data
  • model_wires_for_viewport(handle) — model wires filtered by viewport layer freeze

✅ Step 2 — Per-Viewport Camera

Implemented via camera_for_viewport() in src/scene/mod.rs. Derives a Camera from the Viewport entity's view_direction, view_target, and view_height each frame — no separate HashMap needed since pan_active_viewport() / zoom_active_viewport() already write back to the entity.


✅ Step 3 — Paper-Space Coordinates → Pixel Conversion

pub fn viewport_screen_rect(&self, vp_handle: Handle, canvas_px: (f32, f32)) -> Option<iced::Rectangle>

Added to src/scene/mod.rs. Uses paper_limits() as the paper extent and scene.selection.borrow().vp_size as the live canvas size.


✅ Step 4 — paper_canvas_view Function

Added to src/app/view.rs. Builds a stack![] with:

  1. shader(ViewportPane::paper_sheet(scene)) — full-size paper background
  2. One shader(ViewportPane::paper(scene, handle)) per viewport, positioned via Space offsets
  3. viewport_3d in view() is now paper_canvas_view(tab) when is_paper

✅ Step 5 — Mouse Routing per Viewport

The full MSPACE routing chain is already wired and verified:

  • Double-click inside a paper viewport → Message::EnterViewport(handle) → scene.active_viewport = Some(handle) (update.rs:1704, 2540)
  • Escape → Message::ExitViewport → scene.active_viewport = None (update.rs:658)
  • Middle-drag in MSPACE → pan_active_viewport() via update.rs:1079–1080
  • Scroll in MSPACE → zoom_active_viewport() via update.rs:1784–1792

camera_for_viewport() reads view_target / view_height fresh from the entity every frame, so the ViewportPane::Paper widget automatically reflects pan/zoom changes without any extra wiring.


✅ Step 6 — Per-Viewport Layer Freeze Verification

model_wires_for_viewport() (mod.rs:2208) uses the same handle-based filtering as the older entity_content_wires() (mod.rs:697):

  1. Reads vp.frozen_layers: Vec<Handle> from the viewport entity
  2. Looks up each entity's layer by name → gets its Handle
  3. Skips entities whose layer handle is in the frozen set

Both sets of handles come from the same CadDocument, so they match correctly. Global layer visibility (l.flags.off || l.flags.frozen) is also applied before the per-viewport check.


Implementation Order

  1. ✅ ViewportPane + render helpers — model tab behavior unchanged
  2. ✅ Per-viewport camera — derived from entity each frame
  3. ✅ viewport_screen_rect() — paper → pixel coordinate mapping
  4. ✅ paper_canvas_view() — stack of PaperSheet + Paper widgets
  5. ✅ Mouse routing verification for MSPACE in paper space
  6. ✅ Per-viewport layer freeze verification

Notes

  • CameraState (viewcube hover) is per ViewportPane instance — Iced manages this automatically via shader::Program::State.
  • Paper-space entities (title blocks, frames) are rendered by ViewportPane::PaperSheet using the same paper-space camera as before.
  • The ViewCube is hidden in ViewportPane::Paper mode (only shown in Model and PaperSheet).
  • selection_overlay and grip markers use paper-space screen coordinates — this is correct for PSPACE. For MSPACE, grips of model entities are projected through the paper camera, which may need adjustment in a future step.
  • Known limitation: viewport_screen_rect() uses the last-rendered canvas size (scene.selection.borrow().vp_size), so viewport positions are correct from the second frame onward.