revert: drop PLOTWINDOW forced endpoint snap (#293)
Remove the forces_endpoint_snap mechanism merged in #293: CadCommand::forces_endpoint_snap, Snapper::snap_forced_corners / forced_corner_modes, the PlotWindowCommand override, the on_tick + click-commit wiring, and the associated tests. PLOTWINDOW now uses the normal snapper like every other command. Rationale: forcing Endpoint snap on regardless of the global OSNAP master overrides an explicit user setting and is surprising — a plot window is a coarse print region that usually wants a margin, not a corner lock, and the forced set only caught Endpoint (not center/mid/intersection) so it was a partial, opinionated helper. Parking it pending a design discussion with the PR author; can be reinstated behind a clearer UX (e.g. respecting OSNAP, or an opt-in modifier) if justified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cb41910f12
commit
9490c85b32
4 changed files with 3 additions and 138 deletions
|
|
@ -889,19 +889,8 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||||
} else {
|
} else {
|
||||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||||
self.snapper.from_point = self.last_point;
|
self.snapper.from_point = self.last_point;
|
||||||
let force_corners = self.tabs[i]
|
self.snapper
|
||||||
.active_cmd
|
.snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr)
|
||||||
.as_ref()
|
|
||||||
.map(|c| c.forces_endpoint_snap())
|
|
||||||
.unwrap_or(false);
|
|
||||||
if force_corners {
|
|
||||||
self.snapper.snap_forced_corners(
|
|
||||||
snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
self.snapper
|
|
||||||
.snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Object Snap Tracking: update dwell, then align the cursor
|
// Object Snap Tracking: update dwell, then align the cursor
|
||||||
|
|
@ -1828,23 +1817,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||||
} else {
|
} else {
|
||||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||||
self.snapper.from_point = self.last_point;
|
self.snapper.from_point = self.last_point;
|
||||||
// Mirror the preview path (see on_tick): a command that
|
self.snapper.snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr)
|
||||||
// forces corner snap (PLOTWINDOW) must commit the forced
|
|
||||||
// Endpoint hit, not a plain snap — otherwise the marker
|
|
||||||
// promises an endpoint the click places at the raw cursor
|
|
||||||
// whenever the global OSNAP master (or Endpoint mode) is off.
|
|
||||||
let force_corners = self.tabs[i]
|
|
||||||
.active_cmd
|
|
||||||
.as_ref()
|
|
||||||
.map(|c| c.forces_endpoint_snap())
|
|
||||||
.unwrap_or(false);
|
|
||||||
if force_corners {
|
|
||||||
self.snapper.snap_forced_corners(
|
|
||||||
snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
self.snapper.snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// Snap runs in model space; the result is already model.
|
// Snap runs in model space; the result is already model.
|
||||||
let mut pt = snap_hit.map(|s| s.world).unwrap_or(raw);
|
let mut pt = snap_hit.map(|s| s.world).unwrap_or(raw);
|
||||||
|
|
|
||||||
|
|
@ -530,13 +530,6 @@ pub trait CadCommand: Send {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if this command should have Endpoint/Intersection object-snap
|
|
||||||
/// forced on while it is active, regardless of the global OSNAP toggle.
|
|
||||||
/// Used by PLOTWINDOW so the plot-area corners always snap to geometry.
|
|
||||||
fn forces_endpoint_snap(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when the text editor closes, either because the user committed or cancelled the edit.
|
/// Called when the text editor closes, either because the user committed or cancelled the edit.
|
||||||
fn on_editor_closed(&mut self, _committed: bool) -> CmdResult {
|
fn on_editor_closed(&mut self, _committed: bool) -> CmdResult {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,6 @@ impl CadCommand for PlotWindowCommand {
|
||||||
"PLOTWINDOW"
|
"PLOTWINDOW"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forces_endpoint_snap(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt(&self) -> String {
|
fn prompt(&self) -> String {
|
||||||
if self.p1.is_none() {
|
if self.p1.is_none() {
|
||||||
"PLOTWINDOW Specify first corner of plot window:".into()
|
"PLOTWINDOW Specify first corner of plot window:".into()
|
||||||
|
|
@ -85,14 +81,3 @@ impl CadCommand for PlotWindowCommand {
|
||||||
|
|
||||||
// ── Autocomplete registry ─────────────────────────────────
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
inventory::submit!(crate::command::CommandRegistration { names: &["PLOTWINDOW", "PW"] }); // PlotWindowCommand
|
inventory::submit!(crate::command::CommandRegistration { names: &["PLOTWINDOW", "PW"] }); // PlotWindowCommand
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn plot_window_forces_endpoint_snap() {
|
|
||||||
let cmd = PlotWindowCommand::new();
|
|
||||||
assert!(cmd.forces_endpoint_snap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
86
src/snap.rs
86
src/snap.rs
|
|
@ -1355,48 +1355,6 @@ impl Snapper {
|
||||||
|
|
||||||
best
|
best
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The exact object-snap mode set used while corner snap is forced.
|
|
||||||
///
|
|
||||||
/// Endpoint only, deliberately: frame corners are line endpoints, so
|
|
||||||
/// Endpoint (a single O(n) pass over vertices) catches them. Intersection
|
|
||||||
/// snap is O(n²) over every pair of segments in the hit-test wire set — on a
|
|
||||||
/// large drawing viewed zoomed-out (exactly when a plot window is picked)
|
|
||||||
/// that froze the UI, so it is excluded from the forced set.
|
|
||||||
fn forced_corner_modes() -> HashSet<SnapType> {
|
|
||||||
let mut modes = HashSet::default();
|
|
||||||
modes.insert(SnapType::Endpoint);
|
|
||||||
modes
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run object snap with the active modes replaced by exactly Endpoint,
|
|
||||||
/// regardless of the global OSNAP master toggle or the user's configured
|
|
||||||
/// modes, then restore both afterward. Commands that always want corner
|
|
||||||
/// snapping (PLOTWINDOW) call this so the plot-area corners snap without
|
|
||||||
/// changing the user's global OSNAP settings.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn snap_forced_corners(
|
|
||||||
&mut self,
|
|
||||||
cursor_world: glam::DVec3,
|
|
||||||
cursor_screen: Point,
|
|
||||||
wires: &[WireModel],
|
|
||||||
view_rot: Mat4,
|
|
||||||
eye: glam::DVec3,
|
|
||||||
bounds: Rectangle,
|
|
||||||
grid_origin: Vec3,
|
|
||||||
grid_rot: Mat4,
|
|
||||||
) -> Option<SnapResult> {
|
|
||||||
let saved_master = self.snap_enabled;
|
|
||||||
let saved_modes = self.enabled.clone();
|
|
||||||
self.snap_enabled = true;
|
|
||||||
self.enabled = Self::forced_corner_modes();
|
|
||||||
let result = self.snap(
|
|
||||||
cursor_world, cursor_screen, wires, view_rot, eye, bounds, grid_origin, grid_rot,
|
|
||||||
);
|
|
||||||
self.snap_enabled = saved_master;
|
|
||||||
self.enabled = saved_modes;
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Object-snap priority ───────────────────────────────────────────────────
|
// ── Object-snap priority ───────────────────────────────────────────────────
|
||||||
|
|
@ -1915,47 +1873,3 @@ mod ext_tests {
|
||||||
assert!(circle_tangent_points(Vec3::new(1.0, 0.0, 0.0), c, 5.0).is_none());
|
assert!(circle_tangent_points(Vec3::new(1.0, 0.0, 0.0), c, 5.0).is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod forced_corner_tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn snap_forced_corners_restores_state() {
|
|
||||||
let mut s = Snapper::default();
|
|
||||||
// Master off, and a non-default configured mode set.
|
|
||||||
s.snap_enabled = false;
|
|
||||||
s.enabled.clear();
|
|
||||||
s.enabled.insert(SnapType::Midpoint);
|
|
||||||
let before = s.enabled.clone();
|
|
||||||
|
|
||||||
// Empty wire set → no candidate → None; we only assert the state
|
|
||||||
// is untouched afterward (the risky save/restore path).
|
|
||||||
let r = s.snap_forced_corners(
|
|
||||||
glam::DVec3::ZERO,
|
|
||||||
Point::new(0.0, 0.0),
|
|
||||||
&[],
|
|
||||||
Mat4::IDENTITY,
|
|
||||||
glam::DVec3::ZERO,
|
|
||||||
Rectangle { x: 0.0, y: 0.0, width: 800.0, height: 600.0 },
|
|
||||||
Vec3::ZERO,
|
|
||||||
Mat4::IDENTITY,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(r.is_none());
|
|
||||||
assert!(!s.snap_enabled, "master toggle must be restored to off");
|
|
||||||
assert_eq!(s.enabled, before, "configured modes must be restored");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn forced_corner_modes_are_endpoint_only() {
|
|
||||||
let modes = Snapper::forced_corner_modes();
|
|
||||||
assert!(modes.contains(&SnapType::Endpoint));
|
|
||||||
// Intersection is O(n²) over the wire set and is deliberately excluded —
|
|
||||||
// forcing it froze the UI on large drawings.
|
|
||||||
assert!(!modes.contains(&SnapType::Intersection));
|
|
||||||
assert!(!modes.contains(&SnapType::Midpoint));
|
|
||||||
assert!(!modes.contains(&SnapType::Center));
|
|
||||||
assert_eq!(modes.len(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue