Drag manipulators and automatic operation: extrude (two arrows when symmetric), fillet/chamfer, shell and move arrows drag their value with round-step snapping (Alt/Ctrl: free) and the dialog follows; Extrude operation "auto" (engine) and the dialog follow the geometry: into a body cuts (red), out of one joins, free space makes a new body; a manual choice sticks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
f5c2c725c8
commit
b2b9331415
7 files changed
+250
-25
No files matched your search
@@ -13,7 +13,7 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
.at("SOLID", "CREATE")
|
||||
.icon("extrude")
|
||||
.key("E")
|
||||
.params("distance: expr (or through_all: true); taper?: angle expr; sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [[curve ids]] | [{point:[x,y]}]; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); direction?: positive|negative|symmetric; distance2?; start_offset?; operation?: new|join|cut|intersect; targets?: [body]; name?; body_name?"),
|
||||
.params("distance: expr (or through_all: true); taper?: angle expr; sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [[curve ids]] | [{point:[x,y]}]; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); direction?: positive|negative|symmetric; distance2?; start_offset?; operation?: new|join|cut|intersect|auto (cut into a body, join out of one, else new); targets?: [body]; name?; body_name?"),
|
||||
CommandSpec::new("Revolve", "Revolve", revolve)
|
||||
.at("SOLID", "CREATE")
|
||||
.icon("revolve")
|
||||
@@ -73,6 +73,63 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
CommandSpec::new("FusionMoveCommand", "Move/Copy", move_bodies).at("SOLID", "MODIFY").icon("move").key("M").params("bodies: [names]; translate?: [x,y,z] (exprs or numbers); axis?: [x,y,z]; angle?: expr"),
|
||||
];
|
||||
|
||||
/// Where an extrude starts and which way is positive: a point inside the (first) profile or
|
||||
/// face, and the unit normal.
|
||||
fn extrude_base(s: &Session, p: &Value) -> Option<(Vec3, Vec3)> {
|
||||
if let Some(fp) = p.get("face").and_then(vec3) {
|
||||
return Some((fp, super::face::face_normal(s, fp)?));
|
||||
}
|
||||
let sid = feature_sketch(s, p, "Extrude").ok()?;
|
||||
let st = s.model.state();
|
||||
let ss = st.sketch(sid)?;
|
||||
let ps = &ss.profiles;
|
||||
let q = match profiles(p, "Extrude").ok()? {
|
||||
ProfileSel::All => ps.first()?.region.interior_point(),
|
||||
ProfileSel::Indices { indices } => ps.get(*indices.first()?)?.region.interior_point(),
|
||||
ProfileSel::Points { points } => *points.first()?,
|
||||
ProfileSel::Curves { loops } => {
|
||||
let mut want: Vec<&String> = loops.first()?.iter().collect();
|
||||
want.sort();
|
||||
ps.iter()
|
||||
.find(|x| {
|
||||
let mut have: Vec<&String> = x.outer_curves.iter().collect();
|
||||
have.sort();
|
||||
have == want
|
||||
})?
|
||||
.region
|
||||
.interior_point()
|
||||
}
|
||||
};
|
||||
Some((ss.plane.to_world(q), ss.plane.normal()))
|
||||
}
|
||||
|
||||
/// What `operation: "auto"` means for an extrude with these parameters: `cut` when it goes
|
||||
/// into a body, `join` when it grows out of one, `new` otherwise.
|
||||
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);
|
||||
let symmetric = dir.as_deref() == Some("symmetric");
|
||||
let d = if matches!(dir.as_deref(), Some("negative" | "reverse" | "flip")) { -d } else { d };
|
||||
let (base, n) = extrude_base(s, p)?;
|
||||
let st = s.model.state();
|
||||
if st.bodies.is_empty() || !d.is_finite() {
|
||||
return Some("new");
|
||||
}
|
||||
let inside = |q: Vec3| st.bodies.iter().any(|b| b.mesh().contains(q));
|
||||
let eps = (d.abs() * 1e-3).max(1e-3);
|
||||
let sign = if d < 0.0 { -1.0 } else { 1.0 };
|
||||
if symmetric {
|
||||
return Some(if inside(base + n * (d.abs() * 0.5)) || inside(base - n * (d.abs() * 0.5)) { "cut" } else { "new" });
|
||||
}
|
||||
if inside(base + n * (d * 0.5)) {
|
||||
Some("cut")
|
||||
} else if inside(base - n * (eps * sign)) {
|
||||
Some("join")
|
||||
} else {
|
||||
Some("new")
|
||||
}
|
||||
}
|
||||
|
||||
fn operation(p: &Value, cmd: &str) -> Result<Operation> {
|
||||
match str_(p, "operation") {
|
||||
None => Ok(Operation::NewBody),
|
||||
@@ -219,7 +276,10 @@ fn extrude(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
None => profiles(p, cmd)?,
|
||||
},
|
||||
extent: Extent { distance, direction, distance2, start_offset, through_all, taper },
|
||||
operation: operation(p, cmd)?,
|
||||
operation: match str_(p, "operation") {
|
||||
Some(o) if o.eq_ignore_ascii_case("auto") => Operation::parse(auto_operation(s, p).unwrap_or("new")).unwrap_or(Operation::NewBody),
|
||||
_ => operation(p, cmd)?,
|
||||
},
|
||||
targets: string_list(p, "targets"),
|
||||
};
|
||||
add_feature(s, p, kind)
|
||||
|
||||
Reference in new issue
Block a user