Kernel rig: a volume or area can be graded as a hand-derived range where analysis only bounds it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-31 08:31:27 -04:00
committed by jyviko
parent 033d1a122c
commit 780a0652d9
2 changed files with 30 additions and 6 deletions
+29 -6
View File
@@ -871,12 +871,18 @@ fn check_case(dir: &str, dumps: Option<&std::path::Path>, validation: solidcraft
count(&mut c, "faces", got.faces);
count(&mut c, "edges", edges);
count(&mut c, "vertices", vertices);
if has("volume_mm3") {
c.rel("volume_mm3", got.volume, want["volume_mm3"].as_f64(), rel);
}
if has("area_mm2") {
c.rel("area_mm2", got.area, want["area_mm2"].as_f64(), rel);
}
// A measure is a value within `rel`, or an inclusive range `[min, max]` where analysis only
// bounds it (a cut removes at most its tool's volume).
let measure = |c: &mut Checks, k: &str, g: f64| {
if has(k) {
match want[k].as_array() {
Some(r) => c.push(k, json!(g), want[k].clone(), within(r, g)),
None => c.rel(k, g, want[k].as_f64(), rel),
}
}
};
measure(&mut c, "volume_mm3", got.volume);
measure(&mut c, "area_mm2", got.area);
if has("center_of_mass") {
c.vec("center_of_mass", got.com, v3(&want["center_of_mass"]), abs);
}
@@ -962,6 +968,14 @@ pub fn run(args: &[String]) -> Result<(), String> {
if bad > 0 { Err(format!("{bad} kernel case(s) failed")) } else { Ok(()) }
}
/// `g` lies in the inclusive range `[min, max]`; a range without both ends holds nothing.
fn within(range: &[Value], g: f64) -> bool {
match (range.first().and_then(Value::as_f64), range.get(1).and_then(Value::as_f64), range.len()) {
(Some(lo), Some(hi), 2) => lo <= g && g <= hi,
_ => false,
}
}
#[cfg(test)]
mod tests {
use std::f64::consts::PI;
@@ -1021,6 +1035,15 @@ mod tests {
assert_eq!(surface_word(&face(1, 1, 1, 1)), "bspline");
}
#[test]
fn a_measure_range_holds_its_ends_and_nothing_outside() {
let r = [json!(22500.0), json!(24000.0)];
assert!(within(&r, 22500.0) && within(&r, 24000.0) && within(&r, 23000.0));
assert!(!within(&r, 22499.9) && !within(&r, 24000.1));
assert!(!within(&[json!(1.0)], 1.0), "one end is no range");
assert!(!within(&[json!(1.0), json!(2.0), json!(3.0)], 1.5), "three ends are no range");
}
#[test]
fn json_parts_match_within_tolerance_and_whole_numbers_exactly() {
let got = json!({"bends": [{"angle_deg": 90.00001, "direction": "up", "count": 3}], "extra": 1});
+1
View File
@@ -106,6 +106,7 @@ the frozen truck-geometry PRs, a `kernel-paused` PR), for the report's coverage
| `body_count`, `faces`, `edges`, `vertices` | Exact counts, where the geometry fixes them. Faces and edges count whole analytic surfaces and curves once (a full cylinder side is one face). |
| `volume_mm3`, `area_mm2`, `center_of_mass`, `bbox` | As in `tests/oracle`: totals over every body, relative tolerance 1e-3 for volume and area, 1e-3 of the box diagonal for positions. |
| `faces`, `edges`, `vertices`, `shells`, `body_count` as `[min, max]` | An inclusive range, where the geometry only bounds a count (a modelled thread's faces). |
| `volume_mm3`, `area_mm2` as `[min, max]` | An inclusive range, where analysis only bounds the measure: a cut removes at most its tool's volume, a join adds at most the tool's. `method` derives both ends. |
| `continuity` | `{order, edges}`: every seam `edges` names reaches `order` (`g0` … `g4`), as Continuity Analysis (`inspect.continuity`) measures it: from the faces' exact surfaces, at its default 33 stations a seam and its default tolerances (1 µm, 1°, 0.01 / mm, 0.001 / mm², 0.0001 / mm³), never loosened per case. A failure names the worst seam (lowest order; of those, the one furthest past the next order's tolerance), its faces, the order it reaches and that next order's worst value. `edges`: `blend` (default), every seam of a face a fillet or chamfer made (a round, a chamfer face or a corner patch) with the faces either side of the edge it replaced, and every seam of a corner patch with the rounds it joins: what the blend is built to meet (not where a round runs into an end face, nor where two rounds meet without a patch: that seam is the input's, tangent only where the rounded edges were); `corner`, those of them on a corner patch (a blend made from several replaced edges); or a list of points `[[x,y,z], …]`, the edges nearest them, as Continuity Analysis picks them. `exact: true`: every seam reaches `order` and not the next (a case built to an order proves the measurement reads that order, no more and no less). A constant round meets its faces G1 (curvature jumps by 1 / r), a `curvature` round G2, a chamfer G0; a `corner: g2` patch is built G2 to the rounds and faces round it. |
| `continuity` (older form) | `{near, within_mm, clear_mm, curvature_jump_per_mm}`: over every tangent seam lying wholly within `within_mm` of `near` (a corner's patch and its neighbours), the curvature jump across it (the faces' normal curvatures square to the seam, from their exact surfaces, at 65 stations a seam) is at most `curvature_jump_per_mm` at every station at least `clear_mm` along the seam from a vertex where two of those seams turn (where a neighbour's own G1 seam ends, which no surface is G2 to both sides of). A rolling ball's corner jumps 1 / r everywhere. |
| `surface_types` | The set of kinds the faces' surfaces are, over every body, as the records hold them: `plane`, `cylinder`, `cone`, `sphere`, `torus`, `bspline` (a B-spline or NURBS surface) or `revolution` (a revolved curve). Exact: none missing, none extra. |