feat(ucs): per-viewport UCS applied inside floating viewports

Extend UCS support to in-viewport (MSPACE) editing through the existing
single-converter structure: one active_ucs drives every UCS-aware system,
now in the Model tab AND inside a floating viewport.

- Single predicate DocumentTab::editing_model_space() (Model tab or active
  viewport) replaces the model-only gate in ucs_wire_affine,
  ucs_rotation_angle, ucs_grid_basis, viewcube_ucs_mat, the coordinate
  readout and the cursor-plane pick.
- Per-viewport UCS: refresh_active_ucs() sets active_ucs to the pane's UCS
  on every transition (the entered viewport's own UCS from its
  ucs_origin/x_axis/y_axis/ucs_per_viewport fields, the header model UCS in
  the Model tab, none in plain paper); persist_active_ucs() writes changes
  back to the viewport entity or the header so they round-trip. Wired into
  EnterViewport/ExitViewport, layout switch, load and the UCS command.
- Visuals: grid_views now returns each pane's handle so every content
  viewport's grid aligns to its own UCS; the UCS icon draws inside the
  active viewport projected through the viewport camera.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-24 17:23:29 +03:00
commit 295d3b1b5d
5 changed files with 164 additions and 33 deletions

View file

@ -3967,7 +3967,9 @@ impl Scene {
/// the active UCS in model space, identity everywhere else. Render,
/// hit-test, and click-snap all go through this so they stay in lock-step.
pub fn viewcube_ucs_mat(&self) -> glam::Mat4 {
if self.current_layout == "Model" {
// UCS applies in model space and inside a floating viewport (MSPACE);
// plain paper space stays WCS.
if self.current_layout == "Model" || self.active_viewport.is_some() {
self.viewcube_ucs
} else {
glam::Mat4::IDENTITY
@ -7366,11 +7368,11 @@ impl Scene {
/// `active_viewports` enumeration the renderer uses, so the grid overlay can
/// never drift from the views actually on screen (issue #121). The grid
/// ignores render mode, so any value passes through.
pub fn grid_views(&self, vw: f32, vh: f32) -> Vec<(iced::Rectangle, Camera)> {
pub fn grid_views(&self, vw: f32, vh: f32) -> Vec<(iced::Rectangle, Camera, Handle)> {
self.active_viewports(vw, vh, acadrust::entities::ViewportRenderMode::Wireframe2D)
.into_iter()
.filter(|inst| inst.grid_on)
.map(|inst| (inst.screen_rect, inst.camera))
.map(|inst| (inst.screen_rect, inst.camera, inst.handle))
.collect()
}