Merge pull request #717 from gianlucafiore/fix/716-perpendicular-tracking

Fix perpendicular and OTRACK snap interactions
This commit is contained in:
Hakan Seven authored and GitHub committed 2026-08-09 23:37:56 +03:00
commit ad5164875f
2 files changed
+50 -31

No files matched your search

+11 -7
View File
@@ -1097,10 +1097,8 @@ impl OpenCADStudio {
// Grip edits use the same tracking-point acquisition and OTRACK
// resolution as normal interactive point commands.
let otrack_hit = {
let snap_world = snap_hit.map(|s| s.world);
self.snapper.update_otrack_dwell(
snap_world,
snap_hit,
&snap_candidates,
view_rot,
eye,
@@ -1543,9 +1541,8 @@ impl OpenCADStudio {
// to a tracking ray (and store the alignment so a typed
// distance can place a point along it — issue #69).
let otrack_hit = {
let snap_world = self.tabs[i].snap_result.map(|s| s.world);
self.snapper.update_otrack_dwell(
snap_world,
self.tabs[i].snap_result,
&snap_candidates,
view_rot,
eye,
@@ -1562,7 +1559,11 @@ impl OpenCADStudio {
bounds,
Instant::now(),
);
if self.tabs[i].snap_result.is_none() {
if self.tabs[i].snap_result.is_none()
|| self.tabs[i]
.snap_result
.is_some_and(|s| s.snap_type == crate::snap::SnapType::Extension)
{
// A window corner is a free point, so neither the
// Polar increment nor the Ortho lock may pull the
// OTRACK alignment onto an axis (#291).
@@ -2901,7 +2902,10 @@ impl OpenCADStudio {
}
// OTRACK alignment wins over ortho/polar; otherwise apply
// ortho/polar relative to the last point.
let otrack = if !world_pt_locked && snap_hit.is_none() {
let otrack = if !world_pt_locked
&& (snap_hit.is_none()
|| snap_hit.is_some_and(|s| s.snap_type == crate::snap::SnapType::Extension))
{
let step = if self.polar_mode && !is_window_corner {
Some(self.polar_increment_deg)
} else {
+39 -24
View File
@@ -285,7 +285,7 @@ impl Snapper {
/// `snap_world` is the current snap result world point (if any).
pub fn update_otrack_dwell<W: WireSource + ?Sized>(
&mut self,
snap_world: Option<DVec3>,
snap: Option<SnapResult>,
wires: &W,
view_rot: glam::Mat4,
eye: glam::DVec3,
@@ -302,6 +302,24 @@ impl Snapper {
self.dwell_acquired = false;
return;
}
// Only stable geometric snap points may be acquired automatically for
// OTRACK. Continuous/contextual snaps such as Nearest, Perpendicular,
// Tangent and Extension can move along geometry and would otherwise
// create arbitrary tracking points while the cursor is hovering. (#716)
let snap_world = snap.and_then(|hit| {
matches!(
hit.snap_type,
SnapType::Endpoint
| SnapType::Midpoint
| SnapType::Center
| SnapType::Node
| SnapType::Quadrant
| SnapType::Intersection
| SnapType::Insertion
| SnapType::ApparentIntersection
)
.then_some(hit.world)
});
// With OTRACK off, acquisition is Extension-driven, and Extension tracks
// a line only from a real segment endpoint — so acquire endpoints only.
// This stops a paused cursor on an extension foot (or a midpoint/centre)
@@ -1186,33 +1204,30 @@ impl Snapper {
}
// ── Perpendicular — foot of perpendicular from the drawing base ──
// Drop the foot from the point the command is drawing *from* (so the
// new segment is truly perpendicular to the target). Only when there
// is no base point — e.g. picking the very first point — does it fall
// back to the cursor (a plain nearest-on-line). The candidate is gated
// on its screen distance to the cursor like every other snap, so it
// offers when the cursor is near the perpendicular foot. (#118)
// A perpendicular snap requires a real drawing base. Without one
// (for example while picking the first point of LINE) there is no
// meaningful perpendicular direction, so do not offer a candidate.
// Falling back to the cursor here would make Perpendicular behave
// like Nearest. (#716)
if self.is_on(SnapType::Perpendicular) {
let q = self
.from_point
.map(|v| v.as_dvec3())
.unwrap_or(cursor_world);
if let Some(segments) = &local_segments {
for seg in segments {
if let Some(foot) = perp_foot(q, seg.a, seg.b) {
try_pt(foot, SnapType::Perpendicular);
}
}
} else {
for wire in wires.iter() {
if !wire_in_range(wire) {
continue;
}
for i in 0..wire.points.len().saturating_sub(1) {
if let Some(foot) = perp_foot(q, wp_f64(wire, i), wp_f64(wire, i + 1)) {
if let Some(q) = self.from_point.map(|v| v.as_dvec3()) {
if let Some(segments) = &local_segments {
for seg in segments {
if let Some(foot) = perp_foot(q, seg.a, seg.b) {
try_pt(foot, SnapType::Perpendicular);
}
}
} else {
for wire in wires.iter() {
if !wire_in_range(wire) {
continue;
}
for i in 0..wire.points.len().saturating_sub(1) {
if let Some(foot) = perp_foot(q, wp_f64(wire, i), wp_f64(wire, i + 1)) {
try_pt(foot, SnapType::Perpendicular);
}
}
}
}
}
}