Press Pull on a fillet's or chamfer's face edits that feature's size by the distance (found through the face's persistent name); on another curved face it offsets the face, with the rest of its surface (both halves of a hole's wall)
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
59b59a83e6
commit
06186be555
5 files changed
+106
No files matched your search
@@ -32,6 +32,25 @@ pub(super) fn face_normal(s: &Session, p: Vec3) -> Option<Vec3> {
|
||||
.find_map(|[a, b, c]| (b - a).cross(c - a).normalized())
|
||||
}
|
||||
|
||||
/// Is the body face at `p` planar (`None`: no face there)?
|
||||
pub(super) fn face_is_planar(s: &Session, p: Vec3) -> Option<bool> {
|
||||
let (m, f, d) = face_at(s, p)?;
|
||||
let tol = (m.bounds().diagonal() * 1e-3).max(1e-3);
|
||||
if d > tol * 10.0 {
|
||||
return None;
|
||||
}
|
||||
let ns: Vec<Vec3> = m
|
||||
.triangles
|
||||
.iter()
|
||||
.zip(&m.tri_face)
|
||||
.filter(|(_, tf)| **tf == f)
|
||||
.filter_map(|(t, _)| m.tri(t))
|
||||
.filter_map(|[a, b, c]| (b - a).cross(c - a).normalized())
|
||||
.collect();
|
||||
let n0 = *ns.first()?;
|
||||
Some(ns.iter().all(|n| n.dot(n0) > 1.0 - 1e-6))
|
||||
}
|
||||
|
||||
/// The body face under a point: (mesh, face index, distance).
|
||||
fn face_at(s: &Session, p: Vec3) -> Option<(std::sync::Arc<Mesh>, u32, f64)> {
|
||||
let st = s.model.state();
|
||||
|
||||
@@ -458,6 +458,28 @@ fn press_pull(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if d.abs() < 1e-9 {
|
||||
return Err(bad(cmd, "the distance must not be zero"));
|
||||
}
|
||||
// A fillet's or chamfer's face: its size changes by the distance (the feature is edited).
|
||||
if let Some(fp) = vec3(face)
|
||||
&& super::face::face_is_planar(s, fp) == Some(false)
|
||||
{
|
||||
let name = solvecraft_doc::naming::face_names_at(&s.model.state(), &[fp]).into_iter().next().unwrap_or_default();
|
||||
let fid = name.strip_prefix('F').and_then(|r| r.split(':').next()).and_then(|x| x.parse::<u64>().ok());
|
||||
if name.contains(":blend") {
|
||||
let f = fid.and_then(|id| s.doc.feature(id)).cloned().ok_or_else(|| bad(cmd, "the blend's feature is gone"))?;
|
||||
let (key, old) = match &f.kind {
|
||||
FeatureKind::Fillet { radius, .. } => ("radius", radius.clone()),
|
||||
FeatureKind::Chamfer { distance, .. } => ("distance", distance.clone()),
|
||||
_ => return Err(bad(cmd, "that face is not a fillet's or a chamfer's")),
|
||||
};
|
||||
let new = format!("({old}) + ({distance})");
|
||||
let r = super::find_command("timeline.edit").ok_or_else(|| bad(cmd, "timeline.edit"))?;
|
||||
let out = (r.run)(s, &json!({"feature": f.id, "set": {key: new}}))?;
|
||||
return Ok(json!({"feature": f.id, "edited": f.name, key: new, "recomputed": out.get("recomputed")}));
|
||||
}
|
||||
// Another curved face moves along its normal.
|
||||
let r = super::find_command("FusionOffsetFacesCommand").ok_or_else(|| bad(cmd, "FusionOffsetFacesCommand"))?;
|
||||
return (r.run)(s, &json!({"faces": [face], "distance": distance}));
|
||||
}
|
||||
let (dist, dir, op) = if d > 0.0 { (distance, "positive", "join") } else { (format!("-({distance})"), "negative", "cut") };
|
||||
extrude(s, &json!({"face": face, "distance": dist, "direction": dir, "operation": op}))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user