Fix viewport dimension acquisition and preserve measurement scaling

This commit is contained in:
Christopher Overbeck committed 2026-09-15 14:13:19 -05:00
1 parent 369e0c9fc5
commit 2ee3782850
18 files changed
+957 -577

No files matched your search

+23 -26
View File
@@ -2374,6 +2374,8 @@ impl OpenCADStudio {
extension_dir: None,
viewport: None,
source: None,
secondary_source: None,
model_point: None,
});
if let Some(cmd) = self.tabs[i].active_cmd.as_mut() {
cmd.set_acquisition_hint(Some(pick.label));
@@ -3853,29 +3855,7 @@ impl OpenCADStudio {
command.set_ctrl(self.ctrl_down);
command.set_shift(self.shift_down);
}
// PR2: a dimension "select object" pick that lands inside a paper
// content viewport is resolved against the model geometry that
// viewport displays. Paper hit testing only sees sheet entities,
// so this runs first and falls through when it finds nothing.
let dimension_viewport_pick = if self.tabs[i].active_cmd.as_ref().is_some_and(
|command| command.needs_entity_pick() && command.measures_through_viewports(),
) {
let aperture_paper = {
let camera = self.tabs[i].scene.camera.borrow();
let per_px = if bounds.height > 0.0 {
camera.ortho_size() as f64 * 2.0 / bounds.height as f64
} else {
0.0
};
crate::ui::overlay::pick_box_aperture_px(self.pick_box) as f64 * per_px
};
self.try_dimension_viewport_entity_pick(i, pick_wcs, aperture_paper)
} else {
None
};
let result = if let Some(result) = dimension_viewport_pick {
Some(result)
} else if self.tabs[i]
let result = if self.tabs[i]
.active_cmd
.as_ref()
.map(|c| c.needs_structure_point_pick())
@@ -4065,6 +4045,7 @@ impl OpenCADStudio {
}
}
if !self.dimension_acquisition_allowed(i, None) { self.finish_command_click(i); return Task::none(); }
let shift = self.shift_down;
let result = self.tabs[i].active_cmd.as_mut().map(|c| {
// Shift-swap state for TRIM/EXTEND (#336).
@@ -4072,6 +4053,7 @@ impl OpenCADStudio {
c.set_entity_pick_direction(entity_pick_direction);
c.on_entity_pick(handle, entity_pick_point)
});
self.record_dimension_entity_points(i, None, handle, Vec::new());
// HATCHEDIT: after pick, inject hatch model data into the command.
if self.tabs[i]
.active_cmd
@@ -4142,11 +4124,24 @@ impl OpenCADStudio {
}
}
result
} else if let Some(result) = {
let dimension = self.tabs[i].active_cmd.as_ref().is_some_and(|c| c.measures_through_viewports());
if dimension {
let per_px = self.tabs[i].scene.camera.borrow().ortho_size() as f64 * 2.0 / (bounds.height as f64).max(1.0);
self.try_dimension_viewport_entity_pick(i, pick_wcs, crate::ui::overlay::pick_box_aperture_px(self.pick_box) as f64 * per_px)
} else { None }
} {
Some(result)
} else if self.tabs[i]
.active_cmd
.as_ref()
.is_some_and(|command| command.entity_pick_accepts_points())
{
let pending = self.pending_click_snap.take();
if !self.record_accepted_snap(i, pending.map(|(hit, _)| hit), pending.and_then(|(_, frame)| frame), pick_wcs) {
self.finish_command_click(i);
return Task::none();
}
self.refresh_command_point_pick_context(i);
self.tabs[i]
.active_cmd
@@ -4215,17 +4210,18 @@ impl OpenCADStudio {
self.tabs[i].dyn_active = 0;
}
}
self.last_point = Some(world_pt);
// Record what this point step accepted, alongside the point
// itself (PR1): paper point, model point, viewport, frame and
// geometry identity. Commands that don't care keep consuming
// `world_pt` exactly as before.
let pending = self.pending_click_snap.take();
self.record_accepted_snap(
if !self.record_accepted_snap(
i,
pending.map(|(hit, _)| hit),
pending.and_then(|(_, frame)| frame),
world_pt,
);
) { self.finish_command_click(i); return Task::none(); }
self.last_point = Some(world_pt);
// The one-shot snap override is spent by this pick —
// restore the running osnap configuration (#337).
self.snapper.clear_override();
@@ -4260,6 +4256,7 @@ impl OpenCADStudio {
}
};
self.sync_dimension_snaps(i);
if let Some(r) = result {
let task = self.apply_cmd_result(r);
let mut sel = self.tabs[i].scene.selection.borrow_mut();
+34 -34
View File
@@ -102,10 +102,11 @@ impl OpenCADStudio {
/// after ortho / polar / axis-lock / dynamic-input resolution.
pub(crate) fn record_accepted_snap(
&mut self,
tab: usize,
snap: Option<SnapResult>,
frame: Option<ViewportFrame>,
committed: glam::DVec3,
) {
) -> bool {
let accepted = match snap {
// A viewport snap keeps its frame only when the hit really came
// through it, so a paper-sheet snap never claims a model point.
@@ -115,7 +116,24 @@ impl OpenCADStudio {
}
None => AcceptedSnap::free(committed),
};
if self.tabs[tab]
.active_cmd
.as_ref()
.is_some_and(|cmd| cmd.measures_through_viewports())
{
if self.tabs[tab]
.active_cmd
.as_ref()
.is_some_and(|cmd| cmd.dimension_placement_pending())
{
return true;
}
if !self.dimension_acquisition_allowed(tab, accepted.viewport) {
return false;
}
}
self.push_accepted_snap(accepted);
true
}
/// Append an already-built accepted snap, keeping the retention cap.
@@ -165,30 +183,12 @@ impl OpenCADStudio {
return None;
}
let handles = self.tabs[i].scene.layout_content_viewports();
// Draw order puts the last-drawn viewport on top; probe top-most first
// so overlapping viewports resolve the same way they render.
for &handle in handles.iter().rev() {
if !self.tabs[i].scene.viewport_displays_content(handle) {
continue;
}
let Some(frame) = self.tabs[i].scene.viewport_frame(handle) else {
continue; // oblique / perspective viewport: unsupported
};
// Clip first (rectangle + non-rectangular clip boundary), in paper
// coordinates, so a cursor over the cut-away part of a clipped
// viewport sees nothing.
if !self
.tabs[i]
.scene
.viewport_displays_paper_point(handle, cursor_paper.truncate())
{
continue;
}
let Some((cam, rect)) = self
.tabs[i]
.scene
.viewport_edit_frame_for(handle, canvas)
for frame in self.tabs[i]
.scene
.viewport_frames_at_paper_point(cursor_paper)
{
let handle = frame.viewport;
let Some((cam, rect)) = self.tabs[i].scene.viewport_edit_frame_for(handle, canvas)
else {
continue;
};
@@ -208,8 +208,7 @@ impl OpenCADStudio {
// the viewport camera projects back onto `local`.
let model_cursor = frame.paper_to_model(cursor_paper);
let wires = self
.tabs[i]
let wires = self.tabs[i]
.scene
.model_wires_for_viewport_arc(handle, bounds.height);
let candidates = self.tabs[i].scene.interaction_candidates_near(
@@ -221,14 +220,14 @@ impl OpenCADStudio {
self.snapper.osnap_radius_px,
);
// Object snap only. The grid belongs to the paper sheet, and the
// rubber-band origin / tracking points are paper-space points, so
// perpendicular and extension feet would be nonsense in model
// space. Save and restore, since `Snapper` is shared state.
// The grid belongs to the sheet. Translate the construction
// anchor for perpendicular/tangent snaps into this viewport's
// model coordinates, then restore the shared snapper state.
let saved_grid = self.snapper.grid_snap_on;
let saved_from = self.snapper.from_point;
self.snapper.grid_snap_on = false;
self.snapper.from_point = None;
self.snapper.from_point =
saved_from.map(|p| frame.paper_to_model(p.as_dvec3()).as_vec3());
let hit = self.snapper.snap(
model_cursor,
local,
@@ -251,8 +250,7 @@ impl OpenCADStudio {
// the aperture reaching past the clip. Reject those: a snap must be
// a real feature the viewport actually displays.
let paper = frame.model_to_paper(hit.world);
if !self
.tabs[i]
if !self.tabs[i]
.scene
.viewport_displays_paper_point(handle, paper.truncate())
{
@@ -291,5 +289,7 @@ fn project_hit_to_paper(
}),
viewport: Some(viewport),
source: hit.source,
secondary_source: hit.secondary_source,
model_point: Some(hit.world),
}
}