diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 537d6c4a5f..42604e5770 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1582,7 +1582,7 @@ impl Dialog { fn extrude_target(&self, s: &Session, h: &Hit) -> Option { let Kind::Extrude { thin: false, smart, .. } = &self.kind else { return None }; let to_active = self.active_input().is_some_and(|i| i.label == TO_OBJECT); - if !to_active && (self.active != 0 || !smart.auto_to_object) { + if !to_active && (self.active != 0 || !smart.auto_to_object || smart.profiles_chosen) { return None; } let reference = profile_plane(s, self.inputs.first()?.items.first()?)?; @@ -1607,7 +1607,10 @@ impl Dialog { /// To Object with it. False when the pick is not one. fn take_extrude_target(&mut self, s: &Session, sel: &Sel) -> bool { if self.active != 0 - || !matches!(self.kind, Kind::Extrude { thin: false, smart: crate::smart_extrude::Smart { auto_to_object: true, .. }, .. }) + || !matches!( + self.kind, + Kind::Extrude { thin: false, smart: crate::smart_extrude::Smart { auto_to_object: true, profiles_chosen: false, .. }, .. } + ) { return false; } @@ -2831,6 +2834,10 @@ pub(crate) fn input_rows_range(d: &mut Dialog, ui: &mut egui::Ui, s: &Session, n } if let Some(i) = activate { d.active = i; + // A click into the Profiles box is a choice to add profiles by pick (Extrude's automatic To Object). + if let Kind::Extrude { smart, .. } = &mut d.kind { + smart.profiles_chosen = i == 0; + } } if let Some(i) = clear { if let Some(key) = stored_input_reference_key(d, i) { diff --git a/crates/ui-egui/src/extrude_auto_to_object_tests.rs b/crates/ui-egui/src/extrude_auto_to_object_tests.rs index 757d89e0fd..955f755815 100644 --- a/crates/ui-egui/src/extrude_auto_to_object_tests.rs +++ b/crates/ui-egui/src/extrude_auto_to_object_tests.rs @@ -180,9 +180,7 @@ fn target_below_keeps_its_side_and_exact_length() { } #[test] -fn off_by_default_a_face_off_the_profile_plane_joins_the_profiles() { - let h = Harness::new(); - assert!(!h.app.preferences.extrude_auto_to_object, "the preference starts off"); +fn with_the_preference_off_a_face_off_the_profile_plane_joins_the_profiles() { let mut h = scene_with(false); assert!(click(&mut h, |h| face_hit(h, Vec3::new(40.0, 10.0, 20.0)))); let d = dialog(&h); @@ -209,3 +207,60 @@ fn manual_to_object_accepts_a_parallel_plane_with_automatic_switching_off() { assert_eq!(commands[0].1["to"], json!([0.0, 0.0, 25.0])); assert!((solidcraft_engine::extrude_reach(&h.app.session, &commands[0].1).unwrap() - 25.0).abs() < 1e-9); } + +#[test] +fn the_preference_starts_on() { + assert!(Harness::new().app.preferences.extrude_auto_to_object); +} + +/// The scene with the box's top face (z = 20) picked as the profile instead of the sketch. +fn face_profile_scene() -> Harness { + let mut h = scene(); + let top = face_sel(&h.app.session, Vec3::new(40.0, 10.0, 20.0)).unwrap(); + h.app.dialog.as_mut().unwrap().inputs[0].items = vec![top]; + h.frames(2); + h +} + +#[test] +fn face_profile_then_the_opposite_face_is_the_target() { + let mut h = face_profile_scene(); + assert!(click(&mut h, |h| face_hit(h, Vec3::new(40.0, 10.0, 0.0)))); + let d = dialog(&h); + assert!(matches!(to_object(d), Some(Sel::Face { .. })), "the bottom face is where it goes to"); + assert_eq!(d.inputs[0].items.len(), 1, "the top face stays the only profile"); + let commands = apply_commands(&h.app, d).unwrap(); + assert!(commands[0].1.get("to").is_some()); + let reach = solidcraft_engine::extrude_reach(&h.app.session, &commands[0].1).unwrap(); + assert!((reach + 20.0).abs() < 1e-9, "{reach}: 20 mm down through the body"); +} + +#[test] +fn face_profile_then_a_parallel_face_of_another_body_is_the_target() { + let mut h = face_profile_scene(); + assert!(click(&mut h, |h| face_hit(h, Vec3::new(70.0, 10.0, 30.0)))); + let d = dialog(&h); + assert!(matches!(to_object(d), Some(Sel::Face { .. }))); + assert_eq!(d.inputs[0].items.len(), 1); +} + +#[test] +fn a_side_face_is_a_target_at_its_picked_point() { + let mut h = face_profile_scene(); + assert!(click(&mut h, |h| face_hit(h, Vec3::new(50.0, 10.0, 10.0)))); + let d = dialog(&h); + assert!(matches!(to_object(d), Some(Sel::Face { .. })), "a non-parallel planar face is taken as the target"); + assert_eq!(d.inputs[0].items.len(), 1); +} + +#[test] +fn clicking_into_the_profiles_box_adds_an_off_plane_face_as_a_profile() { + let mut h = scene(); + let at = crate::scenario::rect_of("dialog:field:Profiles").expect("the Profiles field is drawn").center(); + h.step(&json!({"click": [at.x, at.y]})).unwrap(); + h.frames(2); + assert!(click(&mut h, |h| face_hit(h, Vec3::new(40.0, 10.0, 20.0)))); + let d = dialog(&h); + assert!(to_object(d).is_none(), "the box was chosen, so the pick is a profile"); + assert_eq!(d.inputs[0].items.len(), 2); +} diff --git a/crates/ui-egui/src/prefs.rs b/crates/ui-egui/src/prefs.rs index 33963ba8d1..97bcface5b 100644 --- a/crates/ui-egui/src/prefs.rs +++ b/crates/ui-egui/src/prefs.rs @@ -1334,8 +1334,9 @@ preference_settings! { /// over the view (the default), the dialog, or nothing (#7717). stand_up_unknown: StandUpUnknown = StandUpUnknown::default(), PAGE_DESIGN; /// Extrude: with a profile picked, a face, vertex or parallel plane picked off its plane - /// switches the extent to To Object (off: picks only add profiles, as before). - extrude_auto_to_object: bool = false, PAGE_TOOLS => check("Extrude to a picked target", "In Extrude, with a profile picked, picking a face, vertex or parallel plane off its plane switches the extent to To Object"); + /// switches the extent to To Object (off: picks only add profiles, as before). A click into the + /// Profiles box makes the next picks profiles again. + extrude_auto_to_object: bool = true, PAGE_TOOLS => check("Extrude to a picked target", "In Extrude, with a profile picked, picking a face, vertex or parallel plane off its plane switches the extent to To Object"); /// Extrude turns the view to isometric each time it opens, so the depth can be seen (#9176). /// Off (the default): the view stays as it is. extrude_view_isometric: bool = false, PAGE_TOOLS => check_id("prefs.extrude_view_isometric"); @@ -5787,7 +5788,7 @@ mod tests { ("direct_interaction", json!(true), PAGE_TOOLS), ("hole_snaps", json!(true), PAGE_TOOLS), ("hole_moves", json!(true), PAGE_TOOLS), - ("extrude_auto_to_object", json!(true), PAGE_TOOLS), + ("extrude_auto_to_object", json!(false), PAGE_TOOLS), ("extrude_view_isometric", json!(true), PAGE_TOOLS), ("extrude_view_return", json!(true), PAGE_TOOLS), ("auto_operation", json!(false), PAGE_TOOLS), diff --git a/crates/ui-egui/src/smart_extrude.rs b/crates/ui-egui/src/smart_extrude.rs index ec8989d77e..e8e9bb4ca1 100644 --- a/crates/ui-egui/src/smart_extrude.rs +++ b/crates/ui-egui/src/smart_extrude.rs @@ -40,6 +40,9 @@ pub struct Smart { pub shown: Option>, /// Picks off the profile plane become the To Object target (Preferences › Design). pub auto_to_object: bool, + /// The user clicked into the Profiles box: until another input is chosen, an off-plane pick + /// is one more profile, not the To Object target. + pub profiles_chosen: bool, /// The engine's automatic operation for the last parameters and design: a key over all it /// depends on, and its answer. It probes the bodies' triangles, too slow to ask every frame. pub auto: Option<(u64, Option<&'static str>)>, diff --git a/docs/book/src/guide/features.md b/docs/book/src/guide/features.md index 6d991b2112..9baf518b3d 100644 --- a/docs/book/src/guide/features.md +++ b/docs/book/src/guide/features.md @@ -39,14 +39,15 @@ one as a separate piece, and the feature's warning says so: "`Body1` is now in 2 pieces". Combine joins the bodies it is given without that warning. Changing the default does not alter operations already saved in a design. -**Extrude to a picked target** (Preferences › Design, off by default) lets a pick choose the +**Extrude to a picked target** (Preferences › Tools, on by default) lets a pick choose the extent. With it on and a profile selected in the solid Extrude dialog, picking a face away from its plane, a curved face, an off-plane vertex or a parallel plane automatically switches **Distance** to **To Object**. Coplanar faces still join the profile selection; non-parallel -planes are not targets. The extent can still be chosen manually. For a new extrusion without +planes are not targets. Clicking into the **Profiles** box first makes the next picks profiles +again, even off the plane. The extent can still be chosen manually. For a new extrusion without start or end offsets or a From Object input, the target also sets the stored distance and one-sided direction, so returning to **Distance** keeps that reach. Thin Extrude keeps its -existing picks. With the setting off, picks only add profiles, as before. +existing picks. With the setting off, picks only add profiles. A positive extrude taper slopes the side walls outward, so the profile grows along the extrusion direction and the far end is larger; a negative taper slopes them inward, making diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 63bee380dd..834d85d3d9 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -474,7 +474,7 @@ One setting per line, sorted by name. A change that adds a setting adds its own - `default_units` - `dialog_buttons` (`bottom` (default), `top`: a feature dialog's OK, Cancel and Apply sit in its title row instead of under its inputs, #6708; the buttons keep their handles, and `dialog:close` is then Cancel's; the Sketch panel's Finish Sketch row moves above its options, #9860) - `dialog_place` (`edge` (default) or `left_column`: command dialogs open in the left column in place of the browser and the History, #6707) -- `extrude_auto_to_object` (default false: in the Extrude dialog, with a profile picked, a face, vertex or parallel plane picked off its plane switches the extent to To Object; it applies to Extrude dialogs opened afterwards) +- `extrude_auto_to_object` (default true: in the Extrude dialog, with a profile picked, a face, vertex or parallel plane picked off its plane switches the extent to To Object, unless the Profiles box was clicked into; it applies to Extrude dialogs opened afterwards) - `frame_rate_details` (default false: with the frame rate, a frame-time graph of the frames drawn with the triangles over it, a frame-time histogram and what the GPU drew; clicking the readout switches it) - `graphics_adapter` (exact card name, or empty for Automatic; applies after restart; `WGPU_ADAPTER_NAME` takes precedence) - `hidden_toolbar_tabs` (default `[]`: all existing tabs, including Sheet Metal and Weldments, stay visible; lists tabs to hide, each once, e.g. `["SURFACE", "SHEET METAL"]`; hidden tabs stay hidden regardless of the design's features; [ribbon layout](ribbon-layout.md)) diff --git a/docs/design/preferences/pages/tools.md b/docs/design/preferences/pages/tools.md index 2dbaefd856..045b6f48e6 100644 --- a/docs/design/preferences/pages/tools.md +++ b/docs/design/preferences/pages/tools.md @@ -12,7 +12,7 @@ Each group is a 120x64 figure at the left of its checkboxes, with a group name u |---|---|---|---|---|---|---| | Hole: Snap to centres | Checkbox | `Prefs.hole_snaps` | `false` | "The middle of a face and the centres of its round edges." | `tool-hole` | Today's label "Snap to face and edge centres". | | Hole: Move by click or drag | Checkbox | `Prefs.hole_moves` | `false` | "Picking the face again moves the hole instead of removing it." | `tool-hole` | Today's label "Move holes by clicking or dragging". | -| Extrude: Extrude to a picked target | Checkbox | `Prefs.extrude_auto_to_object` | `false` | "A face, vertex or parallel plane switches the extent to To Object." | `tool-extrude` | | +| Extrude: Extrude to a picked target | Checkbox | `Prefs.extrude_auto_to_object` | `true` | "A face, vertex or parallel plane switches the extent to To Object." | `tool-extrude` | | | Extrude: Choose the operation automatically | Checkbox | `Prefs.auto_operation` | `true` | "Join, New Body or Cut, from what the material touches." | `tool-extrude` | | | Rest, Boss, Snap Fit: Placement handles | Checkbox | `Prefs.placement_handles` | `false` | "A marker to drag the feature across the model and a ring to turn it." | `tool-placement` | |