Fillet corners: a radius per edge (kernel::fillet_radii), rounds next to earlier rounds of another radius, and smooth corner patches where three rounds meet
blend_corner.rs rebuilds the planar faces at the ends of the filleted edges (three planar faces and three straight edges at each such vertex) and keeps the rest. One round at a vertex ends in the third face's exact section of its cylinder; two rounds with the third edge sharp trim each other (rolling ball: the crease from where their contact lines cross to the third edge for equal radii, else to the smaller round's contact line on the face that ends the larger one, which gets the exact section between); three rounds meet a sphere for equal radii, else a rational degree-4 patch through plane sections of the cylinders, the sections turned so that the cylinders' curvatures agree where two meet on a face and the patch fitted tangent to all three (within 0.01 degrees) and to the face at its closed corner. kernel::fillet falls back to it when the local blends refuse: an edge that ends on an earlier round (any radius) takes the round off (delete_faces) and blends both edges together, so r3 next to r2 and a third edge into a mitred pair work. delete_faces: a gap stretch along several deleted faces keeps the corners between them (two rounds meeting in a mitre come off back to the sharp corner). splitface::fit is shared for the crease. Tests: volumes against the strips-minus-overlap reference (exact strips, overlap integrated; checked against an independent 1D integral), the sphere-octant formula for three equal rounds, tangency along the patch, closed meshes, refusals without panics. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7820ee405b
commit
740360d7c3
5 files changed
+1124
-10
No files matched your search
@@ -658,7 +658,11 @@ fn loop_split(cur: &Body, pts: &[Vec3]) -> Result<Option<(Vec<Vec<Vec3>>, Vec<Ve
|
||||
/// Constant-radius fillet of the edges nearest to the given points.
|
||||
pub fn fillet(body: &Body, edges: &[Vec3], radius: f64) -> Result<Body> {
|
||||
body.require_brep("fillet")?;
|
||||
blend(body, edges, radius, Shape::Round, "fillet")
|
||||
// Corners the local blends refuse (an earlier round at an edge's end): radius per edge.
|
||||
blend(body, edges, radius, Shape::Round, "fillet").or_else(|e| {
|
||||
let sel: Vec<(Vec3, f64)> = edges.iter().map(|p| (*p, radius)).collect();
|
||||
crate::blend_corner::fillet_radii(body, &sel).map_err(|_| e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Equal-distance chamfer of the edges nearest to the given points. Each edge is cut with a
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -219,18 +219,37 @@ pub fn delete_faces(body: &Body, faces: &[usize]) -> Result<Body> {
|
||||
let m = edges.len();
|
||||
let start = (0..m).find(|i| edges.get((i + m - 1) % m).is_some_and(gap) && edges.get(*i).is_some_and(|e| !gap(e))).unwrap_or(0);
|
||||
let mut new_edges: Vec<mt::Edge> = Vec::new();
|
||||
let mut pending: Option<Vec3> = None;
|
||||
// The moved corners along a gap stretch (two rounds meeting in a mitre leave
|
||||
// the sharp corner between them).
|
||||
let mut pending: Option<Vec<Vec3>> = None;
|
||||
let close = |pts: Vec<Vec3>, a: Vec3| -> Vec<(Vec3, Vec3)> {
|
||||
let mut c: Vec<Vec3> = Vec::new();
|
||||
for p in pts.into_iter().chain(std::iter::once(a)) {
|
||||
if c.last().is_some_and(|l| l.dist(p) <= q * 10.0) {
|
||||
continue;
|
||||
}
|
||||
// Drop a corner in line with its neighbours.
|
||||
if let [.., x, y] = c[..]
|
||||
&& (y - x).cross(p - y).len() <= q * 10.0 * (p - x).len()
|
||||
&& (y - x).dot(p - y) > 0.0
|
||||
{
|
||||
c.pop();
|
||||
}
|
||||
c.push(p);
|
||||
}
|
||||
c.windows(2).filter_map(|w| Some((*w.first()?, *w.get(1)?))).collect()
|
||||
};
|
||||
for k in 0..m {
|
||||
let Some(e) = edges.get((start + k) % m) else { continue };
|
||||
if gap(e) {
|
||||
pending.get_or_insert(pos(e.front()));
|
||||
pending.get_or_insert_with(Vec::new).extend([pos(e.front()), pos(e.back())]);
|
||||
continue;
|
||||
}
|
||||
let (a, b) = (pos(e.front()), pos(e.back()));
|
||||
if let Some(p) = pending.take()
|
||||
&& p.dist(a) > q * 10.0
|
||||
{
|
||||
new_edges.push(line(p, a, &mut verts));
|
||||
if let Some(p) = pending.take() {
|
||||
for (x, y) in close(p, a) {
|
||||
new_edges.push(line(x, y, &mut verts));
|
||||
}
|
||||
}
|
||||
let moves = moved.contains_key(&e.front().id()) || moved.contains_key(&e.back().id());
|
||||
if !moves {
|
||||
@@ -246,8 +265,8 @@ pub fn delete_faces(body: &Body, faces: &[usize]) -> Result<Body> {
|
||||
// A gap stretch at the end closes back to the first edge.
|
||||
if let (Some(p), Some(first)) = (pending, new_edges.first()) {
|
||||
let a = from_p3(first.front().point());
|
||||
if p.dist(a) > q * 10.0 {
|
||||
new_edges.push(line(p, a, &mut verts));
|
||||
for (x, y) in close(p, a) {
|
||||
new_edges.push(line(x, y, &mut verts));
|
||||
}
|
||||
}
|
||||
if new_edges.is_empty() {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod blend;
|
||||
mod blend_corner;
|
||||
mod blend_variable;
|
||||
mod body;
|
||||
mod build;
|
||||
@@ -43,6 +44,7 @@ mod sweep_path;
|
||||
mod topo;
|
||||
|
||||
pub use blend::{ChamferSide, chamfer, chamfer_sides, fillet};
|
||||
pub use blend_corner::fillet_radii;
|
||||
pub use blend_variable::{fillet_chord, fillet_variable};
|
||||
pub use body::{Body, EdgeInfo, FaceInfo, FacePaint, Paint};
|
||||
pub use build::{PathSeg, box_solid, cylinder, extrude, extrude_tapered, loft, revolve, sphere, sweep, torus};
|
||||
|
||||
@@ -325,7 +325,7 @@ fn boundary_hits(face: &mt::Face, g: &Field) -> Vec<Hit> {
|
||||
|
||||
/// A curve through points: a line when they are collinear, else a cubic through every point
|
||||
/// (Catmull-Rom tangents; `closed` when the last point repeats the first).
|
||||
fn fit(pts: &[mt::Point3], scale: f64) -> Result<mt::Curve> {
|
||||
pub(crate) fn fit(pts: &[mt::Point3], scale: f64) -> Result<mt::Curve> {
|
||||
let (Some(a), Some(b)) = (pts.first(), pts.last()) else { return Err(KernelError::Failed("empty split line".into())) };
|
||||
let d = b - a;
|
||||
let len = d.magnitude();
|
||||
|
||||
Reference in new issue
Block a user