SMLib import: an extruded circle past a turn stays its exact spline

A round of a bar's top edge, where the bar's side runs tangent into a cylinder standing on its
round end, seemed to build: SMLib's result holds the cylinder's wall as a rational spline whose
parameter range runs 393° round (u in [-15, 40.18]). Going back into SMLib (to measure the body
or change it again) the record's extruded circles become SMLib cylinders with the face's range on
them, and that range was 6.866 rad: SMLib refuses a cylinder past one turn
(SM_ERR_INVALID_INPUT from BrepFromFlat's face range check). The body could not be measured, a
second round on it failed, and the app said one of its freeform faces can't be used. An extruded
circle whose range runs more than one turn round now stays the exact spline it is.

The bar of the reported design (40 x 16 x 10 with a R8 end, a disc R8 sketched on its top and
extruded 10 up, joined): the front top round removes the spandrel up to the cylinder's wall,
integral over y in [0, 2] of (2 - sqrt(4 - (2 - y)^2)) (40 - sqrt(64 - (8 - y)^2)) = 32.306475;
the three rounds at the other top corner after it remove 53.285832 in all (spandrels outside the
corner cube, the cube less the rolling ball's eighth). Both now build and measure to 1e-7; the
curvature corner of the three edges at once (setback 3) builds too.

Rig cases (fillets) for the three reports: the curvature corner of the three edges, the front
top round, and the three corner rounds after it pass; the same corner rounds with the design's
~1e-10 mm sketch residues fail in SMLib (its corner's surface of revolution is close to singular
at its boundary) and are listed as known failing. (Their ids take the report- convention in the
rig commit later on this branch.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-30 05:50:04 -04:00
committed by Irakli Kourtis
parent 739e47b9df
commit 977949819e
11 changed files with 865 additions and 1 deletions
+66
View File
@@ -427,6 +427,72 @@ fn fillet_the_edges_between_two_overlapping_cylinders() {
assert!(rel(v, expect) < 2e-4, "{v} vs {expect}");
}
/// A bar 40 long, 16 wide and 10 tall whose far end is a half disc of radius 8 about (40, 8),
/// with a disc of radius 8 about the same centre sketched on its top (the top's outline
/// projected) and extruded 10 up, joined: the bar's sides run tangent into the cylinder.
/// V0 = 40·16·10 + π·8²·10/2 + π·8²·10.
fn peg(s: &mut Session) -> f64 {
run(s, "sketch.create", json!({"plane": "XY", "name": "Peg"}));
run(s, "sketch.line", json!({"points": [[0, 0], [40, 0]]}));
run(s, "sketch.arc.center_point", json!({"center": [40, 8], "start": [40, 0], "end": [40, 16]}));
run(s, "sketch.line", json!({"points": [[40, 16], [0, 16]]}));
run(s, "sketch.line", json!({"points": [[0, 16], [0, 0]]}));
run(s, "sketch.finish", json!({}));
run(s, "solid.extrude", json!({"sketch": "Peg", "distance": 10, "body_name": "Peg"}));
run(s, "sketch.create", json!({"plane": {"origin": [0, 0, 10], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}, "name": "Top"}));
run(s, "sketch.project", json!({"refs": [{"face": [2, 8, 10]}]}));
run(s, "sketch.circle.center", json!({"center": [40, 8], "radius": 8}));
run(s, "sketch.finish", json!({}));
run(
s,
"solid.extrude",
json!({"sketch": "Top", "distance": 10, "operation": "join", "targets": ["Peg"], "profiles": [{"point": [40, 8]}], "exact_regions": true}),
);
6400.0 + 960.0 * PI
}
/// Simpson's rule over [a, b] with `n` (even) intervals.
fn simpson(f: impl Fn(f64) -> f64, a: f64, b: f64, n: usize) -> f64 {
let h = (b - a) / n as f64;
let inner: f64 = (1..n).map(|i| if i % 2 == 1 { 4.0 } else { 2.0 } * f(a + h * i as f64)).sum();
(f(a) + f(b) + inner) * h / 3.0
}
/// The material an r2 round of the peg's front top edge (y = 0, z = 10) takes off: the spandrel
/// (height 2 - sqrt(4 - (2 - y)²) above the round at y) from x = `from` to where the cylinder's
/// wall, x = 40 - sqrt(64 - (8 - y)²), takes over (the round runs into the wall there).
fn peg_front_round(from: f64) -> f64 {
let spandrel = |y: f64| 2.0 - (4.0 - (2.0 - y) * (2.0 - y)).max(0.0).sqrt();
let wall = |y: f64| 40.0 - (64.0 - (8.0 - y) * (8.0 - y)).max(0.0).sqrt();
simpson(|y| spandrel(y) * (wall(y) - from), 0.0, 2.0, 20_000)
}
/// The peg's front top edge rounded r2: SMLib's result holds the cylinder's wall as a rational
/// spline whose parameter range runs more than a turn round. As a cylinder over that range (past
/// 360°) SMLib refused it on the way back (SM_ERR_INVALID_INPUT), so the round seemed to build
/// and the body could not be measured or changed. It stays the spline: the body measures, and a
/// round of the three edges at the bar's other top corner builds on it.
#[test]
fn rounds_on_a_bar_running_tangent_into_a_cylinder() {
let mut s = Session::default();
let v0 = peg(&mut s);
run(&mut s, "solid.fillet", json!({"edges": [[20, 0, 10]], "radius": 2}));
let (v, expect) = (volume(&mut s), v0 - peg_front_round(0.0));
assert!(rel(v, expect) < 1e-7, "{v} vs {expect}");
// The vertical edge, the left top edge and the round's end arc on the left face: each
// spandrel (1 - π/4)·r² along the edge outside the corner cube of side 2, the cube less the
// rolling ball's eighth, 8 - 4π/3.
run(&mut s, "solid.fillet", json!({"edges": [[0, 0, 4], [0, 9, 10], [0, 0.5858, 9.4142]], "radius": 2}));
let strip = (1.0 - PI / 4.0) * 4.0;
let expect = v0 - peg_front_round(2.0) - strip * (8.0 + 14.0) - (8.0 - 4.0 * PI / 3.0);
let v = volume(&mut s);
assert!(rel(v, expect) < 1e-7, "{v} vs {expect}");
for b in &s.model.state().bodies {
let bad = b.body.validity();
assert!(bad.is_empty(), "{}: {bad:?}", b.name);
}
}
/// The top outline of two overlapping cylinders whose concave edges were rounded: the walls of
/// the rounds meet the cylinders along the rolling-ball contact lines, straight but not lines.
#[test]
+7 -1
View File
@@ -76,7 +76,7 @@ pub(super) fn analytic(brep: &b::Brep) -> R<Cow<'_, b::Brep>> {
/// A rational spline that is a circular arc swept straight along the arc's axis, as a cylinder
/// with the face's range on it: one direction linear (two rows of control points), the second
/// row the first moved by one vector at right angles to the circle the first row runs on, the
/// weights alike. `None` where it is not.
/// weights alike. `None` where it is not, or where its range runs more than one turn round.
fn extruded_circle(u_knots: &[f64], v_knots: &[f64], net: &[Vec<[f64; 4]>], domain: [[f64; 2]; 2], tol: f64) -> Option<(b::Surface, [[f64; 2]; 2])> {
let linear = |k: &[f64]| k.len() == 4 && k[0] == k[1] && k[2] == k[3] && k[1] < k[2];
let (rows, arc_knots, arc_range, line_knots, line_range): ([Vec<[f64; 4]>; 2], &[f64], [f64; 2], &[f64], [f64; 2]) =
@@ -117,6 +117,12 @@ fn extruded_circle(u_knots: &[f64], v_knots: &[f64], net: &[Vec<[f64; 4]>], doma
prev = a;
angle = a;
}
// A cylinder's range is at most one turn (SMLib refuses more). A spline whose range runs
// further round (SMLib's own, its parameter range past the face's trims) has no such range:
// it stays the exact spline it is.
if angle.abs() > TAU * (1.0 + 1e-9) {
return None;
}
let height = |s: f64| (s - line_knots[1]) / (line_knots[2] - line_knots[1]) * d.len();
let sorted = |a: f64, b: f64| if a <= b { [a, b] } else { [b, a] };
let frame = b::Frame { origin: arr(center), x_axis: arr(x), y_axis: arr(y) };
@@ -0,0 +1,11 @@
{
"sources": [
"tangency-labs/solidcraft#1207"
],
"what": "an r2 round of a bar's top edge whose side runs tangent into a cylinder: the round seemed to build but its body could not be measured or changed (SMLib refused the cylinder's wall coming back as a cylinder over more than a turn)",
"source": "analytic",
"method": "a bar 40 x 16 x 10 whose far end is a half disc R8 about (40, 8), a disc R8 about the same centre sketched on its top (the top's outline projected) and extruded 10 up, joined: the bar's sides run tangent into the cylinder, V0 = 6400 + 960 pi; the r2 round of the front top edge (y = 0, z = 10) runs into the cylinder's wall: removed = integral over y in [0, 2] of (2 - sqrt(4 - (2 - y)^2)) (40 - sqrt(64 - (8 - y)^2)) dy = 32.3064821881 (Simpson)",
"body_count": 1,
"faces": 9,
"volume_mm3": 9383.622465258068
}
@@ -0,0 +1,169 @@
{
"commands": [
{
"command": "sketch.create",
"params": {
"plane": "XY",
"name": "Peg"
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0,
0
],
[
40,
0
]
]
}
},
{
"command": "sketch.arc.center_point",
"params": {
"center": [
40,
8
],
"start": [
40,
0
],
"end": [
40,
16
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
40,
16
],
[
0,
16
]
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0,
16
],
[
0,
0
]
]
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Peg",
"distance": 10,
"body_name": "Peg"
}
},
{
"command": "sketch.create",
"params": {
"plane": {
"origin": [
0,
0,
10
],
"x_dir": [
1,
0,
0
],
"y_dir": [
0,
1,
0
]
},
"name": "Top"
}
},
{
"command": "sketch.project",
"params": {
"refs": [
{
"face": [
2,
8,
10
]
}
]
}
},
{
"command": "sketch.circle.center",
"params": {
"center": [
40,
8
],
"radius": 8
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Top",
"distance": 10,
"operation": "join",
"targets": [
"Peg"
],
"profiles": [
{
"point": [
40,
8
]
}
],
"exact_regions": true
}
},
{
"command": "solid.fillet",
"params": {
"edges": [
[
20,
0,
10
]
],
"radius": 2
}
}
]
}
@@ -0,0 +1,11 @@
{
"sources": [
"tangency-labs/solidcraft#1207"
],
"what": "as 1207-fillet-corner-after-round-running-into-cylinder, with the sketch solver's residues of the reported design",
"source": "analytic",
"method": "the same bar with the sketch solver's residues of the reported design (~1e-10 mm: the front side's ends differ by 3.0e-10 in y, the arc's centre 5.7e-11 off the disc's), length 41.160417754, V0 = 41.160417754 * 16.0000000000762 * 10 + 960 pi; the three r2 rounds at the bar's other top corner (0, 0, 10): the front round's spandrel from x = 2 to the wall (30.5896671706), the vertical edge's and the left top edge's (1 - pi/4) r^2 over 8 and 14, the corner cube of side 2 less the rolling ball's eighth, 8 - 4 pi/3: removed 53.2858385868; the bar being L = 41.160417754 long, the front round runs (L - 40) further: removed + (L - 40)(1 - pi/4) r^2",
"body_count": 1,
"faces": 12,
"volume_mm3": 9547.313838420392
}
@@ -0,0 +1,192 @@
{
"commands": [
{
"command": "sketch.create",
"params": {
"plane": "XY",
"name": "Peg"
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0.4892940775280783,
0.05792545052986289
],
[
41.64971183161969,
0.05792545083296555
]
]
}
},
{
"command": "sketch.arc.center_point",
"params": {
"center": [
41.64971183108453,
8.057925450890195
],
"start": [
41.64971183161969,
0.05792545083296555
],
"end": [
41.64971183161969,
16.05792545083297
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
41.64971183161969,
16.05792545083297
],
[
0.4892940775280783,
16.057925450606035
]
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0.4892940775280783,
16.057925450606035
],
[
0.4892940775280783,
0.05792545052986289
]
]
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Peg",
"distance": 10,
"body_name": "Peg"
}
},
{
"command": "sketch.create",
"params": {
"plane": {
"origin": [
0,
0,
10
],
"x_dir": [
1,
0,
0
],
"y_dir": [
0,
1,
0
]
},
"name": "Top"
}
},
{
"command": "sketch.project",
"params": {
"refs": [
{
"face": [
2.4892940775280783,
8.057925450890195,
10
]
}
]
}
},
{
"command": "sketch.circle.center",
"params": {
"center": [
41.64971183108453,
8.057925450832967
],
"radius": 8.000000000006473
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Top",
"distance": 10,
"operation": "join",
"targets": [
"Peg"
],
"profiles": [
{
"point": [
41.64971183108453,
8.057925450832967
]
}
],
"exact_regions": true
}
},
{
"command": "solid.fillet",
"params": {
"edges": [
[
21.53,
0.0579,
10
]
],
"radius": 2
}
},
{
"command": "solid.fillet",
"params": {
"edges": [
[
0.4893,
0.0579,
4
],
[
0.4893,
9.058,
10
],
[
0.4893,
0.6437,
9.4142
]
],
"radius": 2
}
}
]
}
@@ -0,0 +1,11 @@
{
"sources": [
"tangency-labs/solidcraft#1207"
],
"what": "after the round of a bar's top edge running into a cylinder, r2 rounds of the three edges at the bar's other top corner (the vertical edge, the left top edge and the round's end arc): 'one of its freeform faces can't be used'",
"source": "analytic",
"method": "a bar 40 x 16 x 10 whose far end is a half disc R8 about (40, 8), a disc R8 about the same centre sketched on its top (the top's outline projected) and extruded 10 up, joined: the bar's sides run tangent into the cylinder, V0 = 6400 + 960 pi; the three r2 rounds at the bar's other top corner (0, 0, 10): the front round's spandrel from x = 2 to the wall (30.5896671706), the vertical edge's and the left top edge's (1 - pi/4) r^2 over 8 and 14, the corner cube of side 2 less the rolling ball's eighth, 8 - 4 pi/3: removed 53.2858385868",
"body_count": 1,
"faces": 12,
"volume_mm3": 9362.643108859389
}
@@ -0,0 +1,192 @@
{
"commands": [
{
"command": "sketch.create",
"params": {
"plane": "XY",
"name": "Peg"
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0,
0
],
[
40,
0
]
]
}
},
{
"command": "sketch.arc.center_point",
"params": {
"center": [
40,
8
],
"start": [
40,
0
],
"end": [
40,
16
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
40,
16
],
[
0,
16
]
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0,
16
],
[
0,
0
]
]
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Peg",
"distance": 10,
"body_name": "Peg"
}
},
{
"command": "sketch.create",
"params": {
"plane": {
"origin": [
0,
0,
10
],
"x_dir": [
1,
0,
0
],
"y_dir": [
0,
1,
0
]
},
"name": "Top"
}
},
{
"command": "sketch.project",
"params": {
"refs": [
{
"face": [
2,
8,
10
]
}
]
}
},
{
"command": "sketch.circle.center",
"params": {
"center": [
40,
8
],
"radius": 8
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Top",
"distance": 10,
"operation": "join",
"targets": [
"Peg"
],
"profiles": [
{
"point": [
40,
8
]
}
],
"exact_regions": true
}
},
{
"command": "solid.fillet",
"params": {
"edges": [
[
20,
0,
10
]
],
"radius": 2
}
},
{
"command": "solid.fillet",
"params": {
"edges": [
[
0,
0,
4
],
[
0,
9,
10
],
[
0,
0.5858,
9.4142
]
],
"radius": 2
}
}
]
}
@@ -0,0 +1,14 @@
{
"sources": [
"tangency-labs/solidcraft#9"
],
"what": "three r2 rounds at a top corner of a bar whose far end runs tangent into a cylinder, with the curvature (g2) corner set back 3: SMLib refused the result coming back (a cylinder over more than a turn)",
"source": "analytic",
"method": "a bar 40 x 16 x 10 whose far end is a half disc R8 about (40, 8), a disc R8 about the same centre sketched on its top (the top's outline projected) and extruded 10 up, joined: the bar's sides run tangent into the cylinder, V0 = 6400 + 960 pi; the rolling-ball answer: the three r2 rounds at the bar's other top corner (0, 0, 10): the front round's spandrel from x = 2 to the wall (30.5896671706), the vertical edge's and the left top edge's (1 - pi/4) r^2 over 8 and 14, the corner cube of side 2 less the rolling ball's eighth, 8 - 4 pi/3: removed 53.2858385868; the G2 patch differs from the ball only within the setback cube of side 3 about the vertex, so V is graded as V0 - removed with 3^3 as its tolerance",
"body_count": 1,
"faces": 12,
"volume_mm3": 9362.643108859389,
"tolerance": {
"rel": 0.002883801046998287
}
}
@@ -0,0 +1,191 @@
{
"commands": [
{
"command": "sketch.create",
"params": {
"plane": "XY",
"name": "Peg"
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0,
0
],
[
40,
0
]
]
}
},
{
"command": "sketch.arc.center_point",
"params": {
"center": [
40,
8
],
"start": [
40,
0
],
"end": [
40,
16
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
40,
16
],
[
0,
16
]
]
}
},
{
"command": "sketch.line",
"params": {
"points": [
[
0,
16
],
[
0,
0
]
]
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Peg",
"distance": 10,
"body_name": "Peg"
}
},
{
"command": "sketch.create",
"params": {
"plane": {
"origin": [
0,
0,
10
],
"x_dir": [
1,
0,
0
],
"y_dir": [
0,
1,
0
]
},
"name": "Top"
}
},
{
"command": "sketch.project",
"params": {
"refs": [
{
"face": [
2,
8,
10
]
}
]
}
},
{
"command": "sketch.circle.center",
"params": {
"center": [
40,
8
],
"radius": 8
}
},
{
"command": "sketch.finish",
"params": {}
},
{
"command": "solid.extrude",
"params": {
"sketch": "Top",
"distance": 10,
"operation": "join",
"targets": [
"Peg"
],
"profiles": [
{
"point": [
40,
8
]
}
],
"exact_regions": true
}
},
{
"command": "solid.fillet",
"params": {
"edges": [
[
0,
0,
4
],
[
20,
0,
10
],
[
0,
8,
10
]
],
"radius": 2,
"corner": "g2",
"setbacks": [
{
"vertex": [
0,
0,
10
],
"distance": 3
}
],
"tangent_chain": false
}
}
]
}
+1
View File
@@ -21,6 +21,7 @@
2893-chamfer-distance-and-angle-past-the-wall-refused refused for another reason: SMLib SM_ERR_INVALID_INPUT, not a check saying the angle runs past the wall
2893-chamfer-two-distances-elliptical-rim SMLib status 1001 on the elliptical rim's chamfer
2893-chamfer-two-distances-past-the-bottom-refused refused for another reason: SMLib status 1001, not too large
1207-fillet-corner-after-round-running-into-cylinder-solver-residues SMLib's check refuses the round: its corner's surface of revolution is close to singular at its boundary (SmSurface test 5)
4782-fillet-bar-edge-wider-than-both-faces SMLib status 1001: a convex round past both its faces is not yet built by subtraction
5158-fillet-round-just-past-a-10mm-face SMLib status 1001: a convex round 0.01 past its face is not yet built by subtraction (10.0001-10.04 open)
5835-chamfer-sloped-basin-1-mm-rim-0-5-4-edges SMLib status 1001 on the chamfers meeting at the rim's re-entrant corners