solidcore: a curvature fillet is refused until solidcore has the equal-area Gⁿ section

SMLib d01d7b0's capi builds the curvature section as the Gⁿ generator's order 2 at the
equal-area thumbweight (fork 6148773), taking off (1 - π/4) r² at a square corner. Our capi
port still built solidcore's blend-curve quintic (113/756 r²): a body every check passes,
off the closed form (862's box edge 7892.38 against 7845.49). solidcore has no Gⁿ generator
yet (solidcore#2250, port in progress as #2260), so Blend::Curvature now gives
Error::Unported, "not supported yet" with the issue, rather than another section. The
refusal comes off when the pin carries #2260 and 6148773 with fillet_gn_section_generator on.

The C++ closed-form case takes the equal-area volume; its solidcore twins are ignored until then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEu4Lr3moCCisdZShKz
This commit is contained in:
brett19
2026-11-01 11:47:31 -07:00
parent ff894bfb67
commit e9ac0e606e
4 changed files with 35 additions and 13 deletions
+1
View File
@@ -445,6 +445,7 @@ mod imp {
run on tangentially into each other, or meet in a way no blend corner builds)"
)),
SmError::NoRoom => KernelError::TooLarge(format!("the {what} is larger than the faces beside its edges allow")),
SmError::Unported(why) => KernelError::Invalid(format!("not supported yet: {why}")),
e => failed(what, "operation", e),
})?;
// Checked here unless the evaluation's validation mode leaves it to the feature's end
+9 -7
View File
@@ -235,10 +235,6 @@ fn chamfer_radius(k: &Kernel, b: &mut Brep, e: EdgeId, distance: f64) -> solidco
#[derive(Clone, Copy, PartialEq)]
enum Section<'a> {
Round,
/// The curvature-continuous section (`SM_FSG_BLEND_CURVE`, continuity 2), the quintic whose
/// inner control points lie a third and two thirds of the way from each contact to the corner
/// its tangents make (`SetBlendCurveAtThirds`, fork fd3b6a7).
Curvature,
Chord,
/// A chamfer, with each edge's offset of its second surface (`chamfer_offsets`' `second`;
/// the sizes are the first's).
@@ -334,7 +330,6 @@ fn blend_edges_stopping(
let before = face_set(b)?;
let done = {
let mut s = FilletSession::new(k, b)?;
s.set_blend_curve_at_thirds(matches!(section, Section::Curvature));
// `SetFilletParameters(b, es, sections, solvers, values, laws, -1, -1, -1, -1,
// continuity 1, thumbweight 1)`: the session sets the executive's global merge first, as
// `SmApiFilletEdges` does, and `SetFilletParameters` resets it, as in the capi.
@@ -345,7 +340,6 @@ fn blend_edges_stopping(
Some(&size) => s.prepare_variable_radius_edges(k, edges, size, law, 1, 1),
None => return Ok(Err(Err(Error::Argument))),
},
(Section::Curvature, _) => s.prepare_per_edge_constant_radius(k, edges, sizes, &[FilletSurfaceGeneratorType::BlendCurve], 2, 1.0),
(Section::Chord, _) => s.prepare_per_edge_constant_distance(k, edges, sizes, &[FilletSurfaceGeneratorType::Circular], 1, 1.0),
(Section::Chamfer(second), _) => chamfer_parameters(k, &mut s, edges, sizes, second),
};
@@ -658,6 +652,14 @@ fn untagged(mut b: Brep) -> Out<Brep> {
Ok(b)
}
/// Why [`Blend::Curvature`] is refused. The capi's curvature section is the Gⁿ generator's order 2
/// at the equal-area thumbweight (`SMLIB_BLEND_THUMBWEIGHT`, fork 6148773), which solidcore does
/// not have yet; the blend-curve quintic it does have takes off another area, so it is not built
/// in its place. The refusal comes off when the solidcore pin carries #2260 and 6148773 and its
/// `fillet_gn_section_generator` flag is on.
pub(crate) const CURVATURE_SECTION: &str = "a curvature-continuous fillet on solidcore: the curvature section's equal-area Gⁿ \
generator is not in solidcore yet (solidcore#2250, port in progress as #2260)";
/// [`blend_holes`] without its holes.
#[allow(clippy::too_many_arguments)] // the capi's blend_call
pub(crate) fn blend(
@@ -749,7 +751,7 @@ fn blend_call(
let solid_in = brep_p2::is_manifold_solid(&w)?;
let section = match kind {
Blend::Round => Section::Round,
Blend::Curvature => Section::Curvature,
Blend::Curvature => return Ok(Err(Error::Unported(CURVATURE_SECTION))),
Blend::Chord => Section::Chord,
// `chamfer_edges`: each edge's offsets from its setback at its midpoint.
Blend::Chamfer => {
+21 -6
View File
@@ -107,11 +107,11 @@ fn case(name: &str) -> Case {
}
c(Op::Blend(BLOCK, Blend::Round, edges, r), Expect::Volume(v))
}
// Each section of a curvature-continuous round is the quintic from contact to contact
// whose inner control points lie a third and two thirds of the way to the corner the
// contacts' tangents make (fork fd3b6a7). On the unit square's sides it takes
// ½∮(x dy - y dx) = 113/756 from the corner: abc - 113/756 r² L.
"fillet_curvature_one_edge" => c(Op::Blend(BLOCK, Blend::Curvature, vec![TOP_FRONT], r), Expect::Volume(ABC - 113.0 / 756.0 * r * r * 30.0)),
// The curvature section's default thumbweight is equal area (fork 6148773): on the
// round's rails it takes off what the ball's circle does, abc - (1 - π/4) r² L.
"fillet_curvature_one_edge" => {
c(Op::Blend(BLOCK, Blend::Curvature, vec![TOP_FRONT], r), Expect::Volume(ABC - (1.0 - PI / 4.0) * r * r * 30.0))
}
// abc - d² L / 2.
"chamfer_one_edge" => c(Op::Blend(BLOCK, Blend::Chamfer, vec![TOP_FRONT], r), Expect::Volume(ABC - r * r * 30.0 / 2.0)),
// abc - d1 d2 L / 2: 2 back along the top (the face leaning most along +z), 3 down the
@@ -387,7 +387,7 @@ macro_rules! cases {
cases!(
fillet_one_edge,
fillet_all_edges,
fillet_curvature_one_edge,
fillet_curvature_one_edge: ignore = "solidcore refuses the curvature section until it has the equal-area Gⁿ generator (solidcore#2250)",
chamfer_one_edge,
chamfer_two_distances,
chamfer_distance_angle,
@@ -662,6 +662,21 @@ fn a_setback_corner_left_open_returns_its_hole() {
assert_eq!(refused, Some(Error::CornerRefused { corner: 0 }));
}
/// A curvature-continuous round is refused on solidcore, whose only curvature section is the
/// blend-curve quintic (113/756 r² off a square corner, not the equal-area (1 - π/4) r²), until
/// its pin has the Gⁿ section generator (solidcore#2250).
#[test]
fn a_curvature_round_is_refused_until_solidcore_has_the_equal_area_section() {
let b = BLOCK.core();
let r = blend(&b, Blend::Curvature, &[TOP_FRONT], &[2.0], TOL, &[], &[], Corners::Filled).err();
assert_eq!(r, Some(Error::Unported(super::blend::CURVATURE_SECTION)));
assert!(r.is_some_and(|e| e.to_string().contains("solidcore#2250")));
let set_back = blend(&b, Blend::Curvature, &[TOP_FRONT], &[2.0], TOL, &[[0.0, 0.0, 10.0]], &[3.0], Corners::Filled).err();
assert_eq!(set_back, Some(Error::Unported(super::blend::CURVATURE_SECTION)));
// A pick that misses is still named first.
assert_eq!(blend(&b, Blend::Curvature, &[[90.0; 3]], &[2.0], TOL, &[], &[], Corners::Filled).err(), Some(Error::NoMatch { point: 0 }));
}
/// The capi's argument checks, before any kernel call.
#[test]
fn hostile_arguments_are_refused() {
+4
View File
@@ -85,6 +85,9 @@ pub enum Error {
Solidcore(String),
/// solidcore panicked; the guard caught it (the call, then the panic's message).
Internal(String),
/// A step of the C++ kernel solidcore has not ported yet, refused rather than built another
/// way: what is missing, and the solidcore issue that ports it.
Unported(&'static str),
}
impl std::fmt::Display for Error {
@@ -124,6 +127,7 @@ impl std::fmt::Display for Error {
Error::Record(u) => write!(f, "{u}"),
Error::Solidcore(e) => write!(f, "solidcore: {e}"),
Error::Internal(e) => write!(f, "solidcore failed internally in {e}"),
Error::Unported(what) => write!(f, "not supported yet: {what}"),
}
}
}