boolean2d through boolean_2d, typed edge-vanishes, typed put-back refusal

boolean2d goes through api_brep::boolean_2d, SM_ERR_OFFSET_EDGE_VANISHES through
is_offset_edge_vanishes (solidcore#2016). A mitred round that cannot be put back
(solidcore#2027) is Error::Unsupported. The tests held for solidcore#1462, #1973,
#1980 and #1981 run again. copy_of stays as it is: results of offset_faces on a
brep_copy (NamingScheme::Notify) fail check_brep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
This commit is contained in:
brett19
2026-10-31 06:57:15 -07:00
parent 14b1d19563
commit 8ad306e695
5 changed files with 40 additions and 32 deletions
+3 -3
View File
@@ -515,7 +515,6 @@ fn rounds_at_a_concave_face_corner_are_mitred() {
/// The back and left top edges are each trimmed 8 by those rounds: (0, 30)-(32, 30) and (0, 8)-(0, 30)
/// at z = 10, and each piece keeps its edge's attribute (`Behavior::Copy`), as on the fork.
#[test]
#[ignore = "solidcore#1980 (PR #1977): SubtractRemovalRegion loses the attributes of target edges it replaces"]
fn an_edge_tag_crosses_a_cliff_overflow() {
use solidcore::blend::fillet_executive::session::FilletSession;
use solidcore::boolean::api_brep::boolean_difference;
@@ -560,9 +559,9 @@ fn an_edge_tag_crosses_a_cliff_overflow() {
/// Solidcore alone: the pocket rim chamfered 5.5, as `fillet_mitre_concave_face_corners`' own test
/// does it, with `keep_operand_uv_trims` on too, on the body the boolean gives:
/// 21000 - (50 d² + 4/3 d³) = 19265.666666667 (the fork, and the same body through the flat
/// arrays). Refused: error 3, Corner Edge Computation Failure.
/// arrays). Without `fillet_run_on_bspline_trims` it is refused: error 3, Corner Edge Computation
/// Failure (solidcore#1981).
#[test]
#[ignore = "solidcore#1981 (PR #1996)"]
fn pocket_chamfer_on_a_boolean_body() {
use solidcore::blend::fillet_executive::session::FilletSession;
use solidcore::boolean::api_brep::boolean_difference;
@@ -577,6 +576,7 @@ fn pocket_chamfer_on_a_boolean_body() {
k.fixes.fillet_cliff_overflow = true;
k.fixes.fillet_mitre_concave_face_corners = true;
k.fixes.keep_operand_uv_trims = true;
k.fixes.fillet_run_on_bspline_trims = true;
let plate = create_box(&mut k, Point3::ZERO, 60.0, 40.0, 10.0).unwrap();
let cut = create_box(&mut k, p(15.0, 10.0, 5.0), 30.0, 20.0, 6.0).unwrap();
let mut b = boolean_difference(&mut k, plate, cut).unwrap();
+7 -16
View File
@@ -9,15 +9,14 @@
use std::sync::PoisonError;
use solidcore::SolidError;
use solidcore::boolean::api_brep as sm;
use solidcore::boolean::api_brep::{self as sm, Boolean2dOutcome};
use solidcore::boolean::merge::MergeOptions;
use solidcore::geom::a_object::BooleanOperation;
use solidcore::geom::attribute::ObjectKey;
use solidcore::geom::object::ObjectRef;
use solidcore::kernel::Kernel;
use solidcore::primitive::primitive_creation::p1::{Boolean2d, PrimitiveCreation};
use solidcore::primitive::primitive_creation::p1::Boolean2d;
use solidcore::topology::brep::{p1, p2, p3};
use solidcore::topology::topology_traverser::Marks;
use solidcore::topology::{Brep, edge};
use solidcore::{EntityRef, Result as SolidResult};
@@ -140,7 +139,7 @@ fn operation(op: BooleanOp) -> BooleanOperation {
/// Whether every body is a manifold solid (`IsManifoldSolid` of each copy).
fn all_solid(bodies: &[Brep]) -> SolidResult<bool> {
for b in bodies {
for (n, b) in bodies.iter().enumerate() {
if !p2::is_manifold_solid(b)? {
return Ok(false);
}
@@ -210,7 +209,7 @@ pub(crate) fn boolean2d(a: &Body, b: &Body, op: BooleanOp) -> Result<Option<Body
usable("SmApiBoolean2d", &[a, b])?;
let d = run("SmApiBoolean2d", |k| {
let mut c = copies(k, &[a, b])?.into_iter();
let (Some(mut ca), Some(mut cb)) = (c.next(), c.next()) else {
let (Some(ca), Some(cb)) = (c.next(), c.next()) else {
return Err(SolidError::invalid_input("smlib_boolean2d", "no operand"));
};
if num_faces(&ca) == 0 || num_faces(&cb) == 0 {
@@ -221,17 +220,9 @@ pub(crate) fn boolean2d(a: &Body, b: &Body, op: BooleanOp) -> Result<Option<Body
BooleanOp::Subtract => Boolean2d::Difference,
BooleanOp::Intersect => Boolean2d::Intersection,
};
// SmApiBoolean2d calls the static SmPrimitiveCreation::Boolean2D; its notifications are
// the operands' arena records, as solidcore's own merge calls it without a caller's.
let mut primitive = PrimitiveCreation::new(None, 0, false);
let (mut marks_a, mut marks_b) = (Marks::default(), Marks::default());
let mut state = p2::ArenaNotifyState::new(&ca);
state.seed(&cb);
let mut out = None;
primitive.boolean_2d(k, &mut ca, &mut cb, kind, &mut out, (&mut marks_a, &mut marks_b), &mut state.env(None))?;
match out {
Some(r) => finish(r, false, false),
None => Ok(Done::Empty),
match sm::boolean_2d(k, ca, cb, kind)? {
Boolean2dOutcome::Result(r) => finish(r, false, false),
Boolean2dOutcome::Unassigned { .. } => Ok(Done::Empty),
}
})?;
result(d, Ok(None))
-1
View File
@@ -646,7 +646,6 @@ mod history {
/// Two overlapping squares combined as regions of one plane.
#[test]
#[ignore = "solidcore#1462 (PR #1977): boolean_2d carries no attributes through its merge"]
fn a_boolean2d_has_the_cpp_history() {
use crate::sweeps::Curve;
let square = |x: f64, y: f64, s: f64, operand: usize| {
+7 -7
View File
@@ -53,16 +53,13 @@ const INVALID_INPUT: i32 = 1007;
/// `SMLIB_ERR_WALLS_TOUCH`.
const OFFSET_WALLS_TOUCH: i32 = 1042;
/// `SM_ERR_OFFSET_EDGE_VANISHES` (fork 34318f4): a vertex moved past an edge's other end, which
/// `status` (smlib_shim.h) reports as `SMLIB_ERR_UNSUPPORTED`.
const OFFSET_EDGE_VANISHES: i32 = 1043;
/// `status` (smlib_shim.h): an offset whose walls touch is [`Error::WallsTouch`]; any other
/// status as the blends pass it on.
/// `status` (smlib_shim.h): an offset whose walls touch is [`Error::WallsTouch`]; a vertex moved
/// past an edge's other end (`SM_ERR_OFFSET_EDGE_VANISHES`, fork 34318f4) is
/// `SMLIB_ERR_UNSUPPORTED`; any other status as the blends pass it on.
fn status(e: SolidError) -> Error {
match e {
SolidError::Upstream { code: OFFSET_WALLS_TOUCH, .. } => Error::WallsTouch,
SolidError::Upstream { code: OFFSET_EDGE_VANISHES, .. } => Error::Unsupported,
e if e.is_offset_edge_vanishes() => Error::Unsupported,
e => blend_status(e),
}
}
@@ -373,6 +370,9 @@ fn move_faces(k: &mut Kernel, b: &mut Brep, picked: &[FaceId], distance: f64) ->
OffsetFacesRefusal::Unsupported => Error::Unsupported,
OffsetFacesRefusal::NotExact => Error::NotExact,
OffsetFacesRefusal::NoEffect => Error::NoEffect,
// Not in the fork (solidcore#2020, #2027): the blends cannot be put back, the fillet's
// status kept. SolidCraft has no such error: a move solidcore does not make.
OffsetFacesRefusal::NotPutBack => Error::Unsupported,
OffsetFacesRefusal::None => status(e),
})
}
+23 -5
View File
@@ -775,7 +775,6 @@ mod history {
}
#[test]
#[ignore = "solidcore#1973 (PR #1977): the stitch in the shell drops the flat sheet's edge tags"]
fn tags_cross_a_thicken_of_a_flat_sheet() {
thicken_parity(false);
}
@@ -837,8 +836,11 @@ fn offset_faces_close_a_step_wall() {
/// Rig case shell/4492's rounded box: four mitred rounds, r 3, come off a 40 x 30 x 10 box's top
/// together and follow it. From height 0 the outline is the 34 x 24 top moved out by the round's
/// w(z) = sqrt(9 - (z - axis)^2), or 3 below the axis. With the fork's values at +19, and a body
/// whose rounds go all the way down at -7. With SMLib built, the C++ kernel gives the same.
/// w(z) = sqrt(9 - (z - axis)^2), or 3 below the axis. With the fork's values at +19, and bodies
/// whose rounds go all the way down from -7 and are cut short from -7.5 (solidcore#2027). At -9
/// solidcore refuses to put the rounds back (solidcore#2020, open): SolidCraft says it does not
/// make that move, where the C++ kernel gives the analytic value, also checked here. With SMLib
/// built, the C++ kernel gives the same at every other move.
#[test]
fn mitred_rounds_follow_a_moved_top() {
use super::blend::{Corners, blend};
@@ -856,13 +858,23 @@ fn mitred_rounds_follow_a_moved_top() {
})
.sum::<f64>()
};
let moves = [(19.0, [10, 20, 12]), (1.0, [10, 20, 12]), (-1.0, [10, 20, 12]), (-6.9, [10, 20, 12]), (-7.0, [6, 12, 8])];
let moves = [
(19.0, [10, 20, 12]),
(1.0, [10, 20, 12]),
(-1.0, [10, 20, 12]),
(-6.9, [10, 20, 12]),
(-7.0, [6, 12, 8]),
(-7.5, [6, 12, 8]),
(-8.0, [6, 12, 8]),
(-9.5, [6, 12, 8]),
];
for (d, n) in moves {
let moved = offset_faces(&rounded, &[top], 0.1, d).unwrap();
let v = volume(&moved, 1e-8).unwrap();
assert_eq!(counts(&moved).unwrap(), n, "d {d}");
assert!(rel(v, analytic(d)) < 1e-6, "d {d}: {v} vs {}", analytic(d));
}
assert!(matches!(offset_faces(&rounded, &[top], 0.1, -9.0), Err(Error::Unsupported)));
#[cfg(feature = "native")]
{
use crate::ffi;
@@ -872,7 +884,13 @@ fn mitred_rounds_follow_a_moved_top() {
for (d, _) in moves {
let cpp = ffi::ops::pick_op(ffi::ops::Pick::OffsetFaces, &round, &[top], 0.1, d).unwrap();
let ours = offset_faces(&rounded, &[top], 0.1, d).unwrap();
assert!(rel(volume(&ours, 1e-8).unwrap(), ffi::volume(&cpp, 1e-8).unwrap()) < 1e-9, "d {d}");
let (o, c) = (volume(&ours, 1e-8).unwrap(), ffi::volume(&cpp, 1e-8).unwrap());
// Below -7 the rounds are cut short, and the two differ in the seventh digit: each is
// within 1e-6 of the analytic value, and of the other.
let agree = if d < -7.0 { 1e-6 } else { 1e-9 };
assert!(rel(o, c) < agree, "d {d}: {o} vs the C++ kernel's {c}");
}
let cpp = ffi::ops::pick_op(ffi::ops::Pick::OffsetFaces, &round, &[top], 0.1, -9.0).unwrap();
assert!(rel(ffi::volume(&cpp, 1e-8).unwrap(), analytic(-9.0)) < 1e-6, "d -9");
}
}