mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 02:55:54 +00:00
kernel, kernel-cases: a shell whose walls would meet is refused, saying so
Rig case shell/4711 (a 60 x 40 x 10 block, an r3 through hole at (22.5, 7), a 10 x 10 notch on
the front edge, shelled 2 open at the bottom) failed with "SMLib shell: operation: SMLib failed
(status 1027)". The hole is 4 = 2 x 2 from the front face: the hole wall's offset (r5) is tangent
to the front's at (22.5, 2), the walls meet along a line and the cavity would pinch to zero
thickness there, which is no solid. Refusing it is right; the refusal did not say why.
SMLib now refuses an offset whose walls would meet with its own status, SMLIB_ERR_WALLS_TOUCH
(-1110). The smlib crate reads it as Error::WallsTouch; the kernel's offset backend turns it into
a sentence with the wall, as blends turn NoRoom into one: "shell: the 2 mm walls would meet along
a line (between a hole and the side, for example), which leaves no solid there; use a thinner or
thicker wall, or move the feature". Thicker builds too (SMLib shells 4711 at 2.5, the walls
crossing and merging between the hole and the front), so the sentence offers both.
4711 expects that refusal and its message ("outcome": "refused", "refusal"), its "what" and
"method" saying why: the wall is 4 = 2 x 2, the walls meet along a line. Tests: the smlib crate
shells 4711 built from primitives (2 refused as WallsTouch, 1.5 and 2.5 valid), the kernel a
plate whose hole is 3 from its side at 1.5 (refused, with "the 1.5 mm walls would meet along a
line").
Results (run-sc.sh against SMLib a759622's code, which differs from the tree run only in
comments and docs; baseline 858f381fc-5448b9a): rig 410/447 (409
before; 4711 fail -> pass as the refusal, nothing else changed), ladder 109/116 (unchanged),
kernel lib 535 passed / 202 failed (one new test; the same failing set), smlib crate 53 / 2
(one new test; the same failing set).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113gDVmStT5tCGKW9Fxgc5K
This commit is contained in:
@@ -58,6 +58,6 @@ pub(crate) fn heal(body: &Body, keep: &[Vec<Vec3>]) -> Result<Body> {
|
||||
}
|
||||
}
|
||||
let tolerance = record.tolerance * 10.0;
|
||||
let (healed, history) = crate::offset_backend::run("healing", body, |b| b.merge_faces(&kept, tolerance))?;
|
||||
let (healed, history) = crate::offset_backend::run("healing", body, None, |b| b.merge_faces(&kept, tolerance))?;
|
||||
Ok(healed.with_checked_history(history, &crate::record::counts(&[body])).with_split_lines(body.splits.clone()))
|
||||
}
|
||||
|
||||
@@ -552,6 +552,17 @@ mod smlib {
|
||||
verified(&r, &plate, reach(2.5 * 1.5));
|
||||
}
|
||||
|
||||
/// A 40 x 40 x 10 plate with an r2 hole 3 from its side, shelled 1.5 open at the top: the
|
||||
/// hole's offset just reaches the side's, so the walls would meet along a line. Refused,
|
||||
/// saying so and with the wall, never as SMLib's bare status.
|
||||
#[test]
|
||||
fn a_shell_whose_walls_would_meet_is_refused_saying_so() {
|
||||
let hole = Loop2::circle(Vec2::new(5.0, 20.0), 2.0);
|
||||
let plate = extrude(&Plane::XY, &[Region2 { outer: square(0.0, 0.0, 40.0, 40.0), holes: vec![hole] }], 0.0, 10.0).unwrap().pop().unwrap();
|
||||
let e = offset_backend::shell(&plate, &[Vec3::new(35.0, 20.0, 10.0)], 1.5).unwrap_err().to_string();
|
||||
assert!(e.contains("the 1.5 mm walls would meet along a line"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_moves_a_face() {
|
||||
let b = ell();
|
||||
|
||||
@@ -26,10 +26,19 @@ fn failed(op: &str, what: &str, e: impl std::fmt::Display) -> KernelError {
|
||||
KernelError::Failed(format!("SMLib {op}: {what}: {e}"))
|
||||
}
|
||||
|
||||
/// The error for SMLib's refusal of `op`, in the words of the operation.
|
||||
fn refused(op: &str, e: SmError) -> KernelError {
|
||||
/// The error for SMLib's refusal of `op`, in the words of the operation; `wall` is its wall's
|
||||
/// thickness, where it has one.
|
||||
fn refused(op: &str, wall: Option<f64>, e: SmError) -> KernelError {
|
||||
match e {
|
||||
SmError::NoRoom => KernelError::Invalid(format!("{op}: the wall is too thick: it leaves no room inside the body")),
|
||||
// The walls just meet: thinner they stay apart, thicker they cross and merge.
|
||||
SmError::WallsTouch => {
|
||||
let wall = wall.map(|t| format!(" {t} mm")).unwrap_or_default();
|
||||
KernelError::Invalid(format!(
|
||||
"{op}: the{wall} walls would meet along a line (between a hole and the side, for example), which leaves no solid there; \
|
||||
use a thinner or thicker wall, or move the feature"
|
||||
))
|
||||
}
|
||||
SmError::NoMatch { point } => KernelError::Invalid(format!("{op}: no face at selection point {}", point + 1)),
|
||||
SmError::Ambiguous { point } => KernelError::Invalid(format!("{op}: selection point {} is as near to two faces", point + 1)),
|
||||
SmError::Unsupported => KernelError::Failed(format!("not supported yet: SMLib {op} for this geometry")),
|
||||
@@ -48,12 +57,18 @@ fn points(p: &[Vec3]) -> Vec<[f64; 3]> {
|
||||
}
|
||||
|
||||
/// Runs `op` on `b` in SMLib, tagged as operand 0, and brings the result back with SMLib's
|
||||
/// history over `[b]` (none where an order cannot be matched).
|
||||
pub(crate) fn run(name: &str, b: &Body, op: impl FnOnce(&Brep) -> std::result::Result<Brep, SmError>) -> Result<(Body, Option<History>)> {
|
||||
/// history over `[b]` (none where an order cannot be matched). `wall`: the operation's wall
|
||||
/// thickness, for its refusals.
|
||||
pub(crate) fn run(
|
||||
name: &str,
|
||||
b: &Body,
|
||||
wall: Option<f64>,
|
||||
op: impl FnOnce(&Brep) -> std::result::Result<Brep, SmError>,
|
||||
) -> Result<(Body, Option<History>)> {
|
||||
b.require_brep(name)?;
|
||||
let (sb, ob) = smlib_body_ordered(b)?;
|
||||
let tagged = sb.tagged(0).map_err(|e| failed(name, "tag", e))?;
|
||||
let r = op(&tagged).map_err(|e| refused(name, e))?;
|
||||
let r = op(&tagged).map_err(|e| refused(name, wall, e))?;
|
||||
let check = r.check(CheckLevel::All).map_err(|e| failed(name, "check", e))?;
|
||||
if !check.is_valid() {
|
||||
let input = sb.check(CheckLevel::All).is_ok_and(|c| !c.is_valid());
|
||||
@@ -84,7 +99,7 @@ pub(crate) fn shell(b: &Body, open: &[Vec3], thickness: f64) -> Result<Body> {
|
||||
let opened = crate::offset::with_same_surface(b, &picked);
|
||||
let tol = pick_tolerance(b);
|
||||
let pts = points(&face_points(b, &opened)?);
|
||||
let (r, h) = run("shell", b, |s| s.shell(&pts, tol, -thickness))?;
|
||||
let (r, h) = run("shell", b, Some(thickness), |s| s.shell(&pts, tol, -thickness))?;
|
||||
let h = h.and_then(|h| with_offsets(&r, b, h, thickness));
|
||||
Ok(r.with_checked_history(h, &record::counts(&[b])))
|
||||
}
|
||||
@@ -96,7 +111,7 @@ pub(crate) fn offset_faces(b: &Body, chosen: &[usize], distance: f64) -> Result<
|
||||
let at = face_points(b, chosen)?;
|
||||
let tol = pick_tolerance(b);
|
||||
let pts = points(&at);
|
||||
let (r, h) = run("offset faces", b, |s| s.offset_faces(&pts, tol, distance))?;
|
||||
let (r, h) = run("offset faces", b, None, |s| s.offset_faces(&pts, tol, distance))?;
|
||||
let h = h.map(|mut h| {
|
||||
for f in &mut h.faces {
|
||||
if f.change == Change::Kept && f.origins.iter().any(|o| matches!(o, FaceOrigin::Face { face, .. } if chosen.contains(face))) {
|
||||
@@ -114,7 +129,7 @@ pub(crate) fn thicken(b: &Body, thickness: f64, symmetric: bool) -> Result<Body>
|
||||
if !(thickness.is_finite() && thickness.abs() > 1e-9) {
|
||||
return Err(KernelError::Invalid("thickness must be non-zero".into()));
|
||||
}
|
||||
let (r, h) = run("thicken", b, |s| s.thicken(thickness, symmetric))?;
|
||||
let (r, h) = run("thicken", b, Some(thickness.abs()), |s| s.thicken(thickness, symmetric))?;
|
||||
let step = if symmetric { thickness.abs() / 2.0 } else { thickness.abs() };
|
||||
// The walls first, while the sheet's edges still carry their tags on both copies.
|
||||
let h = h.map(with_edge_walls).and_then(|h| with_offsets(&r, b, h, step));
|
||||
@@ -138,7 +153,7 @@ pub(crate) fn draft(b: &Body, at: &[Vec3], neutral: &solidcraft_geom::Plane, pul
|
||||
let tol = pick_tolerance(b);
|
||||
let pts = points(&face_points(b, &chosen)?);
|
||||
let o = neutral.origin;
|
||||
let (r, h) = run("draft", b, |s| s.draft(&pts, tol, [o.x, o.y, o.z], [normal.x, normal.y, normal.z], [pull.x, pull.y, pull.z], angle))?;
|
||||
let (r, h) = run("draft", b, None, |s| s.draft(&pts, tol, [o.x, o.y, o.z], [normal.x, normal.y, normal.z], [pull.x, pull.y, pull.z], angle))?;
|
||||
let h = h.map(|mut h| {
|
||||
for f in &mut h.faces {
|
||||
if f.change == Change::Kept && f.origins.iter().any(|o| matches!(o, FaceOrigin::Face { face, .. } if chosen.contains(face))) {
|
||||
|
||||
@@ -19,6 +19,7 @@ pub const ERR_NO_ROOM: i32 = -1103;
|
||||
pub const ERR_NO_CUT: i32 = -1104;
|
||||
pub const ERR_UNSUPPORTED: i32 = -1105;
|
||||
pub const ERR_NO_EFFECT: i32 = -1107;
|
||||
pub const ERR_WALLS_TOUCH: i32 = -1110;
|
||||
|
||||
pub const BOOLEAN_UNION: i32 = 0;
|
||||
pub const BOOLEAN_SUBTRACT: i32 = 1;
|
||||
|
||||
@@ -52,6 +52,10 @@ pub enum Error {
|
||||
/// No room for the operation: an inward offset (a shell's wall, faces moved in) leaves none
|
||||
/// inside the body, or a blend is larger than the faces it rolls on allow.
|
||||
NoRoom,
|
||||
/// An offset whose walls would meet (a shell's): the offsets of two faces touch without
|
||||
/// crossing, a hole's wall offset just reaching a side's, so the result would pinch to
|
||||
/// nothing along a line. A thinner or a thicker wall builds.
|
||||
WallsTouch,
|
||||
/// SMLib cannot build this exactly yet (a variable radius whose law has a corner).
|
||||
Unsupported,
|
||||
/// SMLib reported success but the operation did nothing: a blend that made no face, a face
|
||||
@@ -83,6 +87,7 @@ impl std::fmt::Display for Error {
|
||||
Error::NoMatch { point } => write!(f, "selection point {} is not near any edge or face", point + 1),
|
||||
Error::Ambiguous { point } => write!(f, "selection point {} is equally near to two edges or faces", point + 1),
|
||||
Error::NoRoom => write!(f, "there is no room for it: an offset leaves none inside the body, or a blend is larger than its faces allow"),
|
||||
Error::WallsTouch => write!(f, "the offset's walls would meet along a line, which leaves no solid there"),
|
||||
Error::Unsupported => write!(f, "not supported yet: SMLib cannot build this exactly"),
|
||||
Error::NoEffect => write!(f, "SMLib made no change: it reported success, but the operation did nothing"),
|
||||
Error::NotExact => write!(f, "SMLib cannot build this exactly in the input's geometry"),
|
||||
@@ -114,6 +119,7 @@ fn error(s: i32) -> Error {
|
||||
ffi::ERR_EMPTY => Error::Empty,
|
||||
ffi::ops::ERR_NOT_SOLID => Error::NotSolid,
|
||||
ffi::ops::ERR_NO_ROOM => Error::NoRoom,
|
||||
ffi::ops::ERR_WALLS_TOUCH => Error::WallsTouch,
|
||||
ffi::ops::ERR_UNSUPPORTED => Error::Unsupported,
|
||||
ffi::ops::ERR_NO_EFFECT => Error::NoEffect,
|
||||
ffi::trim::ERR_NOT_EXACT => Error::NotExact,
|
||||
|
||||
@@ -182,7 +182,9 @@ impl Brep {
|
||||
|
||||
/// Hollowed to a wall of `thickness` (negative: inside the original surface, positive:
|
||||
/// outside), open at the faces nearest to `open_faces` (each within `tolerance`); none gives
|
||||
/// a closed hollow body. An inward wall that leaves no room inside is [`Error::NoRoom`].
|
||||
/// a closed hollow body. An inward wall that leaves no room inside is [`Error::NoRoom`]; one
|
||||
/// whose walls would just meet (a hole exactly twice the wall from a side) is
|
||||
/// [`Error::WallsTouch`].
|
||||
pub fn shell(&self, open_faces: &[[f64; 3]], tolerance: f64, thickness: f64) -> Result<Brep> {
|
||||
self.pick_op(ffi::ops::Pick::Shell, open_faces, tolerance, thickness, true)
|
||||
}
|
||||
|
||||
@@ -223,6 +223,24 @@ fn hostile_inputs_are_errors() {
|
||||
assert_untouched(&c, 8000.0, [6, 12, 8]);
|
||||
}
|
||||
|
||||
/// Rig case shell/4711: a 60 x 40 x 10 block with an r3 through hole at (22.5, 7), 4 from the
|
||||
/// front face, and a 10 x 10 notch on that edge, shelled 2 inward open at the bottom: the hole's
|
||||
/// offset (r5) is tangent to the front's (y = 2), so the walls meet along a line and the offset
|
||||
/// bounds no solid there. Refused as such; 1.5 (the walls apart) and 2.5 (crossing, merged)
|
||||
/// build.
|
||||
#[test]
|
||||
fn a_shell_whose_walls_would_meet_is_refused_as_such() {
|
||||
let block = Brep::make_box([0.0; 3], [60.0, 40.0, 10.0]).unwrap();
|
||||
let pin = Brep::make_cylinder([22.5, 7.0, -5.0], 3.0, 20.0).unwrap();
|
||||
let notch = Brep::make_box([25.0, -5.0, -5.0], [10.0, 10.0, 20.0]).unwrap();
|
||||
let part = block.boolean(&pin, BooleanOp::Subtract).unwrap().unwrap().boolean(¬ch, BooleanOp::Subtract).unwrap().unwrap();
|
||||
let bottom = [[50.0, 30.0, 0.0]];
|
||||
assert_eq!(part.shell(&bottom, 0.1, -2.0).unwrap_err(), Error::WallsTouch);
|
||||
for t in [1.5, 2.5] {
|
||||
shot(&part.shell(&bottom, 0.1, -t).unwrap(), &format!("shell-4711-{t}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_history_through_tagged_operands() {
|
||||
// A slot across the top of the cube: the cube's 6 faces give 7 result faces (the top is cut
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"sources": [
|
||||
"tangency-labs/solidcraft#4711"
|
||||
],
|
||||
"what": "a 2 mm shell, open at the bottom, of a 60x40x10 block whose r3 through hole breaks out into a 10x10 notch at its edge: truck took 75 s and failed ('boolean Cut: no result')",
|
||||
"what": "a 2 mm shell, open at the bottom, of a 60x40x10 block with an r3 through hole 4 mm from its front face, beside a 10x10 notch at that edge: the walls from the hole and from the front would meet along a line, so the right answer is a refusal that says so (truck took 75 s and failed ('boolean Cut: no result'); SMLib refused it, but as 'status 1027')",
|
||||
"source": "invariants",
|
||||
"method": "every wall has room for 2 mm (the thinnest, between the hole and the block's side, is 4 mm), so the shell exists: one valid closed body with one shell, open at the bottom. The cavity's corners at the hole's break-out follow the offset surfaces, so the volume is not closed-form (the reporter's script)",
|
||||
"body_count": 1,
|
||||
"shells": 1
|
||||
"method": "the hole (centre (22.5, 7), r3) is 7 - 3 = 4 mm from the front face y = 0, and two 2 mm walls take 4 = 2 x 2 mm: the hole wall's offset (r 3 + 2 = 5) is tangent to the front's (y = 2) at (22.5, 2), so the cavity pinches to zero thickness along the line x = 22.5, y = 2, which bounds no solid. A thinner wall leaves the walls apart, a thicker one makes them cross and merge; at exactly 2 mm the shell is refused, naming the cause. Expected message: \"shell: the 2 mm walls would meet along a line (between a hole and the side, for example), which leaves no solid there; use a thinner or thicker wall, or move the feature\".",
|
||||
"outcome": "refused",
|
||||
"refusal": "shell: the 2 mm walls would meet along a line (between a hole and the side, for example), which leaves no solid there; use a thinner or thicker wall, or move the feature"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user