feat(snap): parallel object snap (#277)

Parallel snap did nothing — the type was in the osnap set, priority and
marker, but there was no logic behind it. Implement it: hovering a line
(or polyline segment) for a short dwell acquires its direction as a
reference; then while drawing from the last point, when the cursor runs
parallel to that reference the point locks onto the line through the last
point parallel to it, showing the ∥ marker and the alignment guide.
Curves (circle/arc/ellipse) are ignored — parallel to a curve is
undefined. Works with only the Parallel object snap on, independent of
OTRACK; the reference clears with the command's tracking state.

Closes #277

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-05 13:24:43 +03:00
1 parent 8ad0c8918a
commit e85cec8274
3 files changed
+184 -1

No files matched your search

+31
View File
@@ -906,6 +906,16 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
bounds,
Instant::now(),
);
// Parallel snap: acquire the reference line under the
// cursor (independent of OTRACK). (#277)
self.snapper.update_parallel(
cursor_world.as_vec3(),
&all_wires[..],
view_rot,
eye,
bounds,
Instant::now(),
);
if self.tabs[i].snap_result.is_none() {
let step = if self.polar_mode {
Some(self.polar_increment_deg)
@@ -929,6 +939,27 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
};
self.otrack_active = otrack_hit.map(|h| (h.base, h.dir));
// Parallel snap: with nothing else snapped or tracked, lock
// the point onto the line through last_point parallel to the
// acquired reference, and drive the alignment guide off it.
// (#277)
if 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,
view_rot,
eye,
bounds,
) {
if let (Some(base), Some(dir)) =
(self.last_point, self.snapper.parallel_ref)
{
self.otrack_active = Some((base, dir));
}
self.tabs[i].snap_result = Some(par);
}
}
let effective = {
let mut pt: glam::DVec3 = if let Some(h) = otrack_hit {
// Tracking alignment wins over the free cursor;
+1 -1
View File
@@ -340,7 +340,7 @@ impl OpenCADStudio {
// Shared OTRACK projection basis: the active pane's camera + rect
// (canvas offset included), matching the grips / UCS icon above.
let otrack_proj: Option<(glam::Mat4, glam::DVec3, iced::Rectangle)> =
if self.snapper.tracking_active() {
if self.snapper.alignment_active() {
Some(
if let Some((vp_cam, full)) = tab.scene.viewport_edit_frame((vw, vh)) {
(vp_cam.view_proj_rte(full), vp_cam.eye(), full)