mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
Merge pull request #1922 from aapolipponen/fix/wr96
fix: topology.trimming_tools — opt-in: a later curve loop starts after the previous loop ends (WR-96)
This commit is contained in:
@@ -1384,14 +1384,14 @@ fn run_rust_geom(k: &mut Kernel, op: &OpIn) -> Result<Vec<f64>, Fail> {
|
||||
}
|
||||
|
||||
/// The shim's `tt_sample_safe`.
|
||||
fn sample_safe(l: &CurveLoops, store: &[Curve], key: CurveKey, t: f64) -> Result<bool, Fail> {
|
||||
fn sample_safe(k: &Kernel, l: &CurveLoops, store: &[Curve], key: CurveKey, t: f64) -> Result<bool, Fail> {
|
||||
let c = store.get(key.0).ok_or_else(|| harness("key"))?;
|
||||
let d = c.natural_interval();
|
||||
let tol = d.length() / 1000.0;
|
||||
if d.contains_value(t, -tol) {
|
||||
return Ok(true);
|
||||
}
|
||||
let adj = match l.find_adjacent_loop_curve(store, key, t) {
|
||||
let adj = match l.find_adjacent_loop_curve(k, store, key, t) {
|
||||
Ok(a) => a,
|
||||
Err(e) if e.is_unported() => return Err(fail(&e)),
|
||||
Err(_) => 0,
|
||||
@@ -1433,7 +1433,7 @@ fn run_rust_loops(
|
||||
}
|
||||
LoopOp::StartEnd { i } => {
|
||||
o.push(2.0);
|
||||
let r = l.loop_start_end(u(*i));
|
||||
let r = l.loop_start_end(k, u(*i));
|
||||
o.push(status(&r)?);
|
||||
if let Ok((s, e)) = r {
|
||||
o.extend([s as f64, e as f64]);
|
||||
@@ -1441,7 +1441,7 @@ fn run_rust_loops(
|
||||
}
|
||||
LoopOp::Adjacent { key, t } => {
|
||||
o.push(3.0);
|
||||
let r = l.find_adjacent_loop_curve(&store, CurveKey(*key), *t);
|
||||
let r = l.find_adjacent_loop_curve(k, &store, CurveKey(*key), *t);
|
||||
o.push(status(&r)?);
|
||||
if let Ok(a) = r {
|
||||
o.push(a as f64);
|
||||
@@ -1457,7 +1457,7 @@ fn run_rust_loops(
|
||||
}
|
||||
LoopOp::Sample { key, t } => {
|
||||
o.push(5.0);
|
||||
if !sample_safe(&l, &store, CurveKey(*key), *t)? {
|
||||
if !sample_safe(k, &l, &store, CurveKey(*key), *t)? {
|
||||
o.push(NULL_DEREF);
|
||||
continue;
|
||||
}
|
||||
@@ -1481,7 +1481,7 @@ fn run_rust_loops(
|
||||
o.push(NULL_DEREF);
|
||||
continue;
|
||||
};
|
||||
if !sample_safe(&l, &store, CurveKey(*key), *t)? {
|
||||
if !sample_safe(k, &l, &store, CurveKey(*key), *t)? {
|
||||
o.push(NULL_DEREF);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -145,6 +145,9 @@ impl Caches {
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[non_exhaustive]
|
||||
pub struct UpstreamFixes {
|
||||
/// `SmCurveLoops::GetLoopStartEnd` starts a later loop after the previous loop
|
||||
/// ends, keeping adjacency within that loop (WR-96). Off by default for C++ parity.
|
||||
pub curve_loop_start_after_previous_end: bool,
|
||||
/// The generic temporary-radius constructor reports setup failures before filleting (#458, UB-252 follow-up).
|
||||
pub temporary_radius_setup_errors: bool,
|
||||
/// `SmFilletGeom::Trim` returns the first curve/surface trim or interval-intersection error instead of dropping it and continuing with an unset interval (WR-604, #483). Earlier successful edits remain; off by default for pinned C++ parity.
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
//! `m_apObjects[1]`).
|
||||
//! - **Indices** are `usize` with upstream's `ULONG` wrap-around arithmetic.
|
||||
//! `GetLoopStartEnd` sets the start of a later loop to the previous loop's last index,
|
||||
//! as upstream does (`plan/upstream-issues.md` WR-96). An index past the curve list, which
|
||||
//! as upstream does (WR-96), unless `Kernel::fixes.curve_loop_start_after_previous_end`
|
||||
//! is enabled. An index past the curve list, which
|
||||
//! upstream reads out of bounds, is [`SolidError::InvalidInput`].
|
||||
//! - **Ignored statuses.** `SampleNearbyPoints` ignores `FindAdjacentLoopCurve`'s status
|
||||
//! (index `0` when the curve is not in the loops) and `FindAdjacentLoopCurve` ignores
|
||||
@@ -281,7 +282,7 @@ impl CurveLoops {
|
||||
|
||||
/// `GetLoopStartEnd` with its outputs as left on failure: the status, `rlStart` and
|
||||
/// `rlEnd`.
|
||||
fn loop_start_end_raw(&self, curve_index: usize) -> (bool, usize, usize) {
|
||||
fn loop_start_end_raw(&self, k: &Kernel, curve_index: usize) -> (bool, usize, usize) {
|
||||
let mut start = 0usize;
|
||||
let mut end = 0usize;
|
||||
for (ii, &c) in self.loop_counts.iter().enumerate() {
|
||||
@@ -292,20 +293,24 @@ impl CurveLoops {
|
||||
if curve_index <= end {
|
||||
return (true, start, end);
|
||||
}
|
||||
start = end;
|
||||
start = if k.fixes.curve_loop_start_after_previous_end {
|
||||
end.wrapping_add(1)
|
||||
} else {
|
||||
end
|
||||
};
|
||||
}
|
||||
(false, start, end)
|
||||
}
|
||||
|
||||
/// `GetLoopStartEnd`: the first and last curve index of the loop holding
|
||||
/// `curve_index` (a later loop's start is the previous loop's last index, as
|
||||
/// upstream).
|
||||
/// `curve_index`. With `k.fixes.curve_loop_start_after_previous_end`, a later
|
||||
/// loop starts after the previous loop's end; the default reproduces WR-96.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `SM_ERR` when the index is past every loop.
|
||||
pub fn loop_start_end(&self, curve_index: usize) -> Result<(usize, usize)> {
|
||||
match self.loop_start_end_raw(curve_index) {
|
||||
pub fn loop_start_end(&self, k: &Kernel, curve_index: usize) -> Result<(usize, usize)> {
|
||||
match self.loop_start_end_raw(k, curve_index) {
|
||||
(true, s, e) => Ok((s, e)),
|
||||
_ => Err(SolidError::sm_err(OP)),
|
||||
}
|
||||
@@ -313,7 +318,7 @@ impl CurveLoops {
|
||||
|
||||
/// `FindAdjacentLoopCurve`: the index of the loop curve before `key` (when `param`
|
||||
/// lies in the first half of its natural interval) or after it, cyclically in its
|
||||
/// loop.
|
||||
/// loop. Honors `k.fixes.curve_loop_start_after_previous_end` (WR-96).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -321,6 +326,7 @@ impl CurveLoops {
|
||||
/// in `store`.
|
||||
pub fn find_adjacent_loop_curve(
|
||||
&self,
|
||||
k: &Kernel,
|
||||
store: &[Curve],
|
||||
key: CurveKey,
|
||||
param: f64,
|
||||
@@ -329,7 +335,7 @@ impl CurveLoops {
|
||||
return Err(SolidError::sm_err(OP));
|
||||
};
|
||||
// status ignored upstream
|
||||
let (_, start, end) = self.loop_start_end_raw(idx);
|
||||
let (_, start, end) = self.loop_start_end_raw(k, idx);
|
||||
let mid = curve_of(store, key)?.natural_interval().mid();
|
||||
Ok(if param < mid {
|
||||
if idx == start {
|
||||
@@ -425,7 +431,7 @@ impl CurveLoops {
|
||||
}
|
||||
|
||||
// find adjacent loop curve (status ignored, index 0 on failure)
|
||||
let adj = match self.find_adjacent_loop_curve(store, key, param) {
|
||||
let adj = match self.find_adjacent_loop_curve(k, store, key, param) {
|
||||
Ok(a) => a,
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => 0,
|
||||
@@ -770,10 +776,10 @@ mod tests {
|
||||
let l = loops(&[3, 4]);
|
||||
assert_eq!(l.loop_start_index(1).unwrap(), 3);
|
||||
assert!(l.loop_start_index(2).is_err());
|
||||
assert_eq!(l.loop_start_end(1).unwrap(), (0, 2));
|
||||
assert_eq!(l.loop_start_end(&Kernel::default(), 1).unwrap(), (0, 2));
|
||||
// upstream's start of a later loop is the previous loop's last index
|
||||
assert_eq!(l.loop_start_end(4).unwrap(), (2, 6));
|
||||
assert!(l.loop_start_end(7).is_err());
|
||||
assert_eq!(l.loop_start_end(&Kernel::default(), 4).unwrap(), (2, 6));
|
||||
assert!(l.loop_start_end(&Kernel::default(), 7).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//! WR-96: later loops start after the previous loop's inclusive end.
|
||||
|
||||
use solidcore::topology::trimming_tools::curve_loops::CurveLoops;
|
||||
use solidcore::Kernel;
|
||||
|
||||
#[test]
|
||||
fn later_loop_ranges_are_disjoint_with_the_fix() {
|
||||
let mut k = Kernel::default();
|
||||
k.fixes.curve_loop_start_after_previous_end = true;
|
||||
let keys: Vec<_> = (0..8)
|
||||
.map(solidcore::topology::trimming_tools::curve_loops::CurveKey)
|
||||
.collect();
|
||||
let loops = CurveLoops::from_loops(&[3, 4, 1], None, Some(&keys), &[]);
|
||||
for (index, range) in [
|
||||
(0, (0, 2)),
|
||||
(2, (0, 2)),
|
||||
(3, (3, 6)),
|
||||
(4, (3, 6)),
|
||||
(6, (3, 6)),
|
||||
(7, (7, 7)),
|
||||
] {
|
||||
assert_eq!(loops.loop_start_end(&k, index).unwrap(), range);
|
||||
}
|
||||
assert!(loops.loop_start_end(&k, 8).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_ranges_keep_pinned_cpp_results() {
|
||||
let k = Kernel::default();
|
||||
assert!(!k.fixes.curve_loop_start_after_previous_end);
|
||||
let keys: Vec<_> = (0..8)
|
||||
.map(solidcore::topology::trimming_tools::curve_loops::CurveKey)
|
||||
.collect();
|
||||
let loops = CurveLoops::from_loops(&[3, 4, 1], None, Some(&keys), &[]);
|
||||
assert_eq!(loops.loop_start_end(&k, 0).unwrap(), (0, 2));
|
||||
assert_eq!(loops.loop_start_end(&k, 4).unwrap(), (2, 6));
|
||||
assert_eq!(loops.loop_start_end(&k, 7).unwrap(), (6, 7));
|
||||
assert!(loops.loop_start_end(&k, 8).is_err());
|
||||
assert!(CurveLoops::new().loop_start_end(&k, 0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adjacency_and_sampling_wrap_within_the_second_loop() {
|
||||
use solidcore::curve::curve::Curve;
|
||||
use solidcore::curve::line::Line;
|
||||
use solidcore::math::Point3;
|
||||
use solidcore::nurbs::b_spline_surface_n_lib::p2::OrientType;
|
||||
use solidcore::topology::trimming_tools::curve_loops::CurveKey;
|
||||
|
||||
let store: Vec<Curve> = (0..7)
|
||||
.map(|i| {
|
||||
let x = f64::from(i);
|
||||
Curve::from(Line::from_points(
|
||||
Point3::new(x, 0.0, 0.0),
|
||||
Point3::new(x, 1.0, 0.0),
|
||||
3,
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
let keys: Vec<_> = (0..7).map(CurveKey).collect();
|
||||
let loops = CurveLoops::from_loops(&[3, 4], None, Some(&keys), &[OrientType::Same; 7]);
|
||||
for fix in [false, true] {
|
||||
let mut k = Kernel::default();
|
||||
k.fixes.curve_loop_start_after_previous_end = fix;
|
||||
let previous = if fix { 6 } else { 2 };
|
||||
let next = if fix { 3 } else { 2 };
|
||||
assert_eq!(
|
||||
loops
|
||||
.find_adjacent_loop_curve(&k, &store, CurveKey(3), 0.0)
|
||||
.unwrap(),
|
||||
previous
|
||||
);
|
||||
assert_eq!(
|
||||
loops
|
||||
.find_adjacent_loop_curve(&k, &store, CurveKey(6), 1.0)
|
||||
.unwrap(),
|
||||
next
|
||||
);
|
||||
let (_, before) = loops
|
||||
.sample_nearby_points(&k, &store, CurveKey(3), 0.0)
|
||||
.unwrap();
|
||||
let (_, after) = loops
|
||||
.sample_nearby_points(&k, &store, CurveKey(6), 1.0)
|
||||
.unwrap();
|
||||
assert_eq!(before.x, previous as f64);
|
||||
assert_eq!(after.x, next as f64);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
Updates: WR-96
|
||||
|
||||
#### WR-96 (update). Opt-in correction of later loop starts
|
||||
|
||||
- **Location:** `source/SMLib/src/SmTrimmingTools.cpp:4134`, `SmCurveLoops::GetLoopStartEnd`: `rlStart = rlEnd` copies an inclusive end instead of advancing past it. The pinned source retains this defect; the original record's `loops_start_end_second_loop` fixture confirms the C++ result.
|
||||
- **Rust port:** `Kernel::fixes.curve_loop_start_after_previous_end` (off by default) advances the start to the previous end plus one. `CurveLoops::loop_start_end` and `find_adjacent_loop_curve` now take `&Kernel`; `sample_nearby_points` forwards its kernel so adjacency and sampling use the same opt-in.
|
||||
- **Regression:** `crates/solidcore/tests/wr96_curve_loop_start.rs` checks first, second and singleton third loops, out-of-range indices, default C++ ranges, and both adjacency directions and endpoint sampling on the second loop. Before the formula change, the enabled range regression failed with `(2, 6)` instead of `(3, 6)` for counts `[3, 4, 1]`.
|
||||
- **Scope:** only the later-loop start assignment changes when enabled. Empty-loop count underflow and upstream's ignored-status behavior remain as before. Default difftest inputs and tolerances are unchanged.
|
||||
Reference in New Issue
Block a user