mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 03:25:58 +00:00
Kernel tests: three rounds of different radii at a corner meet in SMLib's setback corner
Each round runs whole to its section square to its edge at its setback (the owner's rule, at least 1.5 r, scaled to the least at which the G1 fill is certified convex), and the hole is the setback split: one patch per round and the triangle in three quadrilaterals, 15 faces, G1. Outside the box the sections span the body is the three strips', so its volume is theirs within that box's. A later r3 round at the mitred corner makes the body all three at once make (7928.142563938, 15 faces), as the engine and the model-cache tests have it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
This commit is contained in:
@@ -317,7 +317,8 @@ fn a_model_saved_under_another_check_mode_is_evaluated_again() {
|
||||
/// later round at the corner where they meet blends them again with it whether the design was
|
||||
/// opened from its model or evaluated anew. Two edges of a 20 mm box rounded r2 (a mitre at the
|
||||
/// corner (20, 0, 20)), saved; opened, the upright edge there rounded r3: the corner all three
|
||||
/// rounded at once make, 10 faces and 7928.743041 mm³, the same body as the design evaluated anew.
|
||||
/// rounded at once make (SMLib's setback corner, kernel `three_rounds_at_a_corner`), 15 faces and
|
||||
/// 7928.142563938 mm³, the same body as the design evaluated anew.
|
||||
#[test]
|
||||
fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
||||
let mut built = Session::default();
|
||||
@@ -334,8 +335,8 @@ fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
||||
let st = s.model.state();
|
||||
let b = &st.body("Box").unwrap().body;
|
||||
let v = solidcraft_engine::kernel::measure(b).unwrap().volume;
|
||||
assert_eq!(b.face_count(), 10);
|
||||
assert!((v - 7928.743041).abs() < 1e-6, "{v}");
|
||||
assert_eq!(b.face_count(), 15);
|
||||
assert!((v - 7928.142563938).abs() < 1e-6, "{v}");
|
||||
}
|
||||
assert_eq!(model("later round", &cached), model("later round", &rebuilt));
|
||||
let _ = std::fs::remove_file(&path);
|
||||
|
||||
@@ -91,10 +91,10 @@ fn fillet_as_wide_as_a_face_rounds_it_off() {
|
||||
|
||||
/// The owner's ruling of 2026-10-30: two edges of a 20 mm box rounded r2 in one fillet (they
|
||||
/// meet in a mitre at the corner (20, 0, 20), the upright edge there sharp), then the upright edge
|
||||
/// r3 in a second fillet, whose tangent chain takes the mitre's seam with it. The corner is one
|
||||
/// smooth patch, the body rounding all three at once makes (kernel `fillet_radii` of the box with
|
||||
/// each edge's radius: 10 faces, 7928.743041 mm³); the r2 rounds keep their names, and the r3
|
||||
/// round and the corner patch are the second fillet's.
|
||||
/// r3 in a second fillet, whose tangent chain takes the mitre's seam with it. The corner is the
|
||||
/// body rounding all three at once makes (kernel `fillet_radii` of the box with each edge's
|
||||
/// radius, `three_rounds_at_a_corner`: SMLib's setback corner, 15 faces, 7928.142563938 mm³); the
|
||||
/// r2 rounds keep their names, and the r3 round and the corner's patches are the second fillet's.
|
||||
#[test]
|
||||
fn a_later_round_at_a_mitred_corner_is_blended_with_the_rounds_there() {
|
||||
let mut s = Session::default();
|
||||
@@ -116,16 +116,17 @@ fn a_later_round_at_a_mitred_corner_is_blended_with_the_rounds_there() {
|
||||
let second = run(&mut s, "solid.fillet", json!({"edges": [[20, 0, 10]], "radius": 3}))["feature"].as_u64().unwrap();
|
||||
assert!(s.model.results.iter().all(|r| r.error.is_none()), "{:?}", s.model.results);
|
||||
let v = volume(&mut s);
|
||||
assert!((v - 7928.743041).abs() < 1e-5, "{v}");
|
||||
assert!((v - 7928.142563938).abs() < 1e-6, "{v}");
|
||||
let faces = run(&mut s, "model.faces", json!({"body": "Box"}))["faces"].as_array().cloned().unwrap_or_default();
|
||||
assert_eq!(faces.len(), 10);
|
||||
assert_eq!(faces.len(), 15);
|
||||
let after: Vec<String> = rounds.iter().map(|p| named(&mut s, *p)).collect();
|
||||
assert_eq!(after, before);
|
||||
// The r3 round (halfway up, at 45°) and the corner patch: the two faces the second fillet made.
|
||||
// The r3 round (halfway up, at 45°) and the corner's patches: the faces the second fillet made.
|
||||
let k3 = 3.0 - 4.5_f64.sqrt();
|
||||
assert!(named(&mut s, solidcraft_geom::Vec3::new(20.0 - k3, k3, 10.0)).starts_with(&format!("F{second}:blend:")));
|
||||
let made = faces.iter().filter(|f| f["name"].as_str().is_some_and(|n| n.starts_with(&format!("F{second}:")))).count();
|
||||
assert_eq!(made, 2, "{faces:?}");
|
||||
// the r3 round and the corner's six patches (a setback patch per round, three in the triangle)
|
||||
assert_eq!(made, 7, "{faces:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -305,30 +305,6 @@ mod tests {
|
||||
8000.0 - full + (sum - union) * hh * hh
|
||||
}
|
||||
|
||||
/// What the strips leave of the corner block where a patch over the corner of rounds `ra`,
|
||||
/// `rb`, `rc` on a, b, c lies: beyond a's end section (y < rc), b's (x > 20 − rc) and c's end
|
||||
/// (z > 20 − max(ra, rb), where c's rails meet a's and b's), the box's material that no
|
||||
/// strip removes, by columns along z. Outside that block such a body is the strips' (its
|
||||
/// rounds are the strips' cylinders up to those ends), so its patch takes at most this
|
||||
/// beyond them.
|
||||
fn corner_block(ra: f64, rb: f64, rc: f64) -> f64 {
|
||||
let len = |d: f64, r: f64| if d < r { r - (r * r - (r - d) * (r - d)).max(0.0).sqrt() } else { 0.0 };
|
||||
let (n, depth) = (2000, ra.max(rb));
|
||||
let h = rc / n as f64;
|
||||
let mut kept = 0.0;
|
||||
for i in 0..n {
|
||||
let dx = (i as f64 + 0.5) * h;
|
||||
for j in 0..n {
|
||||
let dy = (j as f64 + 0.5) * h;
|
||||
if (rc - dx).powi(2) + (rc - dy).powi(2) > rc * rc && dx < rc && dy < rc {
|
||||
continue;
|
||||
}
|
||||
kept += depth - len(dx, ra).max(len(dy, rb)).min(depth);
|
||||
}
|
||||
}
|
||||
kept * h * h
|
||||
}
|
||||
|
||||
/// Largest angle (degrees) between the two faces' normals along edges between curved faces.
|
||||
fn kink(b: &Body) -> f64 {
|
||||
let rec = Faces::of(b).unwrap();
|
||||
@@ -515,29 +491,41 @@ mod tests {
|
||||
assert!(r.validity().is_empty(), "{:?}", r.validity());
|
||||
assert!((vol(&r) - sphere).abs() < 1e-11 * sphere, "{} vs {sphere}", vol(&r));
|
||||
assert!(kink(&r) < 0.01);
|
||||
// Different radii: a smooth patch over the corner, more than the strips take, and no more
|
||||
// than they leave in the block it lies in: its rounds end at a's section y = rc, b's
|
||||
// x = 20 − rc, and c's rails where they meet a's and b's (z = 20 − ra, z = 20 − rb), the
|
||||
// patch's four corners. (2, 2, 3): 1.168 beyond the strips, of the 11.86 the block keeps;
|
||||
// (2, 3, 4): 3.099 of 32.69.
|
||||
// Different radii: SMLib's setback corner (SmBlendSetback.h). Each round runs whole up to
|
||||
// its section square to its edge at its setback, the owner's rule's (at least 1.5 r, more
|
||||
// where the rails beside it cross later, scaled to the least at which the corner's G1 fill
|
||||
// is certified convex), and the hole between the three sections is filled by the setback
|
||||
// split: one setback patch per round and the triangle between them in three
|
||||
// quadrilaterals, so 6 planes, 3 rounds and 6 patches (15 faces), G1 across every seam.
|
||||
// Outside the box the sections span ([20 - d_b, 20] x [0, d_a] x [20 - d_c, 20], d each
|
||||
// edge's setback) the body is the three strips', so its volume is theirs within that box's.
|
||||
// The rounds a and b of one radius lie mirrored about the plane x + y = 20 with c: one
|
||||
// setback. (2, 2, 3): 3.6155, 3.6155, 4.6485; (2, 3, 4): 4.7745, 5.03975, 6.366 (a, b, c).
|
||||
for (rs, sel) in [((2.0, 2.0, 3.0), fillet(&ab, &[c], 3.0)), ((2.0, 3.0, 4.0), fillet_radii(&bx, &[(a, 2.0), (b, 3.0), (c, 4.0)]))] {
|
||||
let r = sel.unwrap();
|
||||
assert_eq!(r.face_count(), 10);
|
||||
assert_eq!(r.face_count(), 15);
|
||||
assert_eq!(r.unmeshed_faces(), 0);
|
||||
assert!(r.validity().is_empty(), "{:?}", r.validity());
|
||||
let k = kink(&r);
|
||||
assert!(k < 0.05, "kink {k}°");
|
||||
assert!(k < 0.01, "kink {k}°");
|
||||
let (ra, rb, rc) = rs;
|
||||
let ends = [
|
||||
Vec3::new(20.0 - ra, rc, 20.0),
|
||||
Vec3::new(20.0, rc, 20.0 - ra),
|
||||
Vec3::new(20.0 - rc, rb, 20.0),
|
||||
Vec3::new(20.0 - rc, 0.0, 20.0 - rb),
|
||||
];
|
||||
for p in ends {
|
||||
assert!(vertices(&r).iter().any(|q| q.dist(p) < 1e-9), "{rs:?}: no patch corner at {p:?}");
|
||||
let vs = vertices(&r);
|
||||
// An edge's setback: where its round's section meets both faces, at two vertices.
|
||||
let setback = |at: &dyn Fn(f64) -> [Vec3; 2], r: f64| -> Option<f64> {
|
||||
vs.iter()
|
||||
.flat_map(|p| [p.y, 20.0 - p.x, 20.0 - p.z])
|
||||
.find(|d| *d >= 1.5 * r - 1e-9 && *d < 20.0 && at(*d).iter().all(|q| vs.iter().any(|p| p.dist(*q) < 1e-9)))
|
||||
};
|
||||
let da = setback(&|d| [Vec3::new(20.0 - ra, d, 20.0), Vec3::new(20.0, d, 20.0 - ra)], ra);
|
||||
let db = setback(&|d| [Vec3::new(20.0 - d, 0.0, 20.0 - rb), Vec3::new(20.0 - d, rb, 20.0)], rb);
|
||||
let dc = setback(&|d| [Vec3::new(20.0 - rc, 0.0, 20.0 - d), Vec3::new(20.0, rc, 20.0 - d)], rc);
|
||||
eprintln!("{rs:?}: setbacks {da:?} {db:?} {dc:?}");
|
||||
let (Some(da), Some(db), Some(dc)) = (da, db, dc) else { panic!("{rs:?}: a round's setback section is missing: {da:?} {db:?} {dc:?}") };
|
||||
if ra == rb {
|
||||
assert!((da - db).abs() < 1e-9, "{da} vs {db}");
|
||||
}
|
||||
let (v, s) = (vol(&r), strips(ra, rb, rc));
|
||||
assert!(v < s + 0.05 && v > s - corner_block(ra, rb, rc), "{v} vs {s}");
|
||||
assert!((v - s).abs() < db * da * dc, "{v} vs {s}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ solidcraft-engine::tests::shell_tangent_chain_that_opens_every_face_explains_how
|
||||
solidcraft-kernel::blend::corner_tests::a_round_ending_in_an_earlier_round_matches_both_together SMLib fillet status 1001 on the round that ends in the earlier one
|
||||
solidcraft-kernel::blend::corner_tests::corner_blends_hold_at_any_scale at 0.01x a picked edge runs on tangentially into an unblended edge and SMLib's result is not a closed solid (the smooth-vertex End, tangency-labs/smlib#81)
|
||||
solidcraft-kernel::blend_corner::tests::issue2422_vertical_after_separate_bottom_rounds SMLib's check refuses the fillet: edge and vertex tolerance 0.97 against the body's 0.01 (#2422)
|
||||
solidcraft-kernel::blend_corner::tests::three_rounds_at_a_corner the corner's variable-radius transition between rounds of different radii (its elliptic law, a rail edge the corner interpolates) meets its neighbours at 0.144 degrees once its sections are the balls' touch circles (0 with the great circles); the corner step replaces that transition
|
||||
solidcraft-kernel::corner_g2::tests::a_long_setback_keeps_the_g2_patch_inside_the_box at set backs 11 to 15 the G2 corner patch bulges 2.6e-6 to 1.33e-3 past the box (the explicit corner is in progress)
|
||||
solidcraft-kernel::corner_g2::tests::the_box_corner_g2_patch_is_a_closed_solid_curvature_continuous_to_its_neighbours the fitted G2 corner patch bulges 1.7e-5 past the box, which SMLib's exact tight box now sees (the explicit G2 corner builds it inside)
|
||||
solidcraft-kernel::tests::fillet_tabbed_plate_outer_loop_and_partial_selection_after_hole the partial selection runs on tangentially into an unblended edge and SMLib's result is not a closed solid (the smooth-vertex End, tangency-labs/smlib#81)
|
||||
|
||||
Reference in New Issue
Block a user