From 888ac0f5c4e152b578d959a2acc61c1484a5a783 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 02:22:42 +0000 Subject: [PATCH] Navigation bar: Look At (selected face or plane, else the active sketch) and visual style (shaded with edges, shaded, wireframe; kept between runs); F6 fits the view Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/ui-egui/src/icons.rs | 9 ++++++++ crates/ui-egui/src/lib.rs | 7 +++++++ crates/ui-egui/src/toolbar.rs | 1 + crates/ui-egui/src/viewport.rs | 38 +++++++++++++++++++++++++++++----- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index 9e198ced..12568f2e 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -183,6 +183,15 @@ pub fn paint(p: &Painter, r: Rect, name: &str, ink: Color32, fill: Color32, acce pen.poly(&[(3.0, 7.0), (9.0, 10.0), (9.0, 17.0), (3.0, 17.0)], pen.fill, ink); pen.poly(&[(21.0, 7.0), (15.0, 10.0), (15.0, 17.0), (21.0, 17.0)], Color32::TRANSPARENT, a); } + "lookat" => { + pen.poly(&[(4.0, 6.0), (16.0, 6.0), (16.0, 18.0), (4.0, 18.0)], pen.fill, ink); + pen.arrow((22.0, 12.0), (13.0, 12.0), a); + } + "style" => { + pen.poly(&[(12.0, 3.0), (20.0, 7.5), (12.0, 12.0), (4.0, 7.5)], pen.fill, ink); + pen.poly(&[(4.0, 7.5), (12.0, 12.0), (12.0, 21.0), (4.0, 16.5)], a, ink); + pen.poly(&[(12.0, 12.0), (20.0, 7.5), (20.0, 16.5), (12.0, 21.0)], Color32::TRANSPARENT, ink); + } "sun" => { pen.circle(12.0, 12.0, 4.5, a, ink); for i in 0..8 { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index e98b1592..37473f09 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -61,6 +61,8 @@ pub struct UiState { pub pick_off: Vec, /// A click on a face picks its whole body. pub pick_bodies: bool, + /// 0: shaded with edges, 1: shaded, 2: wireframe. + pub visual_style: u8, /// Sketches hidden one by one, and finished sketches shown although a feature uses them. pub hidden_sketches: Vec, pub shown_sketches: Vec, @@ -86,6 +88,7 @@ impl Default for UiState { dark: true, pick_off: Vec::new(), pick_bodies: false, + visual_style: 0, hidden_sketches: Vec::new(), shown_sketches: Vec::new(), hidden_profiles: Vec::new(), @@ -184,6 +187,7 @@ impl SolveApp { "perspective": self.ui.perspective, "pick_bodies": self.ui.pick_bodies, "auto_project": self.session.auto_project, + "visual_style": self.ui.visual_style, }) .to_string() } @@ -207,6 +211,9 @@ impl SolveApp { if let Some(a) = flag("auto_project") { self.session.auto_project = a; } + if let Some(s) = v.get("visual_style").and_then(Value::as_u64) { + self.ui.visual_style = u8::try_from(s.min(2)).unwrap_or(0); + } } pub fn with_control(mut self, rx: Receiver) -> Self { diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index 9af765f0..2e9881e8 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -406,6 +406,7 @@ pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) { } } Key::S => app.ui.palette_open = true, + Key::F6 => app.animate_view("fit"), Key::Delete | Key::Backspace => crate::viewport::delete_selection(app), _ => { let name = format!("{k:?}"); diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index 1576f122..5897e3b0 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -155,6 +155,7 @@ fn scene_key(app: &SolveApp) -> u64 { let mut h = std::collections::hash_map::DefaultHasher::new(); app.session.revision.hash(&mut h); app.ui.show_grid.hash(&mut h); + app.ui.visual_style.hash(&mut h); app.ui.show_sketches.hash(&mut h); app.ui.hidden_bodies.hash(&mut h); app.ui.hidden_sketches.hash(&mut h); @@ -210,7 +211,13 @@ fn build_scene(app: &SolveApp) -> GpuScene { let base = s.doc.materials.get(&b.name).and_then(|m| crate::theme::material_color(m)).unwrap_or(tk.body); let col = if faded { c4(base.gamma_multiply(0.35)) } else { c4(base) }; let m = b.mesh(); - for t in &m.triangles { + let (shaded, edges) = match app.ui.visual_style { + 1 => (true, false), + 2 => (false, true), + _ => (true, true), + }; + let edge_col = if shaded { c4(tk.body_edge) } else { c4(tk.text) }; + for t in m.triangles.iter().filter(|_| shaded) { for k in t { let i = *k as usize; if let (Some(p), Some(n)) = (m.positions.get(i), m.normals.get(i)) { @@ -222,12 +229,12 @@ fn build_scene(app: &SolveApp) -> GpuScene { } } } - for (ei, e) in m.edges.iter().enumerate() { + for (ei, e) in m.edges.iter().enumerate().filter(|_| edges) { if m.seams.get(ei).copied().unwrap_or(false) { continue; } for w in e.windows(2) { - sc.line(w[0].to_f32(), w[1].to_f32(), c4(tk.body_edge), 1.3, false); + sc.line(w[0].to_f32(), w[1].to_f32(), edge_col, 1.3, false); } } } @@ -1483,15 +1490,34 @@ fn point_in_poly(p: Pos2, poly: &[Pos2]) -> bool { } /// Navigation bar (bottom centre). +/// Turn the view to face the selected planar face or plane, else the active sketch. +pub fn look_at_selection(app: &mut SolveApp) { + let target = app.session.selection.iter().find_map(|s| match s { + Sel::Face { body, index, point } => crate::dialogs::planar_face(&app.session, body, *index).map(|(_, n)| (*point, n)), + Sel::Plane { name } => solvecraft_engine::geom::Plane::named(name).map(|p| (p.origin, p.normal())), + _ => None, + }); + match target { + Some((p, n)) => { + let mut to = app.cam.looking_from(n); + to.target = p; + app.animate_to(to); + } + None => crate::dialogs::look_at_sketch(app), + } +} + fn nav_bar(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) { let t = Tokens::get(); - let items: [(&str, &str); 7] = [ + let items: [(&str, &str); 9] = [ ("orbit", "Orbit (drag; or Shift+middle / right drag)"), ("pan", "Pan (drag; or middle drag)"), ("zoom", "Zoom (drag; or wheel)"), - ("fit", "Fit"), + ("fit", "Fit (F6)"), + ("lookat", "Look At the selected face or plane (or the active sketch)"), ("home", "Home view"), ("perspective", "Perspective / orthographic"), + ("style", "Visual style: shaded with edges, shaded, wireframe"), ("settings", "Grid on/off"), ]; let w = items.len() as f32 * 30.0 + 10.0; @@ -1521,6 +1547,8 @@ fn nav_bar(app: &mut SolveApp, ui: &mut egui::Ui, rect: Rect) { "zoom" => app.viewport.nav = toggle(NavMode::Zoom, app.viewport.nav), "fit" => app.animate_view("fit"), "home" => app.animate_view("home"), + "lookat" => look_at_selection(app), + "style" => app.ui.visual_style = (app.ui.visual_style + 1) % 3, "perspective" => app.ui.perspective = !app.ui.perspective, _ => app.ui.show_grid = !app.ui.show_grid, }