geometry: a partition the kernel can only nudge off its plane is refused (#427)

The kernel's last-resort boolean retry moves the tool 1.3 µm and turns it
2 µrad. For a partition that puts the cut beside the face it was meant to
run along and leaves a cell bounded by slivers (the open boundary at x = 130
on the crank half). halves() now refuses a result-altering retry and names it.

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-16 00:20:02 -04:00
1 parent 1af87f1f98
commit 3d38753cad
4 files changed
+56 -8

No files matched your search

+7 -6
View File
@@ -945,7 +945,8 @@ pub(crate) mod tests {
/// #384: cuts stay on their plane (the kernel's own split put them 1.8 µm off it on the air
/// of the enclosure, so the cut faces were not even recognized), and a plane along an
/// existing face or through a hole's axis is a warning naming the plane, the body and where.
/// existing face or through a hole's axis is a warning naming the plane, the body and where,
/// or refused where the kernel could only cut it off the plane.
#[test]
fn cuts_stay_on_the_plane_and_risky_ones_are_named() {
let (_dir, mut s) = pi5();
@@ -972,11 +973,11 @@ pub(crate) mod tests {
assert!(!cut.is_empty() && cut.iter().all(|x| (x - 16.5).abs() < 1e-9), "{cell}: {cut:?}");
}
s.execute("edit.undo", &json!({})).unwrap();
// Along the side of the heat sink's pedestal: named.
let r = plane(&mut s, "Side", 34.0);
let warnings = r["warnings"].to_string();
assert!(warnings.contains("partition `PSide`: the plane lies on face `F45:-x` of `Air`"), "{r}");
s.execute("edit.undo", &json!({})).unwrap();
// Along the side of the heat sink's pedestal: the kernel only cuts with its box nudged
// off the plane, so the cut is refused, saying why (#427).
s.execute("geometry.datum", &json!({"name": "Side", "by": {"type": "offset", "plane": "yz", "offset": 34.0}})).unwrap();
let e = s.execute("geometry.partition", &json!({"name": "PSide", "datum": "Side", "bodies": ["Air"]})).unwrap_err().to_string();
assert!(e.contains("could not cut `Air`") && e.contains("only made off the plane"), "{e}");
// Through a standoff's hole: the cell whose boundary breaks at the hole's edge is named.
let r = plane(&mut s, "Axis", 74.5);
let warnings = r["warnings"].to_string();
+35
View File
@@ -769,6 +769,41 @@ mod tests {
}
}
/// A cut at x = 130 runs along the pocket's end wall (#427), and the kernel only makes it
/// with its cutting box nudged 1.3 µm and 2 µrad off the plane: on the half y > 0 that left a
/// cell bounded by slivers next to the wall, which `mesh.cells` refused with an open boundary
/// at x = 130. The partition refuses such a cut, saying why, on the whole crank and on the
/// half, and leaves the body whole; the plane moved past the pocket (x = 136) cuts the half
/// into cells that mesh.
#[test]
fn a_cut_the_kernel_can_only_nudge_off_its_plane_is_refused() {
let datum = |name: &str, x: f64| json!({"name": name, "by": {"type": "offset", "plane": "yz", "offset": x}});
let refused = |s: &mut Session, body: &str| {
let e = s.execute("geometry.partition", &json!({"name": "X130", "datum": "X130", "bodies": [body]})).unwrap_err().to_string();
assert!(e.contains(&format!("could not cut `{body}`")) && e.contains("only made off the plane"), "{e}");
};
let names = |s: &mut Session| -> Vec<String> {
let r = s.execute("geometry.bodies", &json!({})).unwrap();
r.as_array().unwrap().iter().filter_map(|b| b["name"].as_str().map(String::from)).collect()
};
let bytes = fieldcraft_geometry::script_design(CRANK_ARM).unwrap();
let (_dir, path) = crate::tests::design_file(&bytes);
let mut whole = Session::default();
whole.execute("geometry.open", &json!({"path": path})).unwrap();
whole.execute("geometry.datum", &datum("X130", 130.0)).unwrap();
refused(&mut whole, "CrankArm");
assert_eq!(names(&mut whole), ["CrankArm"]);
let mut s = open("crank-mirror");
s.execute("geometry.datum", &datum("X130", 130.0)).unwrap();
refused(&mut s, "CrankArm/b");
assert_eq!(names(&mut s), ["CrankArm/a", "CrankArm/b"]);
s.execute("geometry.datum", &datum("X136", 136.0)).unwrap();
let r = s.execute("geometry.partition", &json!({"name": "X136", "datum": "X136", "bodies": ["CrankArm/b"]})).unwrap();
assert_eq!(r["cells"], json!(["CrankArm/b/a", "CrankArm/b/b"]), "{r}");
s.execute("mesh.cells", &json!({"bodies": ["CrankArm/b/a", "CrankArm/b/b"], "default": "extrude", "size": 3})).unwrap();
}
/// The whole crank extruded all-hex (#198): its back pocket's floor is a rounded rectangle,
/// straight sides running tangent into corner arcs, so the region is a disk whose O-grid's
/// middle block follows its 6:1 elongation; a square middle block squashed its elements to a
+12 -1
View File
@@ -187,6 +187,10 @@ impl Design {
/// several times the body's size, and its boolean then puts the cut up to a few micrometres off
/// the plane (1.8 µm on a 140 mm body), off the faces it should meet (#384); boxes of the body's own
/// size keep the cut on the plane.
///
/// A boolean the kernel could only make by moving one body against the other (its last-resort
/// tool nudge, micrometres and microradians) is refused: the cut would lie off the plane, next to
/// the face it was meant to run along, and leave a cell bounded by slivers no mesher takes (#427).
fn halves(body: &Shape, plane: &Plane) -> solidcraft_kernel::Result<Vec<solidcraft_kernel::Body>> {
let bbox = solidcraft_kernel::measure(&body.body)?.bbox;
let reach = bbox.diagonal().max(LINEAR_REACH);
@@ -203,7 +207,14 @@ fn halves(body: &Shape, plane: &Plane) -> solidcraft_kernel::Result<Vec<solidcra
let mut out = Vec::with_capacity(2);
for (lo, hi) in [(0.0, reach), (-reach, 0.0)] {
let Some(side) = solidcraft_kernel::extrude(plane, std::slice::from_ref(&square), lo, hi)?.pop() else { continue };
out.extend(solidcraft_kernel::boolean(&body.body, &side, solidcraft_kernel::BoolOp::Intersect)?);
let (cut, report) =
solidcraft_kernel::capture_boolean_diagnostics(|| solidcraft_kernel::boolean(&body.body, &side, solidcraft_kernel::BoolOp::Intersect));
if let Some(moved) = report.warning() {
return Err(solidcraft_kernel::KernelError::Failed(format!(
"the cut is only made off the plane ({moved}); the plane runs along a face or edge of the body: move it off (by a fraction of the element size)"
)));
}
out.extend(cut?);
}
Ok(out)
}
+2 -1
View File
@@ -77,7 +77,8 @@ Before the first mesh command:
- **Curved splits are allowed.** A cut does not have to be a plane: use a cylindrical or offset
surface (a ring around a boss, an offset of a fillet) when a plane would create slivers.
- **Never cut along an existing face** (a plane coincident with a face within tolerance leaves
micrometre slivers, #384) and never through the axis of a small hole (degenerate tessellation at the
micrometre slivers, #384; where the kernel can only make it by nudging its cut off the plane,
`geometry.partition` refuses it, #427) and never through the axis of a small hole (degenerate tessellation at the
hole edge). Offset the cut to just outside the feature instead.
- **Keep cuts consistent across touching bodies** so their meshes can match, or tie them
deliberately (`interaction.tie`; linear faces only today).