From e9ac0e606eddbc253bd4266734007ef06e617cfe Mon Sep 17 00:00:00 2001 From: brett19 Date: Sun, 1 Nov 2026 11:47:31 -0700 Subject: [PATCH] =?UTF-8?q?solidcore:=20a=20curvature=20fillet=20is=20refu?= =?UTF-8?q?sed=20until=20solidcore=20has=20the=20equal-area=20G=E2=81=BF?= =?UTF-8?q?=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01SdcgEu4Lr3moCCisdZShKz --- crates/kernel/src/blend_smlib.rs | 1 + crates/smlib/src/core/blend.rs | 16 +++++++++------- crates/smlib/src/core/blend_tests.rs | 27 +++++++++++++++++++++------ crates/smlib/src/lib.rs | 4 ++++ 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/crates/kernel/src/blend_smlib.rs b/crates/kernel/src/blend_smlib.rs index 8a03ad740e..77b62d9ccf 100644 --- a/crates/kernel/src/blend_smlib.rs +++ b/crates/kernel/src/blend_smlib.rs @@ -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 diff --git a/crates/smlib/src/core/blend.rs b/crates/smlib/src/core/blend.rs index d4e4a0395d..ffd12ccfc7 100644 --- a/crates/smlib/src/core/blend.rs +++ b/crates/smlib/src/core/blend.rs @@ -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 { 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 => { diff --git a/crates/smlib/src/core/blend_tests.rs b/crates/smlib/src/core/blend_tests.rs index 2d3a7f2e89..4114251381 100644 --- a/crates/smlib/src/core/blend_tests.rs +++ b/crates/smlib/src/core/blend_tests.rs @@ -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() { diff --git a/crates/smlib/src/lib.rs b/crates/smlib/src/lib.rs index ba0b139744..8f2fb11601 100644 --- a/crates/smlib/src/lib.rs +++ b/crates/smlib/src/lib.rs @@ -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}"), } } }