mesh: a crank arm's plan takes rings round its bosses and a pillow from a carved grid (#455, #190)

When the grid layout does not fit a swept body (sides at an angle to the axes, root arcs where
an arm meets its bosses), mesh.plan tries the ring-and-pillow layout (cmd/plan/pillow.rs): each
boss is an annulus from its bore or pedal hole to its circle through every level, the arm a grid
carved a margin inside the outline and the bosses, with a pillow layer of blocks to the wall each
boundary face faces; a grid corner facing a boss and the outline gets a corner block reaching the
kink where the outline leaves the boss's circle. The old crank test (frame cuts) becomes the
heavy acceptance test at sizes 2.5, 3 and 4; the moved frame cut keeps a unit test.

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 10:38:26 -04:00
1 parent 992bc71b6b
commit 7c3107dec0
3 files changed
+1186 -23

No files matched your search

+8 -5
View File
@@ -64,6 +64,8 @@ pub(crate) struct Layout {
/// The frame round each circle group: its axis point (the sweep's coordinate 0) and the
/// smallest half-side of the frame.
pub(crate) frames: Vec<(V3, f64)>,
/// The ring-and-pillow layout ([`super::pillow`]): the pillow's least thickness along the walls.
pub(crate) pillow: Option<f64>,
}
impl Layout {
@@ -80,13 +82,14 @@ impl Layout {
pub(crate) fn summary(&self) -> String {
let layers: Vec<String> = self.layers.iter().map(|(s, d, w)| format!("{s} {d:.4} mm ({w})")).collect();
format!(
"projection blocking: {} blocks over a {}×{}×{} grid, {} circle(s) ringed{}",
"projection blocking: {} blocks over a {}×{}×{} grid, {} circle(s) ringed{}{}",
self.topology.blocks.len(),
self.planes[0].saturating_sub(1),
self.planes[1].saturating_sub(1),
self.planes[2].saturating_sub(1),
self.rings,
if layers.is_empty() { String::new() } else { format!(", a blend layer on {}", layers.join(", ")) }
if layers.is_empty() { String::new() } else { format!(", a blend layer on {}", layers.join(", ")) },
self.pillow.map_or_else(String::new, |t| format!(", a pillow layer from the carved grid to the walls ({t:.4} mm and more)"))
)
}
}
@@ -107,7 +110,7 @@ pub(crate) struct Input<'a> {
/// The local frame: local axis `a` is world axis `PERM[k][a]`, `k` the sweep's world axis (a
/// cyclic permutation, so blocks keep their handedness).
const PERM: [[usize; 3]; 3] = [[1, 2, 0], [2, 0, 1], [0, 1, 2]];
pub(super) const PERM: [[usize; 3]; 3] = [[1, 2, 0], [2, 0, 1], [0, 1, 2]];
const SIDE_NAMES: [[&str; 2]; 3] = [["-x", "+x"], ["-y", "+y"], ["-z", "+z"]];
@@ -700,7 +703,7 @@ pub(crate) fn layout(inp: &Input) -> Result<Layout, String> {
(world([g.c[0], g.c[1], 0.0]), half)
})
.collect();
Ok(Layout { topology: t, rings, layers, planes: planes.each_ref().map(Vec::len), frames })
Ok(Layout { topology: t, rings, layers, planes: planes.each_ref().map(Vec::len), frames, pillow: None })
}
/// The ring that takes in the blend with points `pts` (local frame): a blend round a corner of
@@ -811,7 +814,7 @@ fn hex_faces(b: &[u32; 8]) -> [[u32; 4]; 6] {
}
/// The Jacobian determinant at corner 1 of a hex8 block (positive: right-handed).
fn corner_det(c: &[V3; 8]) -> f64 {
pub(super) fn corner_det(c: &[V3; 8]) -> f64 {
dot(sub(c[1], c[0]), cross(sub(c[3], c[0]), sub(c[4], c[0])))
}
File diff suppressed because it is too large. Load diff