mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
Merge pull request #2188 from brett19/fix/2129-trim-uv-spans-split
fix: bspline_trim_preserves_uv_spans cleans a UV end span that is a pole (#2129)
This commit is contained in:
@@ -685,7 +685,10 @@ pub struct UpstreamFixes {
|
||||
pub uv_trim_joins_drop_pieces: bool,
|
||||
/// `BSplineCurve::trim` removes short end spans only for model-space curves
|
||||
/// (dimension 3), preserving exact UV trims in surface parameter units
|
||||
/// (WR-441, #1114). Off by default for pinned SMLib compatibility.
|
||||
/// (WR-441, #1114). A short UV end span whose control points all lie within check
|
||||
/// test #15's pole tolerance (`SM_ZONE_TOL_3D / 10`) is still removed as upstream
|
||||
/// removes it, so the trim passes the check (#2129). Off by default for pinned SMLib
|
||||
/// compatibility.
|
||||
pub bspline_trim_preserves_uv_spans: bool,
|
||||
/// `BSplineCurve::trim` keeps a short end span of a rational curve (its control points carry weights, `N_IsCrvRat`: a conic's NURB) instead of removing it with `N_CrvRemoveDegenSegs`, which stretches the remaining spans over its length and so moves a conic off its circle or ellipse (a trimmed half circle of radius 16 by about 1e-6, enough for its edge and vertex gaps to pass the tolerance); non-rational curves are cleaned as upstream (WR-575, #1529). An end span whose control points all lie within check test #15's pole tolerance (`SM_ZONE_TOL_3D / 10`) is not kept: the trim ends at its interior knot instead, on the conic and within that tolerance of the requested end, so the curve passes the check (#2090). Off by default for pinned SMLib compatibility.
|
||||
pub bspline_trim_keeps_rational_end_spans: bool,
|
||||
|
||||
@@ -95,7 +95,9 @@
|
||||
//! [`crate::kernel::UpstreamFixes::bspline_trim_preserves_uv_spans`], degenerate
|
||||
//! end-span removal applies only to dimension-3 model curves. UV spans are in
|
||||
//! surface parameter units and must not be removed using a 3D zone tolerance.
|
||||
//! The default retains pinned SMLib behavior.
|
||||
//! A short UV end span that collapses to a pole (its control points within check
|
||||
//! test #15's tolerance `SM_ZONE_TOL_3D / 10`) is still removed as upstream does
|
||||
//! (#2129). The default retains pinned SMLib behavior.
|
||||
//! - **Opt-in rational end spans (WR-575, #1529).** With
|
||||
//! [`crate::kernel::UpstreamFixes::bspline_trim_keeps_rational_end_spans`], a
|
||||
//! rational trimmed curve (a conic's NURB) keeps its short end spans: their removal
|
||||
@@ -2225,7 +2227,7 @@ impl BSplineCurve {
|
||||
/// zone tolerance mapped to parameter space) are removed. With
|
||||
/// [`crate::kernel::UpstreamFixes::bspline_trim_preserves_uv_spans`] enabled,
|
||||
/// that removal applies only to dimension-3 model curves, preserving UV spans
|
||||
/// exactly. With
|
||||
/// exactly, except a short UV end span that collapses to a pole (#2129). With
|
||||
/// [`TrimMode::IntervalOnly`] only the interval is computed.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -2401,7 +2403,18 @@ impl BSplineCurve {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!k.fixes.bspline_trim_preserves_uv_spans || self.dim == 3)
|
||||
// `fixes.bspline_trim_preserves_uv_spans` (WR-441): a UV curve keeps its
|
||||
// short end spans, unless one collapses to a pole that check test #15 would
|
||||
// report (`SM_ZONE_TOL_3D / 10`); that curve is cleaned as upstream (#2129)
|
||||
let cleans_spans = !k.fixes.bspline_trim_preserves_uv_spans
|
||||
|| self.dim == 3
|
||||
|| short_end_span_is_pole(
|
||||
cur1.cpts(),
|
||||
degree,
|
||||
knots.len() > 2 && head < zone_tol_1d_min,
|
||||
knots.len() > 2 && tail < zone_tol_1d_max,
|
||||
);
|
||||
if cleans_spans
|
||||
&& !keeps_rational
|
||||
&& ((head < zone_tol_1d_min && knots.len() > 2)
|
||||
|| (tail < zone_tol_1d_max && knots.len() > 2))
|
||||
@@ -2436,6 +2449,18 @@ impl BSplineCurve {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the short head span (`head`) or short tail span (`tail`) of a trimmed curve
|
||||
/// collapses to a pole as check test #15 measures one ([`end_span_is_pole`] with its
|
||||
/// tolerance `SM_ZONE_TOL_3D / 10`). Not upstream (#2129).
|
||||
fn short_end_span_is_pole(cpts: &[CPoint], degree: usize, head: bool, tail: bool) -> bool {
|
||||
let pole_tol = ZONE_TOL_3D / 10.0;
|
||||
let n_cpts = cpts.len();
|
||||
(head && end_span_is_pole(cpts, 0, degree, pole_tol))
|
||||
|| (tail
|
||||
&& n_cpts > degree
|
||||
&& end_span_is_pole(cpts, n_cpts - 1 - degree, degree, pole_tol))
|
||||
}
|
||||
|
||||
/// Whether the `degree + 1` control points of the span starting at control point
|
||||
/// `first` all lie within `tol` of the first, as check test #15 measures a pole
|
||||
/// (`SmBSplineCurve::HasInternalPole`, [`BSplineCurve::has_internal_pole`]); `false`
|
||||
|
||||
@@ -63,8 +63,14 @@ fn cylinder(k: &mut Kernel, radius: f64, seam: f64, axis: Vec3, angle: f64, to:
|
||||
/// A's seam `seam_a` degrees round from its bottom line (180 is its top, through the
|
||||
/// origin), B's `seam_b` round from its +x side (270 is its top).
|
||||
fn union(split: bool, seam_a: f64, seam_b: f64) -> (Kernel, Result<Brep>) {
|
||||
union_with(split, false, seam_a, seam_b)
|
||||
}
|
||||
|
||||
/// [`union`], with `UpstreamFixes::bspline_trim_preserves_uv_spans` set to `uv_spans`.
|
||||
fn union_with(split: bool, uv_spans: bool, seam_a: f64, seam_b: f64) -> (Kernel, Result<Brep>) {
|
||||
let mut k = Kernel::default();
|
||||
k.fixes.ssi_split_curves_at_singular_points = split;
|
||||
k.fixes.bspline_trim_preserves_uv_spans = uv_spans;
|
||||
let y = Vec3::new(0.0, 1.0, 0.0);
|
||||
let x = Vec3::new(1.0, 0.0, 0.0);
|
||||
let a = cylinder(&mut k, 10.0, seam_a, y, 90.0, Vec3::new(-20.0, 0.0, -10.0));
|
||||
@@ -78,7 +84,11 @@ fn check_lines(k: &mut Kernel, b: &mut Brep) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn assert_sound(seam_a: f64, seam_b: f64) {
|
||||
let (mut k, u) = union(true, seam_a, seam_b);
|
||||
assert_sound_with(false, seam_a, seam_b);
|
||||
}
|
||||
|
||||
fn assert_sound_with(uv_spans: bool, seam_a: f64, seam_b: f64) {
|
||||
let (mut k, u) = union_with(true, uv_spans, seam_a, seam_b);
|
||||
let mut u = u.unwrap_or_else(|e| panic!("seams {seam_a} {seam_b}: {e:?}"));
|
||||
assert!(brep_is_manifold_solid(&u).unwrap());
|
||||
let lines = check_lines(&mut k, &mut u);
|
||||
@@ -118,6 +128,19 @@ fn split_seams_through_the_node() {
|
||||
assert_sound(180.0, 270.0);
|
||||
}
|
||||
|
||||
/// #2129: with `bspline_trim_preserves_uv_spans` also on, A's seam at 270 with B's at
|
||||
/// 135 or 225 left a UV trim a head span 8e-8 long whose four control points lie within
|
||||
/// check test #15's pole tolerance (`SmBSplineCurve` #15, "Bad Repeated `CPts`"); that trim
|
||||
/// is now cleaned as upstream cleans it, and every placement checks clean.
|
||||
#[test]
|
||||
fn split_with_uv_spans_kept_checks_clean_at_every_seam() {
|
||||
for ia in 0..8 {
|
||||
for ib in 0..8 {
|
||||
assert_sound_with(true, 45.0 * f64::from(ia), 45.0 * f64::from(ib));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upstream_fails_the_check_at_the_issue_seams() {
|
||||
let (mut k, u) = union(false, 135.0, 225.0);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Updates: WR-441
|
||||
|
||||
#### WR-441 (update, 2026-11-01). A short UV end span that collapses to a pole is still cleaned (#2129)
|
||||
- **Trigger:** the #2110 tangent-cylinder union (`crates/solidcore/tests/issue_2110_tangent_cylinders_union.rs`) with `ssi_split_curves_at_singular_points` and `bspline_trim_preserves_uv_spans` both on, A's seam at 270 degrees and B's at 135 or 225. A UV trim is trimmed to `[3.928811601715155, 23.78128087946006]` (and `[13.326503945274428, 33.17897323660256]`), leaving a cubic head span `8.18e-8` (`9.54e-8`) long whose four control points lie within `8.2e-8` of each other in `(u, v)`. The flag kept that span, and `check_brep` Level2 reported `SmBSplineCurve` test #15 (Bad Repeated CPts, tolerance `SM_ZONE_TOL_3D / 10 = 1e-6`). With the flag off, upstream's `N_CrvRemoveDegenSegs` removes it and all 64 seam placements check clean.
|
||||
- **Rust port:** under the same flag, a dimension-2 curve whose short end span (shorter than the zone tolerance mapped to parameter space, as before) has all its `degree + 1` control points within check test #15's pole tolerance is cleaned as upstream cleans it (`N_CrvRemoveDegenSegs`); other short UV end spans are still kept. Flag off is unchanged. Regression: `split_with_uv_spans_kept_checks_clean_at_every_seam` in `crates/solidcore/tests/issue_2110_tangent_cylinders_union.rs` (all 64 placements clean at Level2 and within `1e-6` relative of the closed-form volume).
|
||||
Reference in New Issue
Block a user