forked from 1b-agent/opencadstudio-upstream
feat: render active MSPACE viewport as a true 3D widget in paper space
When a paper-space viewport is activated (double-click / MSPACE command), it is now rendered through its own dedicated PaperViewportPipeline rather than the CPU-projection approach used for inactive viewports. Architecture: - PaperSheet widget (full-canvas): renders paper entities + CPU-projected content of all viewports *except* the active one. - PaperViewportPane widget (Fixed w×h, positioned at viewport screen rect): renders the active viewport with a true 3D camera derived from the viewport's view_direction/view_target/view_height. PaperViewportPipeline is a newtype of Pipeline. Having a distinct type gives it its own Iced storage entry (keyed by TypeId), preventing the shared prepare() overwrite that broke the earlier per-viewport approach. Changes: - render.rs: PaperViewportPipeline, PaperViewportPrimitive newtypes; build_active_viewport_primitive(); build_paper_sheet_primitive() now excludes active_viewport from CPU projection. - viewport_pane.rs: PaperViewportPane<'a> with shader::Program impl. - mod.rs: viewport_content_wires() gains exclude_vp parameter; viewport_screen_rect() is no longer dead code. - view.rs: paper_canvas_view() overlays PaperViewportPane when MSPACE is active. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
1 parent
d1d4bb3d02
commit
97dbd80c93
4 files changed
+171
-21
No files matched your search
+10
-5
@@ -351,7 +351,7 @@ impl Scene {
|
||||
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));
|
||||
wires.extend(self.viewport_content_wires(layout_block, None, None));
|
||||
}
|
||||
wires
|
||||
}
|
||||
@@ -375,7 +375,7 @@ impl Scene {
|
||||
}
|
||||
Some(vp_handle) => {
|
||||
// MSPACE: only model content visible through the active viewport
|
||||
self.viewport_content_wires(layout_block, Some(vp_handle))
|
||||
self.viewport_content_wires(layout_block, Some(vp_handle), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -667,7 +667,12 @@ impl Scene {
|
||||
|
||||
/// Collect model-space wires projected into paper space for all (or one specific)
|
||||
/// user viewports. `only_vp = Some(h)` restricts output to that viewport.
|
||||
fn viewport_content_wires(&self, paper_block: Handle, only_vp: Option<Handle>) -> Vec<WireModel> {
|
||||
fn viewport_content_wires(
|
||||
&self,
|
||||
paper_block: Handle,
|
||||
only_vp: Option<Handle>,
|
||||
exclude_vp: Option<Handle>,
|
||||
) -> Vec<WireModel> {
|
||||
use acadrust::entities::Viewport;
|
||||
use std::collections::HashSet as HSet;
|
||||
|
||||
@@ -682,6 +687,7 @@ impl Scene {
|
||||
&& vp.common.owner_handle == paper_block
|
||||
&& vp.status.is_on
|
||||
&& only_vp.map_or(true, |h| vp.common.handle == h)
|
||||
&& exclude_vp.map_or(true, |h| vp.common.handle != h)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -2104,8 +2110,7 @@ impl Scene {
|
||||
///
|
||||
/// `canvas_px` — the pixel dimensions of the area that shows the paper.
|
||||
/// Map a paper-space viewport into screen-pixel coordinates.
|
||||
/// Needed when per-viewport shader widgets are revived (see `ViewportPaneMode::Paper`).
|
||||
#[allow(dead_code)]
|
||||
/// Used to position the active-viewport widget in MSPACE paper space.
|
||||
pub fn viewport_screen_rect(
|
||||
&self,
|
||||
vp_handle: Handle,
|
||||
|
||||
+64
-1
@@ -14,6 +14,56 @@ use super::pipeline::Pipeline;
|
||||
use super::tessellate;
|
||||
use super::{HatchModel, ImageModel, MeshModel, Scene, Uniforms, WireModel};
|
||||
|
||||
// ── PaperViewportPipeline / PaperViewportPrimitive ────────────────────────
|
||||
//
|
||||
// Newtype wrappers around Pipeline / Primitive so that the active-MSPACE
|
||||
// viewport widget gets its own Iced storage entry (keyed by TypeId of the
|
||||
// Pipeline type). This prevents the shared-pipeline prepare() overwrite
|
||||
// that occurs when PaperSheet and the viewport widget both use `Pipeline`.
|
||||
|
||||
/// Dedicated pipeline for the MSPACE active-viewport shader widget.
|
||||
pub struct PaperViewportPipeline(pub(super) Pipeline);
|
||||
|
||||
impl iced::widget::shader::Pipeline for PaperViewportPipeline {
|
||||
fn new(
|
||||
device: &iced::wgpu::Device,
|
||||
queue: &iced::wgpu::Queue,
|
||||
format: iced::wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
Self(Pipeline::new(device, queue, format))
|
||||
}
|
||||
}
|
||||
|
||||
/// Primitive returned by `PaperViewportPane`; delegates everything to the
|
||||
/// inner `Primitive` via the dedicated `PaperViewportPipeline`.
|
||||
#[derive(Debug)]
|
||||
pub struct PaperViewportPrimitive(pub(super) Primitive);
|
||||
|
||||
impl shader::Primitive for PaperViewportPrimitive {
|
||||
type Pipeline = PaperViewportPipeline;
|
||||
|
||||
fn prepare(
|
||||
&self,
|
||||
pipeline: &mut PaperViewportPipeline,
|
||||
device: &iced::wgpu::Device,
|
||||
queue: &iced::wgpu::Queue,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
self.0.prepare(&mut pipeline.0, device, queue, bounds, viewport);
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
pipeline: &PaperViewportPipeline,
|
||||
encoder: &mut iced::wgpu::CommandEncoder,
|
||||
target: &iced::wgpu::TextureView,
|
||||
clip: &Rectangle<u32>,
|
||||
) {
|
||||
self.0.render(&pipeline.0, encoder, target, clip);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Camera hover state (shader::Program::State) ───────────────────────────
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -209,7 +259,9 @@ impl Scene {
|
||||
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let mut wires = self.paper_sheet_wires();
|
||||
wires.extend(self.viewport_content_wires(layout_block, None));
|
||||
// When MSPACE is active, exclude that viewport from the CPU projection —
|
||||
// it is rendered in 3D by the separate PaperViewportPane widget.
|
||||
wires.extend(self.viewport_content_wires(layout_block, None, self.active_viewport));
|
||||
if let Some(iw) = &self.interim_wire {
|
||||
wires.push(iw.clone());
|
||||
}
|
||||
@@ -260,6 +312,17 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap `build_viewport_primitive()` in `PaperViewportPrimitive` for use
|
||||
/// by `PaperViewportPane`, which needs its own dedicated pipeline type.
|
||||
pub(super) fn build_active_viewport_primitive(
|
||||
&self,
|
||||
vp_handle: Handle,
|
||||
hover_region: Option<usize>,
|
||||
bounds: Rectangle,
|
||||
) -> PaperViewportPrimitive {
|
||||
PaperViewportPrimitive(self.build_viewport_primitive(vp_handle, hover_region, bounds))
|
||||
}
|
||||
|
||||
/// Update viewcube hover state from cursor position within `bounds`.
|
||||
pub(super) fn update_viewcube_state(
|
||||
&self,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::render::{CameraState, Primitive};
|
||||
use super::render::{CameraState, PaperViewportPrimitive, Primitive};
|
||||
use super::Scene;
|
||||
use acadrust::Handle;
|
||||
use iced::widget::shader;
|
||||
@@ -48,7 +48,59 @@ impl<'a> ViewportPane<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// ── shader::Program impl ──────────────────────────────────────────────────
|
||||
// ── PaperViewportPane ─────────────────────────────────────────────────────
|
||||
//
|
||||
// A shader widget for the MSPACE active viewport. Uses PaperViewportPrimitive
|
||||
// (and therefore PaperViewportPipeline) so it gets its own Iced storage entry,
|
||||
// separate from the ViewportPane/PaperSheet pipeline.
|
||||
|
||||
pub struct PaperViewportPane<'a> {
|
||||
pub scene: &'a Scene,
|
||||
pub handle: Handle,
|
||||
}
|
||||
|
||||
impl<'a> PaperViewportPane<'a> {
|
||||
pub fn new(scene: &'a Scene, handle: Handle) -> Self {
|
||||
Self { scene, handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for PaperViewportPane<'a> {
|
||||
type State = CameraState;
|
||||
type Primitive = PaperViewportPrimitive;
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
_cursor: mouse::Cursor,
|
||||
bounds: Rectangle,
|
||||
) -> Self::Primitive {
|
||||
self.scene.build_active_viewport_primitive(self.handle, state.hover_region, bounds)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
event: &Event,
|
||||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Option<iced::widget::Action<Msg>> {
|
||||
self.scene.update_viewcube_state(state, bounds, cursor);
|
||||
let _ = event;
|
||||
None
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
state: &Self::State,
|
||||
_b: Rectangle,
|
||||
_c: mouse::Cursor,
|
||||
) -> mouse::Interaction {
|
||||
self.scene.viewcube_mouse_interaction(state)
|
||||
}
|
||||
}
|
||||
|
||||
// ── ViewportPane shader::Program impl ────────────────────────────────────
|
||||
|
||||
impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a> {
|
||||
type State = CameraState;
|
||||
|
||||
Reference in new issue
Block a user