Smart Extrude: direction from the picked side, and a Cut stays a Cut (#1257) (#1261)

* 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:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-14 21:31:10 -04:00
1 parent d73cd8a177
commit ed22d663e4
8 files changed
+363 -11

No files matched your search

+12 -2
View File
@@ -137,9 +137,16 @@ fn extrude_base(s: &Session, p: &Value) -> Option<(Vec3, Vec3)> {
Some((ss.plane.to_world(q), ss.plane.normal()))
}
/// How far past the profile [`auto_operation`] looks for material it starts in.
const START_PROBE_MM: f64 = 0.1;
/// What `operation: "auto"` means for an extrude with these parameters: `cut` when it goes
/// into a body, otherwise `join` when the active component has a solid. Keeping an
/// addition separate requires an explicit `new` operation.
///
/// "Goes into a body" is decided where the extrude starts: just past the profile in the
/// direction it grows, or halfway along it. With the halfway point alone, a cut dragged far
/// past a body's far side read as a Join (#1257).
pub fn auto_operation(s: &Session, p: &Value) -> Option<&'static str> {
let d = s.doc.eval(&expr(p, "distance")?, Kind::Length).ok()?;
let dir = str_(p, "direction").map(str::to_ascii_lowercase);
@@ -161,10 +168,13 @@ pub fn auto_operation(s: &Session, p: &Value) -> Option<&'static str> {
return Some("new");
}
let inside = |q: Vec3| bodies.iter().any(|b| b.mesh().contains(q));
// A step into whatever is right in front of the profile, short of the far end.
let near = |d: f64| d.signum() * (d.abs() * 0.5).min(START_PROBE_MM);
let into = |d: f64| inside(base + n * near(d)) || inside(base + n * (d * 0.5));
if symmetric {
return Some(if inside(base + n * (d.abs() * 0.5)) || inside(base - n * (d.abs() * 0.5)) { "cut" } else { "join" });
return Some(if into(d.abs()) || into(-d.abs()) { "cut" } else { "join" });
}
if inside(base + n * (d * 0.5)) {
if into(d) {
Some("cut")
} else {
// A profile can meet material along its side, or only around part of a