feat: Grid snap adaptive spacing + Polar tracking improvements

Grid snap (F9):
  • snap spacing now tracks the visible grid zoom level — computed via
    compute_grid_step() (same adaptive pow-of-5 logic as draw_grid),
    updated on every ViewportMove so zooming in/out keeps snap aligned
    with the grid lines on screen

Polar tracking (F10):
  • configurable angle increment stored in polar_increment_deg (default 45°)
  • right-click the POLAR pill to cycle through 15 → 30 → 45 → 90°
  • pill label shows current angle ("POLAR 45°")
  • visual guide line: while drawing with polar on, a dashed cyan line is
    drawn from last_point along the snapped direction across the viewport
  • SetPolarAngle message + handler; all polar_constrain() calls use the
    stored increment instead of the hardcoded 45°

ROADMAP updated: Grid snap and Polar tracking marked ✅.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-07 19:30:25 +03:00
commit 4c1c13cab6
6 changed files with 165 additions and 21 deletions

View file

@ -43,8 +43,10 @@ pub(super) struct H7CAD {
pre_cmd_tangent: Option<bool>,
/// Orthogonal drawing constraint (F8): constrains picks to 0°/90°/180°/270°.
ortho_mode: bool,
/// Polar tracking (F10): constrains picks to 45° angle increments.
/// Polar tracking (F10): constrains picks to configurable angle increments.
polar_mode: bool,
/// Polar tracking angle increment in degrees (15 / 30 / 45 / 90).
polar_increment_deg: f32,
/// Show grid lines in the viewport (F7).
show_grid: bool,
/// Show the UCS icon in the bottom-left corner of model space (UCSICON).
@ -224,8 +226,10 @@ pub enum Message {
ToggleGrid,
/// Toggle orthogonal drawing constraint — F8 / ORTHO status-bar button.
ToggleOrtho,
/// Toggle polar-angle constraint (45° increments) — F10 / POLAR status-bar button.
/// Toggle polar-angle constraint — F10 / POLAR status-bar button.
TogglePolar,
/// Set polar tracking angle increment (right-click POLAR button).
SetPolarAngle(f32),
/// Toggle an individual snap mode (from popup row click).
ToggleSnap(crate::snap::SnapType),
/// Open / close the OSNAP popup (▾ arrow click).
@ -429,6 +433,7 @@ impl H7CAD {
pre_cmd_tangent: None,
ortho_mode: false,
polar_mode: false,
polar_increment_deg: 45.0,
show_grid: false,
show_ucs_icon: true,
last_point: None,

View file

@ -814,7 +814,7 @@ impl H7CAD {
if self.ortho_mode {
snapped = ortho_constrain(snapped, base);
} else if self.polar_mode {
snapped = polar_constrain(snapped, base, 45.0);
snapped = polar_constrain(snapped, base, self.polar_increment_deg);
}
}
@ -846,6 +846,9 @@ impl H7CAD {
.pick_on_target_plane(p, bounds)
};
let view_proj = self.tabs[i].scene.camera.borrow().view_proj(bounds);
// Sync grid-snap spacing to the adaptive spacing of the visible grid.
self.snapper.grid_spacing =
crate::ui::overlay::compute_grid_step(view_proj, bounds);
// In MSPACE, map paper-space cursor to model space so that
// command previews and snapping work in the correct coordinate space.
let cursor_world = self.tabs[i].scene.paper_to_model(cursor_paper);
@ -879,14 +882,14 @@ impl H7CAD {
if self.ortho_mode {
pt = ortho_constrain(pt, base);
} else if self.polar_mode {
pt = polar_constrain(pt, base, 45.0);
pt = polar_constrain(pt, base, self.polar_increment_deg);
}
}
pt
};
self.tabs[i].last_cursor_world = effective;
let previews = if needs_entity {
let mut previews = if needs_entity {
let hover_handle =
scene::hit_test::click_hit(p, &all_wires, view_proj, bounds)
.and_then(|s| Scene::handle_from_wire_name(s))
@ -899,6 +902,37 @@ impl H7CAD {
.map(|c| c.on_preview_wires(effective))
.unwrap_or_default()
};
// Polar tracking guide line: dotted line from last_point along
// the snapped angle direction, extending across the drawing.
if self.polar_mode && !needs_entity {
if let Some(base) = self.last_point {
let dx = effective.x - base.x;
let dy = effective.y - base.y;
if (dx * dx + dy * dy).sqrt() > 1e-4 {
let far = 1e5_f32;
let dir = glam::Vec3::new(dx, dy, 0.0).normalize();
let far_pos = base + dir * far;
let far_neg = base - dir * far;
let guide = crate::scene::WireModel {
name: "__polar_guide__".into(),
points: vec![
[far_neg.x, far_neg.y, far_neg.z],
[far_pos.x, far_pos.y, far_pos.z],
],
color: [0.2, 0.7, 0.9, 0.6],
selected: false,
aci: 0,
pattern_length: 0.8,
pattern: [0.5, -0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
line_weight_px: 1.0,
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices: vec![],
};
previews.push(guide);
}
}
}
self.tabs[i].scene.set_preview_wires(previews);
} else {
self.tabs[i].snap_result = None;
@ -1046,7 +1080,7 @@ impl H7CAD {
if self.ortho_mode {
pt = ortho_constrain(pt, base);
} else if self.polar_mode {
pt = polar_constrain(pt, base, 45.0);
pt = polar_constrain(pt, base, self.polar_increment_deg);
}
}
pt
@ -1481,6 +1515,12 @@ impl H7CAD {
if self.polar_mode { self.ortho_mode = false; }
Task::none()
}
Message::SetPolarAngle(deg) => {
self.polar_increment_deg = deg;
self.polar_mode = true;
self.ortho_mode = false;
Task::none()
}
Message::ToggleSnap(t) => { self.snapper.toggle(t); Task::none() }
Message::ToggleSnapPopup => { self.snap_popup_open ^= true; Task::none() }
Message::CloseSnapPopup => { self.snap_popup_open = false; Task::none() }

View file

@ -177,6 +177,7 @@ impl H7CAD {
self.snap_popup_open,
self.ortho_mode,
self.polar_mode,
self.polar_increment_deg,
self.show_grid,
tab.scene.layout_names(),
tab.scene.current_layout.clone(),