Merge pull request #2264 from MarkusReichart/fix/refine-cc-outside-parameter

fix: a refined parameter outside the range is an answer, opt-in (WR-663, fork 8ea6a20)
This commit is contained in:
bherbruck
2026-11-01 16:10:10 -05:00
committed by GitHub
4 changed files with 46 additions and 38 deletions
+11 -7
View File
@@ -78,16 +78,16 @@
//! are wired; [`SolidError::Unported`] is propagated from what they reach.
//! - **Statuses passed on (WR-663).** With the opt-in
//! `Kernel::fixes.query_statuses_pass_on` the statuses SMLib drops are passed on, as the
//! fork (tangency-labs/smlib 8311f8e): `sm_RefineCCSolutions` (its local intersections
//! and `Inversion`s), the degenerate `ApproximateCurve`'s `EditParameterization`,
//! fork (tangency-labs/smlib 8311f8e): `sm_RefineCCSolutions` (its local intersections;
//! its `Inversion`s not, tangency-labs/smlib 8ea6a20), the degenerate `ApproximateCurve`'s `EditParameterization`,
//! `ClassifyIntersectingRangeSolution`, `SmCPGlobalSolver::LocalSolve` and
//! `GlobalPointSolve`'s `SolveIt`.
use crate::curve::curve::knots_ignoring_status;
use crate::curve::curve::p2::{
add_to_curve, evaluate_continuity, find_test_params, flush_crv, inversion_passed_on, output,
pop_stack, proj_signed_angle, push_point, stack_look, test_step, CpGlobalSolver, Curve,
CurveKind, CurvePropertyType, TessStepResult, BSPLINE_CURVE_TYPE, CRV_ON_SURF_TYPE,
add_to_curve, evaluate_continuity, find_test_params, flush_crv, output, pop_stack,
proj_signed_angle, push_point, stack_look, test_step, CpGlobalSolver, Curve, CurveKind,
CurvePropertyType, TessStepResult, BSPLINE_CURVE_TYPE, CRV_ON_SURF_TYPE,
};
use crate::curve::curve::p3::{eval_n, local_curve_intersect, PointSolveOptions};
use crate::curve::curve::p4::proj_dist;
@@ -249,9 +249,13 @@ where
if x.deviation < end.solution_value {
// Do not allow big shifts. [140915b]
let inv1 = sol_ivl1.inversion(x.my_param);
inversion_passed_on(&inv1, pass_on)?;
let inv2 = sol_ivl2.inversion(x.other_param);
inversion_passed_on(&inv2, pass_on)?;
// The fork (tangency-labs/smlib 8ea6a20): a refined parameter outside the
// range is an answer, which the shift limit judges. Only a zero-length
// range gives no fraction, and then the end stays where it is.
if pass_on && (inv1.parameter.is_none() || inv2.parameter.is_none()) {
continue;
}
let mut frac1 = inv1.parameter.unwrap_or(UNDEF_DOUBLE);
let mut frac2 = inv2.parameter.unwrap_or(UNDEF_DOUBLE);
if reverse1 {
+2 -31
View File
@@ -201,12 +201,12 @@ use crate::curve::offset_curve::OffsetCurve;
use crate::curve::parabola::Parabola;
use crate::curve::projected_curve::ProjectedCurve;
use crate::curve::tangent_field::TangentFieldCurve;
use crate::error::{ignore_or_pass_on, ignored, ignored_or_pass_on, Result, SolidError, SM_ERR};
use crate::error::{ignore_or_pass_on, ignored, ignored_or_pass_on, Result, SolidError};
use crate::geom::a_object::{self, AObject, CacheStore};
use crate::geom::attribute::{AttributeStore, ObjectKey};
use crate::geom::axis2_placement::Axis2Placement;
use crate::geom::context::{Context, IdentitySlot};
use crate::geom::extent1d::{Extent1d, Inversion};
use crate::geom::extent1d::Extent1d;
use crate::geom::extent2d::Extent2d;
use crate::geom::extent3d::Extent3d;
use crate::geom::geom_utility::p1::{
@@ -2281,22 +2281,6 @@ pub fn base_ends<C: CurveEvaluate + ?Sized>(k: &Kernel, curve: &C) -> Result<End
})
}
/// The status of `Inversion` (`SM_ERR` when `v` is outside the interval or the
/// interval has no length) at a site where SMLib drops it and the fork passes it on
/// (`SER`, tangency-labs/smlib 8311f8e): with the opt-in
/// [`crate::kernel::UpstreamFixes::query_statuses_pass_on`] (`pass_on`) a failed
/// inversion is returned; without it the status is dropped, as upstream.
pub(crate) fn inversion_passed_on(inv: &Inversion, pass_on: bool) -> Result<()> {
if pass_on && !inv.is_success() {
return Err(SolidError::Upstream {
operation: "geom::extent1d::Extent1d::inversion",
code: SM_ERR,
entity: None,
});
}
Ok(())
}
/// The outcome of [`ignored_or_soft`].
#[derive(Debug)]
pub(crate) enum Soft<T> {
@@ -4477,19 +4461,6 @@ mod tests {
b
}
#[test]
fn issue_2009_inversion_status_is_passed_on() {
// tangency-labs/smlib 8311f8e (sm_RefineCCSolutions `SER(Inversion)`): a value
// outside the interval (or one that a zero-length interval cannot map) is
// SM_ERR with the opt-in fix and dropped without it.
let ivl = Extent1d::from_min_max(0.0, 1.0);
assert!(inversion_passed_on(&ivl.inversion(0.5), true).is_ok());
assert!(inversion_passed_on(&ivl.inversion(2.0), true).is_err());
assert!(inversion_passed_on(&ivl.inversion(2.0), false).is_ok());
let flat = Extent1d::from_min_max(1.0, 1.0);
assert!(inversion_passed_on(&flat.inversion(2.0), true).is_err());
}
#[test]
fn evaluation_copy_keeps_nested_knots_flags_and_owners() {
use crate::curve::projected_curve::OwnsCurve;
@@ -105,3 +105,27 @@ fn issue_948_difference_of_two_ruled_skins_fails_as_upstream() {
),
}
}
/// WR-663 with the fork's tangency-labs/smlib@8ea6a20: a refined end past a curve/curve range
/// no longer aborts the intersection. The difference still fails, as the pinned C++ and the
/// fork do, but where the default path does, not in `Extent1d::inversion`.
#[test]
fn a_refined_parameter_outside_the_range_does_not_abort_the_intersection() {
let data = sections(MIN7);
let mut k = Kernel::default();
k.fixes.query_statuses_pass_on = true;
let outer = ruled_skin(&mut k, &data[0].1);
let inner = ruled_skin(&mut k, &data[1].1);
match boolean_difference(&mut k, outer, inner) {
Err(SolidError::Upstream {
operation, code, ..
}) => {
assert_eq!(code, SM_ERR);
assert_eq!(operation, "topology::brep::p6");
}
other => panic!(
"expected the default path's SM_ERR, got {:?}",
other.map(|_| "a body")
),
}
}
@@ -0,0 +1,9 @@
Updates: WR-663
#### WR-663 (update, 2026-11-01). A refined parameter outside the range is an answer, not a failed status
- **What changes:** with `Kernel::fixes.query_statuses_pass_on`, `sm_RefineCCSolutions` no longer returns the status of `Inversion`. A refined end just past the solution's range keeps its fraction, and the shift limit judges it. Only a zero-length range gives no fraction, and then the end stays where it was. With the flag off nothing changes.
- **How found:** this site took 8311f8e's `SER(Inversion)` from the fork, but not the fork's later tangency-labs/smlib@8ea6a20 (9b93b88 before cherry-picking), which takes it back out. The fork at d01d7b0 has `(void)sSolIvl1.Inversion(..)` and `bHaveFracs`. The port's `SM_ERR` aborted the whole curve/curve intersection as `geom::extent1d::Extent1d::inversion`.
- **Confirmed:** in the Rust port.
- #948's `min7.txt`: with this flag alone the difference failed in `Extent1d::inversion`, earlier than the default path. It now fails where the default path does, in `topology::brep::p6`. The fork fails that difference with 1001 too, so this does not fix #948.
- The order-2 round of the pipe on a plate (#2252's `pipe_on_plate_g2`), on #2260's head 9f3cdcfbc with every fix on except `geometric_g2_continuity`: `Extent1d::inversion` 1001 before, built after. Volume 40258.543608517 against the fork case's 40258.5435962825 (3.0e-10 relative), seams G2. That case needs #2260's generator, so it is not a test here.
- The fork's C++ was run only as far as #948's status (d01d7b0 built from `source/SMLib` and `source/SM_API`).