diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index 074e2c05b7..e4451ba67e 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -2766,22 +2766,25 @@ pub fn section_plane(app: &SolveApp) -> Option<(Vec3, Vec3)> { scratch.execute("inspect.section", &p).ok()?; return scratch.section; } - // Slice (Sketch Palette): cut the model at the plane of the sketch being edited. - if app.session.section.is_none() { - let st = crate::open_meshing::shown_world(app); - if let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)).filter(|ss| ss.sketch.view.slice) { - return Some((ss.plane.origin, ss.plane.normal())); - } - } - app.session.section + slice_plane(app).or(app.session.section) +} + +/// Slice (Sketch Palette): the plane of the sketch being edited, when Slice is on in it. It +/// stands in for a Section Analysis until it is turned off or the sketch closes: with the +/// section first, turning Slice on did nothing (#10705). +fn slice_plane(app: &SolveApp) -> Option<(Vec3, Vec3)> { + let id = app.session.active_sketch?; + let st = crate::open_meshing::shown_world(app); + let ss = st.sketch(id).filter(|ss| ss.sketch.view.slice)?; + Some((ss.plane.origin, ss.plane.normal())) } /// Whether the section [`section_plane`] shows has hatched cut faces: an open Section Analysis -/// dialog's Hatch option, else the section's own. +/// dialog's Hatch option, a Slice's always, else the section's own. pub fn section_hatch(app: &SolveApp) -> bool { match app.dialog.as_ref().map(|d| &d.kind) { Some(crate::dialogs::Kind::Section { hatch, .. }) => *hatch, - _ => app.session.section_hatch, + _ => app.session.section_hatch || slice_plane(app).is_some(), } } @@ -9929,6 +9932,32 @@ mod tests { assert!(!legend(&mut h)); } + #[test] + fn a_sketch_slice_cuts_at_the_sketch_while_a_section_is_on() { + // #10705: with a Section Analysis on, turning Slice on in a sketch did nothing. + let mut h = crate::scenario::Harness::new(); + h.app.run("solid.box", json!({"length":40,"width":30,"height":20,"body_name":"Block"})).unwrap(); + h.app.run("construct.plane.offset", json!({"base":"XY","offset":10,"name":"Mid"})).unwrap(); + h.app.run("sketch.create", json!({"plane":"Mid"})).unwrap(); + h.app.run("inspect.section", json!({"plane":"XZ","offset":15,"hatch":false})).unwrap(); + h.frames(2); + let across_y = |h: &crate::scenario::Harness| section_plane(&h.app).is_some_and(|(o, n)| n.y.abs() > 0.99 && (o.y.abs() - 15.0).abs() < 1e-9); + assert!(across_y(&h)); + assert!(!section_hatch(&h.app)); + h.app.run("sketch.options", json!({"slice":true})).unwrap(); + h.frames(2); + let (o, n) = section_plane(&h.app).expect("the slice"); + assert!(n.z.abs() > 0.99 && (o.z - 10.0).abs() < 1e-9, "{o:?} {n:?}"); + assert!(section_hatch(&h.app), "a slice is hatched as it is without a section"); + // Slice off, or the sketch closed: the section is back. + h.app.run("sketch.options", json!({"slice":false})).unwrap(); + assert!(across_y(&h)); + h.app.run("sketch.options", json!({"slice":true})).unwrap(); + h.app.run("sketch.finish", json!({})).unwrap(); + h.frames(2); + assert!(across_y(&h)); + } + #[test] fn a_section_leaves_the_origin_planes_and_axes_whole() { // #1229: the origin planes and axes are reference geometry; the section plane cuts the