mesh: report a kernel failure to partition as the kernel's, not a plane that misses (#283)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 11:11:14 -04:00
1 parent 22f2e0c9cc
commit d23c2efaec
3 files changed
+56 -10

No files matched your search

+1 -1
View File
@@ -94,7 +94,7 @@ pub const DATUM_DELETE: CommandSpec = CommandSpec {
pub const PARTITION: CommandSpec = CommandSpec {
id: "geometry.partition",
label: "Partition",
params: r#"{"name": "Half", "datum": "Mid", "bodies"?: ["Body1"] (default: every body the plane divides)} — cuts bodies of the open design into cells by a datum plane, in memory (the design file is not changed); the model keeps the partition and applies it again whenever the design is opened, after the partitions before it. A cut body `B` becomes the cells `B/a` (on the plane's negative side) and `B/b` (several pieces on a side: `B/a1`, `B/a2`, …). The two faces of the cut are paired by name: `partition:<name>:a` on the cells on the negative side, `partition:<name>:b` on the positive side (`…#0`, `…#1` where a cell has several); every other face keeps the persistent name of the face it is a piece of (`<face>#k` for several pieces on one cell), so sets by face name still select it. Reports the cells made and the cut faces named; a plane that divides no body, or a listed body it does not divide, is an error"#,
params: r#"{"name": "Half", "datum": "Mid", "bodies"?: ["Body1"] (default: every body the plane divides)} — cuts bodies of the open design into cells by a datum plane, in memory (the design file is not changed); the model keeps the partition and applies it again whenever the design is opened, after the partitions before it. A cut body `B` becomes the cells `B/a` (on the plane's negative side) and `B/b` (several pieces on a side: `B/a1`, `B/a2`, …). The two faces of the cut are paired by name: `partition:<name>:a` on the cells on the negative side, `partition:<name>:b` on the positive side (`…#0`, `…#1` where a cell has several); every other face keeps the persistent name of the face it is a piece of (`<face>#k` for several pieces on one cell), so sets by face name still select it. Reports the cells made and the cut faces named; a plane that divides no body, a listed body it does not divide, or a body the kernel fails to cut (its message) is an error"#,
form: Some(super::forms::PARTITION),
kind: CommandKind::Model,
requires: &[Requirement::Design, Requirement::DatumPlane],
+26
View File
@@ -708,6 +708,32 @@ mod tests {
assert!(r["energy_balance"].as_f64().unwrap() < 0.05, "{r}");
}
/// A partition the kernel fails to make is reported as the kernel's failure, never as a plane
/// that does not divide the body (#283): x = 148 plainly crosses the crank's half y > 0, whose
/// cut fails in the kernel's boolean, and the same plane cuts the half y < 0.
#[test]
fn a_kernel_failure_to_partition_is_reported_as_such() {
use fieldcraft_geometry::Cut;
let design = fieldcraft_geometry::open_design(&fieldcraft_geometry::script_design(CRANK_ARM).unwrap()).unwrap();
let cut = |name: &str, point: [f64; 3], normal: [f64; 3], bodies: &[&str]| Cut {
name: name.into(),
point,
normal,
bodies: bodies.iter().map(|b| b.to_string()).collect(),
};
let half = cut("Half", [0.0; 3], [0.0, 1.0, 0.0], &[]);
let p = design.partitioned(&[half.clone(), cut("X148", [148.0, 0.0, 0.0], [1.0, 0.0, 0.0], &[CRANK_HALF])]).unwrap();
assert!(p.errors.is_empty(), "{:?}", p.errors);
let p = design.partitioned(&[half, cut("X148", [148.0, 0.0, 0.0], [1.0, 0.0, 0.0], &["CrankArm/b"])]).unwrap();
// Should the kernel learn to make this cut, the cut is all there is to check.
if p.cuts[1].split.is_empty() {
let errors: Vec<&str> = p.errors.iter().filter(|e| e.feature == "partition X148").map(|e| e.message.as_str()).collect();
assert!(matches!(errors.as_slice(), [m] if m.starts_with("the kernel could not cut `CrankArm/b`")), "{errors:?}");
} else {
assert_eq!(p.cuts[1].split, ["CrankArm/b"]);
}
}
/// `file.sample NAME`, checked clean: ready, no errors or warnings.
fn open(name: &str) -> Session {
let mut s = Session::default();