mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 19:15:57 +00:00
Kernel rig: a flat_pattern field grades a sheet metal case's flat pattern
From the script's last sheet.flat_pattern result: the outline's size, its area less the cut-outs', and the bend lines, each matched to its own one of the result's either way round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -583,6 +583,61 @@ fn case_name(dir: &str) -> (String, String) {
|
||||
(name(p.parent()), name(Some(p)))
|
||||
}
|
||||
|
||||
/// The area a polygon of `[x, y]` points encloses (shoelace), unsigned.
|
||||
fn polygon_area(points: &Value) -> f64 {
|
||||
let pts: Vec<(f64, f64)> = points.as_array().into_iter().flatten().filter_map(|p| Some((p.get(0)?.as_f64()?, p.get(1)?.as_f64()?))).collect();
|
||||
pts.iter().zip(pts.iter().cycle().skip(1)).map(|(a, b)| a.0 * b.1 - b.0 * a.1).sum::<f64>().abs() / 2.0
|
||||
}
|
||||
|
||||
/// The `flat_pattern` check, against the script's last `sheet.flat_pattern` result: `size_mm`
|
||||
/// ([w, h] of the outline), `area_mm2` (the outline loops' area less the cut-outs') and
|
||||
/// `bend_lines` ([[x, y], [x, y]] each, the bends' centre lines in flat coordinates, every one
|
||||
/// matched to its own one of the result's, either way round, and as many of them).
|
||||
fn flat_pattern_checks(c: &mut Checks, want: &Value, results: &[Value], rel: f64, abs: f64) {
|
||||
let Some(got) = results.iter().rev().find(|r| r.get("flat_size_mm").is_some()) else {
|
||||
c.push("flat_pattern", json!("no sheet.flat_pattern result"), want.clone(), false);
|
||||
return;
|
||||
};
|
||||
if let Some(w) = want.get("size_mm").and_then(Value::as_array) {
|
||||
let g = &got["flat_size_mm"];
|
||||
let ok = w.len() == 2
|
||||
&& (0..2).all(|k| match (g.get(k).and_then(Value::as_f64), w.get(k).and_then(Value::as_f64)) {
|
||||
(Some(g), Some(w)) => (g - w).abs() <= abs,
|
||||
_ => false,
|
||||
});
|
||||
c.push("flat_pattern.size_mm", json!([g.get(0), g.get(1)]), json!(w), ok);
|
||||
}
|
||||
if let Some(w) = want.get("area_mm2").and_then(Value::as_f64) {
|
||||
let loops = |k: &str| got[k].as_array().into_iter().flatten().map(polygon_area).sum::<f64>();
|
||||
c.rel("flat_pattern.area_mm2", loops("outline") - loops("cutouts"), Some(w), rel);
|
||||
}
|
||||
if let Some(w) = want.get("bend_lines").and_then(Value::as_array) {
|
||||
let line = |v: &Value| -> Option<[f64; 4]> {
|
||||
let (a, b) = (v.get(0)?, v.get(1)?);
|
||||
Some([a.get(0)?.as_f64()?, a.get(1)?.as_f64()?, b.get(0)?.as_f64()?, b.get(1)?.as_f64()?])
|
||||
};
|
||||
let mut left: Vec<[f64; 4]> = got["bends"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|b| line(&json!([b["start"], b["end"]])))
|
||||
.collect();
|
||||
let got_lines = json!(left.iter().map(|l| [[l[0], l[1]], [l[2], l[3]]]).collect::<Vec<_>>());
|
||||
let near = |p: [f64; 4], q: [f64; 4]| (0..4).all(|k| (p[k] - q[k]).abs() <= abs);
|
||||
let mut ok = left.len() == w.len();
|
||||
for l in w.iter().map(line) {
|
||||
let found = l.and_then(|l| left.iter().position(|g| near(*g, l) || near(*g, [l[2], l[3], l[0], l[1]])));
|
||||
match found {
|
||||
Some(i) => {
|
||||
left.swap_remove(i);
|
||||
}
|
||||
None => ok = false,
|
||||
}
|
||||
}
|
||||
c.push("flat_pattern.bend_lines", got_lines, json!(w), ok);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_case(dir: &str, dumps: Option<&std::path::Path>) -> Value {
|
||||
let (area, name) = case_name(dir);
|
||||
let mut out = json!({"case": name, "area": area, "kernel": kernel()});
|
||||
@@ -852,6 +907,9 @@ fn check_case(dir: &str, dumps: Option<&std::path::Path>) -> Value {
|
||||
if has("construction") {
|
||||
check_construction(&s, &want["construction"], pos_tol, &mut c);
|
||||
}
|
||||
if has("flat_pattern") {
|
||||
flat_pattern_checks(&mut c, &want["flat_pattern"], &answers, rel, abs);
|
||||
}
|
||||
out["status"] = json!(if c.pass { "pass" } else { "fail" });
|
||||
out["checks"] = json!(c.list);
|
||||
out
|
||||
@@ -1111,4 +1169,27 @@ mod tests {
|
||||
assert!(script(&a, "/scratch", &r).unwrap_err().contains("deeper than"));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
/// The flat pattern is graded on the last `sheet.flat_pattern` result: its size, its outline's
|
||||
/// area less the cut-outs', and its bend lines matched either way round, one for one.
|
||||
#[test]
|
||||
fn flat_pattern_grades_size_area_and_bend_lines() {
|
||||
let flat = json!({"flat_size_mm": [10.0, 4.0, 2.0],
|
||||
"outline": [[[0.0, 0.0], [10.0, 0.0], [10.0, 4.0], [0.0, 4.0]]], "cutouts": [[[1.0, 1.0], [2.0, 1.0], [2.0, 2.0], [1.0, 2.0]]],
|
||||
"bends": [{"start": [5.0, 0.0], "end": [5.0, 4.0]}]});
|
||||
let results = [json!({"volume_mm3": 1.0}), flat];
|
||||
let grade = |want: Value| {
|
||||
let mut c = Checks { list: Vec::new(), pass: true };
|
||||
flat_pattern_checks(&mut c, &want, &results, 1e-3, 1e-3);
|
||||
c.pass
|
||||
};
|
||||
assert!(grade(json!({"size_mm": [10.0, 4.0], "area_mm2": 39.0, "bend_lines": [[[5.0, 4.0], [5.0, 0.0]]]})));
|
||||
assert!(!grade(json!({"area_mm2": 40.0})), "the cut-out is not material");
|
||||
assert!(!grade(json!({"size_mm": [10.0, 5.0]})));
|
||||
assert!(!grade(json!({"bend_lines": [[[5.0, 0.0], [5.0, 4.0]], [[5.0, 0.0], [5.0, 4.0]]]})), "one bend line matches one only");
|
||||
assert!(!grade(json!({"bend_lines": [[[6.0, 0.0], [6.0, 4.0]]]})));
|
||||
let mut c = Checks { list: Vec::new(), pass: true };
|
||||
flat_pattern_checks(&mut c, &json!({"area_mm2": 1.0}), &results[..1], 1e-3, 1e-3);
|
||||
assert!(!c.pass, "no flat pattern was made");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ the frozen truck-geometry PRs, a `kernel-paused` PR), for the report's coverage
|
||||
| `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. |
|
||||
| `splines` | `[{degree: [p, q], spans?: [m, n]}, …]`: one entry per B-spline face, each matching one face (directions in either order); `spans`, the distinct knot intervals each way, only where the construction fixes them. |
|
||||
| `boundary_edges` | The edges only one face uses (a sheet's free boundary), over every body, as the records hold them; exact or `[min, max]`. |
|
||||
| `flat_pattern` | A sheet metal case's flat pattern, from the script's last `sheet.flat_pattern` result: `size_mm` ([w, h] of the outline), `area_mm2` (the outline's area less the cut-outs', relative tolerance as volume) and `bend_lines` (each `[[x, y], [x, y]]`, a bend's centre line in flat coordinates, matched one for one, either way round, and as many as the result has), positions within the box-diagonal tolerance. |
|
||||
| `watertight` | `true`: each body's display mesh (chord tolerance `tolerance.mesh_mm`, default 0.01 mm) has no open and no over-shared edges, with vertices closer than `tolerance.weld_mm` (default 1e-4 mm) taken as one. |
|
||||
| `timeout_s` | A longer timeout for this case than the run's (at most 1800 s), for cases that are slow by nature. A case that only needs it on a loaded machine shouldn't set it. |
|
||||
| `tolerance` | Optional overrides: `rel`, `abs_mm`, `mesh_mm`, `weld_mm`. |
|
||||
@@ -188,7 +189,7 @@ check on any change to `crates/engine/src/cmd/`, a recipe or `EXEMPT`, even when
|
||||
|
||||
`ladder/` orders kernel operations by complexity instead of by where they went wrong: tiers from
|
||||
primitives through degenerate booleans, analytic and spline profiles, holes and taps, threads,
|
||||
fillets, chamfers, shells, patterns, to whole parts. Later tiers build on earlier tiers' shapes (a
|
||||
fillets, chamfers, shells, patterns, whole parts, and sheet metal. Later tiers build on earlier tiers' shapes (a
|
||||
running part grows one tier at a time through `base`), so the last case carries everything.
|
||||
|
||||
- `ladder/<NN-tier>/<NNN-name>/`: cases in the format above, each a test with its own answer.
|
||||
|
||||
Reference in New Issue
Block a user