* Smart Extrude: direction from the picked side, Cut into a body stays a Cut (#1257) - A sketch profile (or a thin extrude's curve) extrudes toward the side it was picked from: from the bottom view it goes down. Body faces still go outward. - The automatic operation is latched per side of the profile: a drag that starts into a body is a Cut and stays one past the far side; a Join does not become a Cut when it reaches another body. Passing through zero decides afresh for the new side. - An explicit operation, Direction or Reverse ends the automatic choices. - auto_operation also looks just past the profile, so a long cut through a body reads as a Cut from the start (the engine's operation: "auto" too). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Scenario s07: the XZ profile seen from behind now extrudes toward the camera Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d73cd8a177
commit
ed22d663e4
8 files changed
+363
-11
No files matched your search
@@ -42,6 +42,8 @@ pub enum Kind {
|
||||
/// Into a body: cut; additions: join when a solid exists, otherwise new.
|
||||
/// until the user picks one.
|
||||
auto_op: bool,
|
||||
/// The direction from the picked side and the latched operation (#1257).
|
||||
smart: crate::smart_extrude::Smart,
|
||||
/// The other side's distance (two sides).
|
||||
distance2: String,
|
||||
/// Taper angle (empty or zero: none).
|
||||
@@ -439,6 +441,7 @@ impl Dialog {
|
||||
direction: 0,
|
||||
operation: usize::from(has_bodies),
|
||||
auto_op: true,
|
||||
smart: Default::default(),
|
||||
distance2: "10 mm".into(),
|
||||
taper: "0 deg".into(),
|
||||
taper_present: false,
|
||||
@@ -1797,23 +1800,56 @@ fn measure_rows(app: &SolveApp, ui: &mut egui::Ui, r: Option<&Value>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep an automatic extrude operation in step with the geometry.
|
||||
/// Smart Extrude (#1257, see [`crate::smart_extrude`]): a new pick sets the direction from the
|
||||
/// side it was picked from, and the automatic operation follows the geometry, latched per side.
|
||||
fn auto_operation(app: &SolveApp, d: &mut Dialog) {
|
||||
let Kind::Extrude { smart, .. } = &d.kind else { return };
|
||||
let picked = d.inputs.first().map(|i| i.items.as_slice()).unwrap_or_default();
|
||||
if smart.picked != picked {
|
||||
let new_first = smart.picked.first() != picked.first();
|
||||
let picked = picked.to_vec();
|
||||
let side_of_pick = if new_first && !smart.manual_dir { picked.first().and_then(|first| picked_side(app, d, first)) } else { None };
|
||||
let Kind::Extrude { direction, smart, .. } = &mut d.kind else { return };
|
||||
smart.picked = picked;
|
||||
smart.latch = None;
|
||||
if *direction < 2
|
||||
&& let Some(dir) = side_of_pick
|
||||
{
|
||||
*direction = dir;
|
||||
}
|
||||
}
|
||||
let Kind::Extrude { direction, distance, .. } = &d.kind else { return };
|
||||
let Ok(dist) = app.session.doc.eval(distance, solidcraft_engine::doc::expr::Kind::Length) else { return };
|
||||
let side = crate::smart_extrude::side(dist, *direction);
|
||||
if !matches!(d.kind, Kind::Extrude { auto_op: true, .. }) {
|
||||
return;
|
||||
}
|
||||
let Some(op) =
|
||||
dialog_commands(app, d).ok().and_then(|c| c.into_iter().next()).and_then(|(_, p)| solidcraft_engine::auto_operation(&app.session, &p))
|
||||
let Some(fresh) = dialog_commands(app, d)
|
||||
.ok()
|
||||
.and_then(|c| c.into_iter().next())
|
||||
.and_then(|(_, p)| solidcraft_engine::auto_operation(&app.session, &p))
|
||||
.and_then(|op| OPS.iter().position(|o| *o == op))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Kind::Extrude { operation, .. } = &mut d.kind
|
||||
&& let Some(i) = OPS.iter().position(|o| *o == op)
|
||||
{
|
||||
*operation = i;
|
||||
if let Kind::Extrude { operation, smart, .. } = &mut d.kind {
|
||||
*operation = crate::smart_extrude::operation(fresh, *operation, side, &mut smart.latch);
|
||||
}
|
||||
}
|
||||
|
||||
/// The one-side direction a new extrude takes for its first pick: toward the side of a sketch
|
||||
/// profile (or curve) the camera looks from; None for picks that keep their own way.
|
||||
fn picked_side(app: &SolveApp, d: &Dialog, first: &Sel) -> Option<usize> {
|
||||
let normal = match first {
|
||||
Sel::Profile { sketch, .. } => crate::canvas::profile_plane(app, *sketch)?.normal(),
|
||||
Sel::SketchCurve { id } => app.session.world_state().sketches.iter().rev().find(|ss| ss.sketch.curve_index(id).is_some())?.plane.normal(),
|
||||
Sel::Face { .. } => return Some(0),
|
||||
_ => return None,
|
||||
};
|
||||
let (at, _) = crate::canvas::anchor(app, d)?;
|
||||
Some(crate::smart_extrude::picked_direction(crate::smart_extrude::view_ray(&app.cam, at), normal, false))
|
||||
}
|
||||
|
||||
/// Show the active dialog (if any), docked flush to an edge of the viewport just below the axis
|
||||
/// view, sized to its content. The right edge is its place, except while a sketch is open: the
|
||||
/// Sketch Palette keeps the right there, and the dialog takes the left so both stay on screen.
|
||||
@@ -1995,6 +2031,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
direction,
|
||||
operation,
|
||||
auto_op,
|
||||
smart,
|
||||
distance2,
|
||||
taper,
|
||||
taper_present,
|
||||
@@ -2050,6 +2087,9 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
combo(ui, "ex_dir", &["One Side", "Two Sides", "Symmetric"], &mut pos);
|
||||
if pos != order.iter().position(|o| o == direction).unwrap_or(0) {
|
||||
*direction = order.get(pos).copied().unwrap_or(0);
|
||||
// An explicit direction ends the automatic choices (#1257).
|
||||
smart.manual_dir = true;
|
||||
*auto_op = false;
|
||||
}
|
||||
ui.end_row();
|
||||
if *direction < 2 {
|
||||
@@ -2057,6 +2097,9 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let mut reverse = *direction == 1;
|
||||
if ui.checkbox(&mut reverse, tr("Reverse Direction")).changed() {
|
||||
*direction = usize::from(reverse);
|
||||
// An explicit direction ends the automatic choices (#1257).
|
||||
smart.manual_dir = true;
|
||||
*auto_op = false;
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
@@ -4139,6 +4182,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
},
|
||||
operation: op_index(operation),
|
||||
auto_op: false,
|
||||
smart: crate::smart_extrude::Smart { manual_dir: true, ..Default::default() },
|
||||
distance2: extent.distance2.clone().unwrap_or_else(|| "10 mm".into()),
|
||||
taper: extent.taper.clone().unwrap_or_else(|| "0 deg".into()),
|
||||
taper_present: extent.taper.is_some(),
|
||||
|
||||
Reference in new issue
Block a user