forked from 1b-agent/opencadstudio-upstream
fix(snap): parallel snap keeps its reference, base and lock in f64
The reference direction, base point and locked point went through f32, so the drawn line drifted off true parallel. Fixes #1387 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
399b5b2c95
commit
c964e365c3
3 files changed
+29
-30
No files matched your search
@@ -2293,7 +2293,7 @@ impl OpenCADStudio {
|
||||
Instant::now(),
|
||||
);
|
||||
self.snapper.update_parallel(
|
||||
cursor_world.as_vec3(),
|
||||
cursor_world,
|
||||
&snap_candidates,
|
||||
view_rot,
|
||||
eye,
|
||||
@@ -2324,8 +2324,8 @@ impl OpenCADStudio {
|
||||
// (#277)
|
||||
if axis_lock.is_none() && self.tabs[i].snap_result.is_none() && otrack_hit.is_none() {
|
||||
if let Some(par) = self.snapper.parallel_snap(
|
||||
cursor_world.as_vec3(),
|
||||
self.last_point.map(|p| p.as_vec3()),
|
||||
cursor_world,
|
||||
self.last_point,
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
@@ -2333,7 +2333,7 @@ impl OpenCADStudio {
|
||||
if let (Some(base), Some((dir, _))) =
|
||||
(self.last_point, self.snapper.parallel_ref)
|
||||
{
|
||||
self.otrack_active = Some((base, dir.as_dvec3()));
|
||||
self.otrack_active = Some((base, dir));
|
||||
self.otrack_cross = None;
|
||||
}
|
||||
self.tabs[i].snap_result = Some(par);
|
||||
|
||||
+1
-1
@@ -789,7 +789,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
let parallel_ref_marker: Option<iced::Point> =
|
||||
match (otrack_proj, self.snapper.parallel_ref) {
|
||||
(Some((view_rot, eye, ob)), Some((_, pt))) => {
|
||||
let s = ost_project(pt.as_dvec3(), view_rot, eye, ob);
|
||||
let s = ost_project(pt, view_rot, eye, ob);
|
||||
(s.x.is_finite() && s.y.is_finite()).then_some(s)
|
||||
}
|
||||
_ => None,
|
||||
|
||||
+24
-25
@@ -252,11 +252,11 @@ pub struct Snapper {
|
||||
/// parallel to this, the point locks onto that parallel line; the point half
|
||||
/// marks the reference on screen. Works with only the Parallel object snap
|
||||
/// on — independent of OTRACK (#277).
|
||||
pub parallel_ref: Option<(Vec3, Vec3)>,
|
||||
pub parallel_ref: Option<(DVec3, DVec3)>,
|
||||
/// Dwell state for acquiring/removing `parallel_ref`: the candidate line
|
||||
/// direction + point under the cursor, when it was first hovered, and
|
||||
/// whether this dwell has already fired (so it acquires/toggles once).
|
||||
parallel_dwell: Option<(Vec3, Vec3, Instant, bool)>,
|
||||
parallel_dwell: Option<(DVec3, DVec3, Instant, bool)>,
|
||||
/// One-shot snap override (Shift+RMB menu): the (enabled set, snap on)
|
||||
/// pair saved when the override engaged, restored when it is consumed by
|
||||
/// the next point pick or cancelled. While `Some`, `enabled` holds only
|
||||
@@ -1025,7 +1025,7 @@ impl Snapper {
|
||||
/// is undefined. Call on every viewport move. (#277)
|
||||
pub fn update_parallel<W: WireSource + ?Sized>(
|
||||
&mut self,
|
||||
cursor_world: Vec3,
|
||||
cursor_world: DVec3,
|
||||
wires: &W,
|
||||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
@@ -1038,7 +1038,7 @@ impl Snapper {
|
||||
return;
|
||||
}
|
||||
const PAR_DWELL_MS: u128 = 150;
|
||||
let parallel = |a: Vec3, b: Vec3| (a.x * b.x + a.y * b.y).abs() > 0.9998;
|
||||
let parallel = |a: DVec3, b: DVec3| (a.x * b.x + a.y * b.y).abs() > 0.9998;
|
||||
let Some((dir, pt)) = nearest_segment(
|
||||
cursor_world,
|
||||
wires,
|
||||
@@ -1081,8 +1081,8 @@ impl Snapper {
|
||||
/// of that line). Independent of OTRACK. (#277)
|
||||
pub fn parallel_snap(
|
||||
&self,
|
||||
cursor_world: Vec3,
|
||||
base: Option<Vec3>,
|
||||
cursor_world: DVec3,
|
||||
base: Option<DVec3>,
|
||||
view_rot: glam::Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: iced::Rectangle,
|
||||
@@ -1095,18 +1095,18 @@ impl Snapper {
|
||||
let d = cursor_world - base;
|
||||
// Need a bit of travel from the base, and the cursor must be pulling
|
||||
// roughly along the reference (not backward-only noise near the base).
|
||||
if (d.x * d.x + d.y * d.y).sqrt() < self.grid_spacing * 0.01 {
|
||||
if (d.x * d.x + d.y * d.y).sqrt() < self.grid_spacing as f64 * 0.01 {
|
||||
return None;
|
||||
}
|
||||
let t = d.x * dir.x + d.y * dir.y;
|
||||
let locked = base + dir * t;
|
||||
let sl = world_to_screen(locked.as_dvec3(), view_rot, eye, bounds);
|
||||
let sc = world_to_screen(cursor_world.as_dvec3(), view_rot, eye, bounds);
|
||||
let sl = world_to_screen(locked, view_rot, eye, bounds);
|
||||
let sc = world_to_screen(cursor_world, view_rot, eye, bounds);
|
||||
if dist2(sl, sc) > self.osnap_radius_px * self.osnap_radius_px {
|
||||
return None; // cursor not near the parallel line — don't lock
|
||||
}
|
||||
Some(SnapResult {
|
||||
world: locked.as_dvec3(),
|
||||
world: locked,
|
||||
screen: sl,
|
||||
snap_type: SnapType::Parallel,
|
||||
tangent_obj: None,
|
||||
@@ -3475,16 +3475,16 @@ pub(crate) fn foot_on_triangle(
|
||||
/// Center snap hint and "parallel to a curve" is meaningless. Used to acquire
|
||||
/// the Parallel-snap reference. (#277)
|
||||
fn nearest_segment<W: WireSource + ?Sized>(
|
||||
cursor_world: Vec3,
|
||||
cursor_world: DVec3,
|
||||
wires: &W,
|
||||
view_rot: Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: Rectangle,
|
||||
aperture_px: f32,
|
||||
) -> Option<(Vec3, Vec3)> {
|
||||
let cs = world_to_screen(cursor_world.as_dvec3(), view_rot, eye, bounds);
|
||||
) -> Option<(DVec3, DVec3)> {
|
||||
let cs = world_to_screen(cursor_world, view_rot, eye, bounds);
|
||||
let mut best_d2 = aperture_px * aperture_px;
|
||||
let mut best: Option<(Vec3, Vec3)> = None;
|
||||
let mut best: Option<(DVec3, DVec3)> = None;
|
||||
if let Some(segments) = indexed_segments(wires) {
|
||||
for segment in segments {
|
||||
let Some(wire) = wires.source_wire(segment.wire) else {
|
||||
@@ -3506,9 +3506,8 @@ fn nearest_segment<W: WireSource + ?Sized>(
|
||||
let l = (dx * dx + dy * dy).sqrt();
|
||||
if l > 1e-9 {
|
||||
best_d2 = d2;
|
||||
let dir = Vec3::new((dx / l) as f32, (dy / l) as f32, 0.0);
|
||||
let np =
|
||||
nearest_on_segment(cursor_world.as_dvec3(), segment.a, segment.b).as_vec3();
|
||||
let dir = DVec3::new(dx / l, dy / l, 0.0);
|
||||
let np = nearest_on_segment(cursor_world, segment.a, segment.b);
|
||||
best = Some((dir, np));
|
||||
}
|
||||
}
|
||||
@@ -3538,8 +3537,8 @@ fn nearest_segment<W: WireSource + ?Sized>(
|
||||
let l = (dx * dx + dy * dy).sqrt();
|
||||
if l > 1e-9 {
|
||||
best_d2 = d2;
|
||||
let dir = Vec3::new((dx / l) as f32, (dy / l) as f32, 0.0);
|
||||
let np = nearest_on_segment(cursor_world.as_dvec3(), a, b).as_vec3();
|
||||
let dir = DVec3::new(dx / l, dy / l, 0.0);
|
||||
let np = nearest_on_segment(cursor_world, a, b);
|
||||
best = Some((dir, np));
|
||||
}
|
||||
}
|
||||
@@ -3552,16 +3551,16 @@ fn nearest_segment<W: WireSource + ?Sized>(
|
||||
/// line through `line_pt` along `line_dir`. Used to tell whether a hovered line
|
||||
/// is the acquired parallel reference (same line) regardless of zoom. (#277)
|
||||
fn screen_perp_dist(
|
||||
q: Vec3,
|
||||
line_pt: Vec3,
|
||||
line_dir: Vec3,
|
||||
q: DVec3,
|
||||
line_pt: DVec3,
|
||||
line_dir: DVec3,
|
||||
view_rot: Mat4,
|
||||
eye: glam::DVec3,
|
||||
bounds: Rectangle,
|
||||
) -> f32 {
|
||||
let sq = world_to_screen(q.as_dvec3(), view_rot, eye, bounds);
|
||||
let s0 = world_to_screen(line_pt.as_dvec3(), view_rot, eye, bounds);
|
||||
let s1 = world_to_screen((line_pt + line_dir).as_dvec3(), view_rot, eye, bounds);
|
||||
let sq = world_to_screen(q, view_rot, eye, bounds);
|
||||
let s0 = world_to_screen(line_pt, view_rot, eye, bounds);
|
||||
let s1 = world_to_screen(line_pt + line_dir, view_rot, eye, bounds);
|
||||
let ex = s1.x - s0.x;
|
||||
let ey = s1.y - s0.y;
|
||||
let l = (ex * ex + ey * ey).sqrt();
|
||||
|
||||
Reference in new issue
Block a user