Variable-radius and chord-length fillets

New kernel file blend_variable.rs. The hooks into blend.rs are visibility
only: plane_normal, vtx, rebuild_face and unsupported are now pub(crate).
- fillet_variable(body, edge, start, r0, r1) works on the same edge
  configuration as the constant fillet (straight edge, planar faces, end
  faces square to it). The radius runs linearly from the end nearest
  `start`, and every section square to the edge is a circular arc. The
  blend is an exact degree (2, 1) rational B-spline: arc control points
  move linearly.
- fillet_chord(body, edges, chord): the radius per edge comes from its
  face angle (chord = 2 r cos(phi/2)).

Fillet feature: style constant | chord | variable {radius2, start};
FusionFilletEdgesCommand takes type, radius2 and start.

Tests:
- Variable 2 -> 5 on a box edge removes (1 - pi/4) L (r0^2 + r0 r1 + r1^2)/3.
  Starting from the other end gives the same solid; equal radii give the
  constant fillet; too large is refused.
- Chord 4 on a right angle equals radius 4/sqrt(2).
- Engine: both types by command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 21:30:58 +00:00
1 parent c9ed3d154d
commit c0e3e3d216
12 files changed
+373 -14

No files matched your search

+10 -1
View File
@@ -2101,7 +2101,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
}
Ok(())
}
FeatureKind::Fillet { edges, radius, body } | FeatureKind::Chamfer { edges, distance: radius, body, .. } => {
FeatureKind::Fillet { edges, radius, body, .. } | FeatureKind::Chamfer { edges, distance: radius, body, .. } => {
let r = val(vals, radius, Kind::Length)?;
let ti = blend_target(st, body, edges)?;
let Some(mb) = st.bodies.get(ti) else { return Err(DocError::Invalid("body".into())) };
@@ -2144,6 +2144,15 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
resolve_edges(&mb.body, edges, &f.edge_refs, warning)?
};
let nb = match &f.kind {
FeatureKind::Fillet { style: crate::FilletStyle::Chord, .. } => kernel::fillet_chord(&mb.body, &edges, r)?,
FeatureKind::Fillet { style: crate::FilletStyle::Variable { radius2, start }, .. } => {
let r2 = val(vals, radius2, Kind::Length)?;
let mut b = mb.body.clone();
for p in &edges {
b = kernel::fillet_variable(&b, *p, *start, r, r2)?;
}
b
}
FeatureKind::Fillet { .. } => kernel::fillet(&mb.body, &edges, r)?,
FeatureKind::Chamfer { distance2, angle, flip, .. } => chamfer_with(vals, &mb.body, &edges, r, distance2, angle, *flip)?,
_ => return Err(DocError::Invalid("blend".into())),