Compare commits
1
Commits
main
...
sketch-grid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a940f77447 |
No files matched your search
@@ -349,6 +349,20 @@ pub fn scene_with_section(s: &Session, cam: &Camera, section: Option<(Vec3, Vec3
|
|||||||
scene_with_guides(s, cam, section, Guides::default())
|
scene_with_guides(s, cam, section, Guides::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The plane of the sketch that is open, if any: it always shows a grid, whatever the Grid switch
|
||||||
|
/// says (outside a sketch the grid is the user's choice).
|
||||||
|
pub fn open_sketch_plane(s: &Session) -> Option<Plane> {
|
||||||
|
let id = s.active_sketch?;
|
||||||
|
s.world_state().sketch(id).map(|ss| ss.plane)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The grid on a sketch plane: its centre (the view target's foot on the plane, snapped to the
|
||||||
|
/// major step in the plane's own axes) and the plane's x and y directions.
|
||||||
|
pub fn sketch_grid_frame(plane: &Plane, target: Vec3, major: f64) -> (Vec3, Vec3, Vec3) {
|
||||||
|
let snap = |d: Vec3| d * (((target - plane.origin).dot(d) / major).round() * major);
|
||||||
|
(plane.origin + snap(plane.x) + snap(plane.y), plane.x, plane.y)
|
||||||
|
}
|
||||||
|
|
||||||
/// What a headless render draws besides the model: the ground grid and the origin's axes.
|
/// What a headless render draws besides the model: the ground grid and the origin's axes.
|
||||||
/// Both are on by default; the grid lies on Z 0, so it cuts through a model that straddles it.
|
/// Both are on by default; the grid lies on Z 0, so it cuts through a model that straddles it.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@@ -434,12 +448,17 @@ fn build_scene(
|
|||||||
if guides.grid {
|
if guides.grid {
|
||||||
let (minor, major) = grid_step(cam.half_height());
|
let (minor, major) = grid_step(cam.half_height());
|
||||||
let ext = (cam.half_height() * 3.0 / major).ceil() * major;
|
let ext = (cam.half_height() * 3.0 / major).ceil() * major;
|
||||||
let tf = cam.to_frame(cam.target);
|
// Headless pictures keep the grid on the ground, the viewport's default; an open sketch
|
||||||
// Headless pictures keep the grid on the ground, the viewport's default.
|
// has its own plane's grid (world coordinates, no camera frame).
|
||||||
let (u, v) = (Vec3::X, Vec3::Y);
|
let (c, u, v, w): (Vec3, Vec3, Vec3, Box<dyn Fn(Vec3) -> Vec3>) = if let Some(pl) = open_sketch_plane(s) {
|
||||||
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
let (c, u, v) = sketch_grid_frame(&pl, cam.target, major);
|
||||||
let c = snap(u) + snap(v);
|
(c, u, v, Box::new(|p| p))
|
||||||
let w = |p: Vec3| cam.from_frame(p);
|
} else {
|
||||||
|
let tf = cam.to_frame(cam.target);
|
||||||
|
let (u, v) = (Vec3::X, Vec3::Y);
|
||||||
|
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
||||||
|
(snap(u) + snap(v), u, v, Box::new(|p| cam.from_frame(p)))
|
||||||
|
};
|
||||||
let n = ((ext / minor) as i64).clamp(1, 400);
|
let n = ((ext / minor) as i64).clamp(1, 400);
|
||||||
for i in -n..=n {
|
for i in -n..=n {
|
||||||
let t = i as f64 * minor;
|
let t = i as f64 * minor;
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ pub(crate) fn view_scene(app: &SolveApp, guides: solidcraft_engine::view::Guides
|
|||||||
|
|
||||||
/// The grid and the origin's axes as the window shows them (the Grid and Origin switches).
|
/// The grid and the origin's axes as the window shows them (the Grid and Origin switches).
|
||||||
pub(crate) fn window_guides(app: &SolveApp) -> solidcraft_engine::view::Guides {
|
pub(crate) fn window_guides(app: &SolveApp) -> solidcraft_engine::view::Guides {
|
||||||
solidcraft_engine::view::Guides { grid: app.ui.show_grid && !crate::viewport::active_view(app).hide_grid, axes: app.origin_visible() }
|
solidcraft_engine::view::Guides { grid: app.session.active_sketch.is_some() || (app.ui.show_grid && !crate::viewport::active_view(app).hide_grid), axes: app.origin_visible() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A picture of the view as the window shows it, RGBA, drawn on the GPU with the viewport's
|
/// A picture of the view as the window shows it, RGBA, drawn on the GPU with the viewport's
|
||||||
|
|||||||
@@ -733,33 +733,39 @@ fn build_sketch_scene(app: &SolveApp, proj: &Proj) -> GpuScene {
|
|||||||
let s = &app.session;
|
let s = &app.session;
|
||||||
let st = s.world_state();
|
let st = s.world_state();
|
||||||
let mut fill_budget = 131_072;
|
let mut fill_budget = 131_072;
|
||||||
if app.ui.show_grid && !active_view(app).hide_grid {
|
let sketch_plane = solidcraft_engine::view::open_sketch_plane(s);
|
||||||
|
if sketch_plane.is_some() || (app.ui.show_grid && !active_view(app).hide_grid) {
|
||||||
let (minor, major) = grid_step(app.cam.half_height());
|
let (minor, major) = grid_step(app.cam.half_height());
|
||||||
let ext = (app.cam.half_height() * 3.0 / major).ceil() * major;
|
let ext = (app.cam.half_height() * 3.0 / major).ceil() * major;
|
||||||
// The ground plane: XY with Z up, XZ with Y up (laid out in the camera's Z-up frame), or
|
// An open sketch always has its grid on its own plane; otherwise the ground plane: XY with
|
||||||
// the upright origin plane facing the view when the ground is seen edge-on.
|
// Z up, XZ with Y up (laid out in the camera's Z-up frame), or the upright origin plane
|
||||||
let tf = app.cam.to_frame(app.cam.target);
|
// facing the view when the ground is seen edge-on.
|
||||||
// On the ground (XY) unless the Grid follows the view preference is on (#1470); then on
|
let (c, u, v, w3): (Vec3, Vec3, Vec3, Box<dyn Fn(Vec3) -> [f32; 3]>) = if let Some(pl) = &sketch_plane {
|
||||||
// the plane the last frame drew, so the choice is hysteretic near the thresholds (#1993).
|
let (c, u, v) = solidcraft_engine::view::sketch_grid_frame(pl, app.cam.target, major);
|
||||||
let (u, v) = if app.preferences.grid_follows_view {
|
(c, u, v, Box::new(|p| p.to_f32()))
|
||||||
app.viewport.grid_plane.lock().map_or((Vec3::X, Vec3::Y), |mut last| {
|
|
||||||
*last = grid_plane(app.cam.to_frame(app.cam.back()), *last);
|
|
||||||
last.axes()
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
(Vec3::X, Vec3::Y)
|
let tf = app.cam.to_frame(app.cam.target);
|
||||||
|
// On the ground (XY) unless the Grid follows the view preference is on (#1470); then on
|
||||||
|
// the plane the last frame drew, so the choice is hysteretic near the thresholds (#1993).
|
||||||
|
let (u, v) = if app.preferences.grid_follows_view {
|
||||||
|
app.viewport.grid_plane.lock().map_or((Vec3::X, Vec3::Y), |mut last| {
|
||||||
|
*last = grid_plane(app.cam.to_frame(app.cam.back()), *last);
|
||||||
|
last.axes()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
(Vec3::X, Vec3::Y)
|
||||||
|
};
|
||||||
|
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
||||||
|
(snap(u) + snap(v), u, v, Box::new(|p| app.cam.from_frame(p).to_f32()))
|
||||||
};
|
};
|
||||||
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
|
||||||
let c = snap(u) + snap(v);
|
|
||||||
let w3 = |p: Vec3| app.cam.from_frame(p).to_f32();
|
|
||||||
let n = ((ext / minor) as i64).clamp(1, 300);
|
let n = ((ext / minor) as i64).clamp(1, 300);
|
||||||
for i in -n..=n {
|
for i in -n..=n {
|
||||||
let t = i as f64 * minor;
|
let t = i as f64 * minor;
|
||||||
let maj = (t / major).round() * major == t;
|
let maj = (t / major).round() * major == t;
|
||||||
let col = if maj { c4(tk.grid_major) } else { c4(tk.grid) };
|
let col = if maj { c4(tk.grid_major) } else { c4(tk.grid) };
|
||||||
let w = if maj { 1.0 } else { 0.7 };
|
let wd = if maj { 1.0 } else { 0.7 };
|
||||||
sc.under_line(w3(c + u * t - v * ext), w3(c + u * t + v * ext), col, w);
|
sc.under_line(w3(c + u * t - v * ext), w3(c + u * t + v * ext), col, wd);
|
||||||
sc.under_line(w3(c - u * ext + v * t), w3(c + u * ext + v * t), col, w);
|
sc.under_line(w3(c - u * ext + v * t), w3(c + u * ext + v * t), col, wd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for sid in visible_sketches(app) {
|
for sid in visible_sketches(app) {
|
||||||
@@ -6188,6 +6194,28 @@ mod grid_plane_tests {
|
|||||||
assert_eq!(*app.viewport.grid_plane.lock().unwrap(), GridPlane::Ground, "a clear turn off the ground returns");
|
assert_eq!(*app.viewport.grid_plane.lock().unwrap(), GridPlane::Ground, "a clear turn off the ground returns");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An open sketch always has its grid, on its own plane, whatever the Grid switch and the
|
||||||
|
/// follow-the-view preference say; outside a sketch the switch decides.
|
||||||
|
#[test]
|
||||||
|
fn an_open_sketch_always_shows_a_grid_on_its_plane() {
|
||||||
|
let rect = Rect::from_min_size(Pos2::ZERO, egui::vec2(800.0, 600.0));
|
||||||
|
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||||
|
app.ui.show_grid = false;
|
||||||
|
let grid = |app: &SolveApp| build_sketch_scene(app, &projection(app, rect)).under;
|
||||||
|
assert!(grid(&app).is_empty(), "no grid outside a sketch with the switch off");
|
||||||
|
app.run("sketch.create", json!({"plane": "XZ"})).unwrap();
|
||||||
|
for follows in [false, true] {
|
||||||
|
app.preferences.grid_follows_view = follows;
|
||||||
|
let under = grid(&app);
|
||||||
|
assert!(!under.is_empty(), "the open sketch shows a grid (follows the view: {follows})");
|
||||||
|
// Each line is 32 bytes: two f32 points, a colour and a width; the XZ plane is Y = 0.
|
||||||
|
let ys: Vec<f32> = under.chunks_exact(32).flat_map(|l| [&l[4..8], &l[16..20]]).map(|b| f32::from_le_bytes(b.try_into().unwrap())).collect();
|
||||||
|
assert!(ys.iter().all(|y| y.abs() < 1e-3), "the grid lies on the sketch plane");
|
||||||
|
}
|
||||||
|
app.ui.show_grid = true;
|
||||||
|
assert!(!grid(&app).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
/// A sketch is drawn in a parallel view even when the preference asks for perspective; the
|
/// A sketch is drawn in a parallel view even when the preference asks for perspective; the
|
||||||
/// preference comes back when the sketch is finished.
|
/// preference comes back when the sketch is finished.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user